Compare commits
34 Commits
Author | SHA1 | Date | |
---|---|---|---|
997934f680 | |||
26e69d6264 | |||
153eabcbf2 | |||
6d0145c335 | |||
81a79f9476 | |||
537f404fe0 | |||
eb29f76b9a | |||
56816dc060 | |||
899d5f3e5e | |||
c8c455bb57 | |||
5468fc0748 | |||
78516abf2e | |||
0424f98eb5 | |||
2188b8b2e2 | |||
0bf614a75c | |||
9f21f744a4 | |||
b94cda6205 | |||
3c0e4046a4 | |||
338c22a606 | |||
25dd895e0d | |||
ea9ef9e82a | |||
edd86eda77 | |||
671b857a79 | |||
408fd0f35e | |||
30184d08b1 | |||
95f257c47a | |||
41297c6712 | |||
a8e0ade0c8 | |||
3338e699c4 | |||
e07da3efa5 | |||
4f7f015250 | |||
2a4c15d0dc | |||
70ef894ec5 | |||
bb9179d5f9 |
87
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
Normal file
87
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
Normal file
@ -0,0 +1,87 @@
|
||||
name: Bug report
|
||||
description: Create a report to help us address issues you are facing
|
||||
title: "[Bug] "
|
||||
labels: [Bug]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to make us better!
|
||||
|
||||
- type: checkboxes
|
||||
id: duplication
|
||||
attributes:
|
||||
label: ⠀
|
||||
options:
|
||||
- label: This issue is not duplicated with any other open or closed issues
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Describe the bug
|
||||
description: A clear and concise description of what the bug is
|
||||
placeholder: |
|
||||
Example:
|
||||
App crashes on startup every time after changing settings.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: expected
|
||||
attributes:
|
||||
label: Expected behavior
|
||||
description: A clear and concise description of what you expected to happen
|
||||
placeholder: |
|
||||
Example:
|
||||
App started normally, everything worked fine.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: reproduce
|
||||
attributes:
|
||||
label: Steps to reproduce
|
||||
description: Steps to reproduce the bug
|
||||
placeholder: |
|
||||
Example:
|
||||
1. Change "HyperNet Server" to "127.0.1" in "Network" settings
|
||||
2. Restart the app
|
||||
3. Crash
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: environment
|
||||
attributes:
|
||||
label: Device information
|
||||
description: Provide details about your system environment
|
||||
placeholder: |
|
||||
Example:
|
||||
Device: Google Pixel 8 Pro
|
||||
System: Baklava (BP22.250124.009)
|
||||
Version*: 2.3.2
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: screenshots
|
||||
attributes:
|
||||
label: Screenshots
|
||||
description: If applicable, add screenshots to help explain your problem
|
||||
placeholder: |
|
||||
Example:
|
||||
setting_items.jpg
|
||||
crash_screen.jpg
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
id: additional
|
||||
attributes:
|
||||
label: Additional context
|
||||
description: Add any other context about the problem here
|
||||
placeholder: |
|
||||
Crash report or other useful informations
|
||||
validations:
|
||||
required: false
|
83
.github/ISSUE_TEMPLATE/bug_report_zh.yaml
vendored
Normal file
83
.github/ISSUE_TEMPLATE/bug_report_zh.yaml
vendored
Normal file
@ -0,0 +1,83 @@
|
||||
name: 问题反馈
|
||||
description: 提交 Bug 或其它问题的反馈
|
||||
title: "[Bug] 标题"
|
||||
labels: [Bug]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
非常感谢,你将要提交的反馈会让我们变得更好!
|
||||
|
||||
- type: checkboxes
|
||||
id: duplication
|
||||
attributes:
|
||||
label: ⠀
|
||||
options:
|
||||
- label: 我已经搜索并确认此 issue 不与其它任何 issue 重复
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: 问题描述
|
||||
description: 清楚且详细地描述你遇到的 Bug 或问题
|
||||
placeholder: |
|
||||
发生了什么?生动地描述你所看到的一切
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: expected
|
||||
attributes:
|
||||
label: 期望表现
|
||||
description: 清楚且详细地描述你期望发生的事
|
||||
placeholder: |
|
||||
什么功能应该正常运行,运行后会有什么结果
|
||||
什么界面应该正常显示,应该会显示什么内容
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: reproduce
|
||||
attributes:
|
||||
label: 复现步骤
|
||||
description: 能够复现问题的每一步
|
||||
placeholder: |
|
||||
1. 尽可能详细地描述每一步
|
||||
2. 更改的设置、添加的好友...
|
||||
3. 这里也可以描述你看到的界面
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: environment
|
||||
attributes:
|
||||
label: 环境/版本
|
||||
description: 提供运行时的环境信息
|
||||
placeholder: |
|
||||
示例:
|
||||
设备型号: Google Pixel 8 Pro
|
||||
系统板本: Baklava (BP22.250124.009)
|
||||
程序版本: 2.3.2
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: screenshots
|
||||
attributes:
|
||||
label: 屏幕截图/录制
|
||||
description: 提供截屏或录屏来更好地描述问题
|
||||
placeholder: |
|
||||
错误显示的界面/崩溃时的界面、先前改动的设置
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
id: additional
|
||||
attributes:
|
||||
label: 更多信息
|
||||
description: 任何与问题有关且有用的信息
|
||||
placeholder: |
|
||||
崩溃报告、日志,或是你的用户名
|
||||
validations:
|
||||
required: false
|
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
blank_issues_enabled: true
|
||||
contact_links:
|
||||
- name: Solsynth Releases
|
||||
url: https://files.solsynth.dev/production01/solian
|
||||
about: Another place to download released apps
|
59
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
Normal file
59
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
Normal file
@ -0,0 +1,59 @@
|
||||
name: Feature request
|
||||
description: Suggest features you want to add or suggest to modify existing features
|
||||
title: "[Feature] "
|
||||
labels: [Feature]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to make us better!
|
||||
|
||||
- type: checkboxes
|
||||
id: duplication
|
||||
attributes:
|
||||
label: ⠀
|
||||
options:
|
||||
- label: This issue is not duplicated with any other open or closed issues
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Describe the feature
|
||||
description: A clear and concise description of what the feature is
|
||||
placeholder: |
|
||||
Example:
|
||||
A Quick Settings tile to start the service, long press to launch the app.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: reasons
|
||||
attributes:
|
||||
label: Reason for adding
|
||||
description: Explain why this feature would be useful to you
|
||||
placeholder: |
|
||||
Example:
|
||||
Start the service quickly from the Quick Settings tile and save lots of time.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: examples
|
||||
attributes:
|
||||
label: Example(s)
|
||||
description: Post screenshots/drawings/links/etc of the feature request, or proof-of-concept images about the feature
|
||||
placeholder: |
|
||||
Example:
|
||||
shazam_toggle.jpg
|
||||
nekobox_switch.jpg
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
id: additional
|
||||
attributes:
|
||||
label: Additional context
|
||||
description: Add any other context about the feature here
|
||||
validations:
|
||||
required: false
|
49
.github/ISSUE_TEMPLATE/feature_request_zh.yaml
vendored
Normal file
49
.github/ISSUE_TEMPLATE/feature_request_zh.yaml
vendored
Normal file
@ -0,0 +1,49 @@
|
||||
name: 功能建议
|
||||
description: 提出你想要添加或更改的功能
|
||||
title: "[Feature] 标题"
|
||||
labels: [Feature]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
非常感谢,你将要提交的请求会让我们变得更好!
|
||||
|
||||
- type: checkboxes
|
||||
id: duplication
|
||||
attributes:
|
||||
label: ⠀
|
||||
options:
|
||||
- label: 我已经搜索并确认此 issue 不与其它任何 issue 重复
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: 功能描述
|
||||
description: 清楚且详细地描述要添加/更改后的功能
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: reasons
|
||||
attributes:
|
||||
label: 添加/更改理由
|
||||
description: 解释为什么要这样做,对用户有什么好处
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: examples
|
||||
attributes:
|
||||
label: 功能示例
|
||||
description: 相似/已存在功能的截图,或画出大致的界面
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
id: additional
|
||||
attributes:
|
||||
label: 更多信息
|
||||
description: 任何与功能有关且有用的信息,或已存在功能的代码/仓库
|
||||
validations:
|
||||
required: false
|
1
.github/workflows/nightly.yml
vendored
1
.github/workflows/nightly.yml
vendored
@ -55,6 +55,7 @@ jobs:
|
||||
sudo apt-get install libmpv-dev mpv
|
||||
sudo apt-get install libayatana-appindicator3-dev
|
||||
sudo apt-get install keybinder-3.0
|
||||
sudo apt-get install libnotify-dev
|
||||
- run: flutter pub get
|
||||
- run: flutter build linux
|
||||
- name: Archive production artifacts
|
||||
|
@ -12,9 +12,9 @@ post {
|
||||
|
||||
body:json {
|
||||
{
|
||||
"alias": "BaLoading",
|
||||
"name": "BaLoading",
|
||||
"attachment_id": "2JCI2uh21mKkfk9P",
|
||||
"pack_id": 3
|
||||
"alias": "Deadge",
|
||||
"name": "Dead",
|
||||
"attachment_id": "pcbFd0u4zgdM39HM",
|
||||
"pack_id": 4
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ meta {
|
||||
}
|
||||
|
||||
post {
|
||||
url: {{endpoint}}/cgi/id/dev/notify/122
|
||||
url: {{endpoint}}/cgi/id/dev/notify/328
|
||||
body: json
|
||||
auth: inherit
|
||||
}
|
||||
@ -15,9 +15,9 @@ body:json {
|
||||
"client_id": "{{third_client_id}}",
|
||||
"client_secret":"{{third_client_tk}}",
|
||||
"type": "general",
|
||||
"subject": "处理该帐号 @solian 的决定",
|
||||
"subtitle": "违反用户协议",
|
||||
"content": "您的帐号违反了我们用户协议中关于冒充我们官方的行为,至此做出停权的决定。还请见谅。该决定是最终决定,不接受上诉。",
|
||||
"subject": "处理该发布者 @vedal987 的决定",
|
||||
"subtitle": "一条来自 Solar Network 客户支持的信息",
|
||||
"content": "您的发布者违反了我们用户协议中的「禁止冒充他人」的相关条例,经管理决定,将相关内容隐藏。冒充他人的判定无论作者是否有主观意志,只要造成了误解我们就有责任处理。希望您能理解,本次决定未作出任何帐号相关的连带处罚。",
|
||||
"priority": 10
|
||||
}
|
||||
}
|
||||
|
@ -548,6 +548,7 @@
|
||||
"termAcceptNextWithAgree": "By clicking the \"Next\", it means you agree to our terms and its updates.",
|
||||
"unauthorized": "Unauthorized",
|
||||
"unauthorizedDescription": "Login to explore the entire Solar Network.",
|
||||
"projectDetail": "Project Details",
|
||||
"serviceStatus": "Service Status",
|
||||
"termRelated": "Related Terms",
|
||||
"appDetails": "App Details",
|
||||
@ -583,6 +584,7 @@
|
||||
"colorSchemeBlack": "Black",
|
||||
"colorSchemeApplied": "Color scheme has been applied, may need restart the app to take effect.",
|
||||
"postFeaturedComment": "Featured Comment",
|
||||
"postCategory": "Category",
|
||||
"postCategoryTechnology": "Technology",
|
||||
"postCategoryGaming": "Gaming",
|
||||
"postCategoryLife": "Life",
|
||||
@ -625,6 +627,7 @@
|
||||
"realmJoin": "Join Realm",
|
||||
"realmCommunityHint": "This realm is a community realm, you can freely join.",
|
||||
"realmCommunityPublicChannelsHint": "The public channels in this realm",
|
||||
"realmCommunityPublishersHint": "The publishers in this realm",
|
||||
"realmJoined": "Joined realm {}.",
|
||||
"join": "Join",
|
||||
"pollEditorNew": "New Poll",
|
||||
@ -665,5 +668,55 @@
|
||||
"zero": "No views",
|
||||
"one": "{} view",
|
||||
"other": "{} views"
|
||||
}
|
||||
},
|
||||
"attachmentBillingUploaded": "Used space",
|
||||
"attachmentBillingDiscount": "Free space",
|
||||
"attachmentBillingRatio": "Usage",
|
||||
"attachmentBillingHint": "Sliding Window Pricing®\nFees will only apply if the size of the file uploaded within 24 hours exceeds the free space.",
|
||||
"postThumbnail": "Post Thumbnail",
|
||||
"accountRealms": "Realms",
|
||||
"postInGlobal": "Global",
|
||||
"postInGlobalDescription": "Do not link this post with any realm.",
|
||||
"postChannelGlobal": "Global",
|
||||
"postChannelFriends": "Friends",
|
||||
"postChannelFollowing": "Following",
|
||||
"postChannelRealm": "Realms",
|
||||
"postFilterReset": "Reset Filter",
|
||||
"postFilterResetDescription": "Clear filter and show all posts.",
|
||||
"postFilterWithCategory": "Viewing posts in {}",
|
||||
"databaseSize": "Database Size",
|
||||
"databaseDelete": "Delete Database",
|
||||
"databaseDeleteDescription": "Remove the database on your local disk, the content will be fetched from server again.",
|
||||
"databaseDeleted": "The local database has been deleted.",
|
||||
"settingsEnablePushNotifications": "Enable Push Notifications",
|
||||
"settingsEnablePushNotificationsDescription": "Re-enable and request permission to receive push notifications. Just in case it didn't run automatically.",
|
||||
"settingsEnabledPushNotifications": "Push notification has been enabled.",
|
||||
"screenStickers": "Stickers",
|
||||
"stickersDiscovery": "Discovery",
|
||||
"stickersOwned": "Owned",
|
||||
"stickersCreated": "Created",
|
||||
"stickersAdd": "Add Sticker Pack",
|
||||
"stickersAdded": "Sticker pack has been added.",
|
||||
"add": "Add",
|
||||
"stickersRemoved": "Sticker pack has been removed, you can add it again anytime.",
|
||||
"stickersReload": "Reload Stickers",
|
||||
"stickersReloadDescription": "Reload stickers from the server, update the sticker picker.",
|
||||
"stickersReloaded": "Sticker packs has been reloaded.",
|
||||
"stickersPackDelete": "Delete Pack {}",
|
||||
"stickersPackDeleteDescription": "Are you sure you want to delete this sticker pack? This operation is irreversible.",
|
||||
"stickersPackDeleted": "Sticker pack has been deleted.",
|
||||
"stickersDelete": "Delete Sticker {}",
|
||||
"stickersDeleteDescription": "Are you sure you want to delete this sticker? This operation is irreversible.",
|
||||
"stickersDeleted": "Sticker has been deleted.",
|
||||
"fieldStickerName": "Sticker Name",
|
||||
"fieldStickerAlias": "Sticker Alias",
|
||||
"fieldStickerAliasHint": "The unique sticker placeholder with the pack prefix.",
|
||||
"fieldStickerPackName": "Name",
|
||||
"fieldStickerPackDescription": "Description",
|
||||
"fieldStickerPackPrefix": "Prefix",
|
||||
"fieldStickerAttachment": "Attachment",
|
||||
"stickersNew": "New Sticker",
|
||||
"stickersNewDescription": "Create a new sticker belongs to this pack.",
|
||||
"stickersPackNew": "New Sticker Pack",
|
||||
"trayMenuShow": "Show"
|
||||
}
|
||||
|
@ -546,6 +546,7 @@
|
||||
"termAcceptNextWithAgree": "点击 “下一步”,即表示你同意我们的各项条款,包括其之后的更新。",
|
||||
"unauthorized": "未登陆",
|
||||
"unauthorizedDescription": "登陆以探索整个 Solar Network。",
|
||||
"projectDetail": "项目详情",
|
||||
"serviceStatus": "服务状态",
|
||||
"termRelated": "相关条款",
|
||||
"appDetails": "应用程序详情",
|
||||
@ -581,6 +582,7 @@
|
||||
"colorSchemeBlack": "黑色",
|
||||
"colorSchemeApplied": "主题色已应用,可能需要重启来生效。",
|
||||
"postFeaturedComment": "精选评论",
|
||||
"postCategory": "分类",
|
||||
"postCategoryTechnology": "技术",
|
||||
"postCategoryGaming": "游戏",
|
||||
"postCategoryLife": "生活",
|
||||
@ -624,6 +626,7 @@
|
||||
"realmJoin": "加入领域",
|
||||
"realmCommunityHint": "该领域是一个社区领域,你可以自由加入。",
|
||||
"realmCommunityPublicChannelsHint": "该领域包含的公共频道",
|
||||
"realmCommunityPublishersHint": "该领域的发布者",
|
||||
"realmJoined": "已加入领域 {}。",
|
||||
"join": "加入",
|
||||
"pollEditorNew": "新投票",
|
||||
@ -664,5 +667,54 @@
|
||||
"zero": "{} 次浏览",
|
||||
"one": "{} 次浏览",
|
||||
"other": "{} 次浏览"
|
||||
}
|
||||
},
|
||||
"attachmentBillingUploaded": "已占用的字节数",
|
||||
"attachmentBillingDiscount": "免费的字节数",
|
||||
"attachmentBillingHint": "滑动窗口计价®\n在24小时内上传的文件大小超出免费空间才会适用扣费。",
|
||||
"postThumbnail": "帖子缩略图",
|
||||
"accountRealms": "领域",
|
||||
"postInGlobal": "全站",
|
||||
"postInGlobalDescription": "不关联此帖子与任何领域。",
|
||||
"postChannelGlobal": "全站",
|
||||
"postChannelFriends": "好友",
|
||||
"postChannelFollowing": "关注",
|
||||
"postChannelRealm": "领域",
|
||||
"postFilterReset": "重置过滤器",
|
||||
"postFilterResetDescription": "清除过滤器并显示所有帖子。",
|
||||
"postFilterWithCategory": "查看{}区中的帖子",
|
||||
"databaseSize": "数据库大小",
|
||||
"databaseDelete": "删除数据库",
|
||||
"databaseDeleteDescription": "删除本地数据库,内容将从服务器重新获取。",
|
||||
"databaseDeleted": "本地数据库已被删除。",
|
||||
"settingsEnablePushNotifications": "启用推送数据",
|
||||
"settingsEnablePushNotificationsDescription": "重新启用并请求推送权限,以防自动激活失败。",
|
||||
"settingsEnabledPushNotifications": "推送通知已经注册。",
|
||||
"screenStickers": "贴图",
|
||||
"stickersDiscovery": "发现",
|
||||
"stickersOwned": "由我拥有",
|
||||
"stickersCreated": "由我发布",
|
||||
"stickersAdd": "添加贴图包",
|
||||
"stickersAdded": "贴图包已添加。",
|
||||
"add": "添加",
|
||||
"stickersRemoved": "贴图包已被移除,你可以随时再次添加回来。",
|
||||
"stickersReload": "重载贴图包",
|
||||
"stickersReloadDescription": "从服务器重新加载添加过的贴图,更新贴图选择器。",
|
||||
"stickersReloaded": "贴图包已重载。",
|
||||
"stickersPackDelete": "删除贴图包 {}",
|
||||
"stickersPackDeleteDescription": "你确定要删除这个贴图包吗?这个操作不可撤销。",
|
||||
"stickersPackDeleted": "贴图包已被删除。",
|
||||
"stickersDelete": "删除贴图 {}",
|
||||
"stickersDeleteDescription": "你确定要删除这个贴图吗?这个操作不可撤销。",
|
||||
"stickersDeleted": "贴图已被删除。",
|
||||
"fieldStickerName": "贴图名称",
|
||||
"fieldStickerAlias": "贴图别名",
|
||||
"fieldStickerAliasHint": "和贴图包前缀组合成为本贴图的唯一占位符。",
|
||||
"fieldStickerPackName": "名称",
|
||||
"fieldStickerPackDescription": "描述",
|
||||
"fieldStickerPackPrefix": "贴图包前缀",
|
||||
"fieldStickerAttachment": "附件",
|
||||
"stickersNew": "新建贴图",
|
||||
"stickersNewDescription": "创建一个新的贴图。",
|
||||
"stickersPackNew": "新建贴图包",
|
||||
"trayMenuShow": "显示"
|
||||
}
|
||||
|
@ -546,6 +546,7 @@
|
||||
"termAcceptNextWithAgree": "點擊 “下一步”,即表示你同意我們的各項條款,包括其之後的更新。",
|
||||
"unauthorized": "未登陸",
|
||||
"unauthorizedDescription": "登陸以探索整個 Solar Network。",
|
||||
"projectDetail": "項目詳情",
|
||||
"serviceStatus": "服務狀態",
|
||||
"termRelated": "相關條款",
|
||||
"appDetails": "應用程序詳情",
|
||||
@ -581,6 +582,7 @@
|
||||
"colorSchemeBlack": "黑色",
|
||||
"colorSchemeApplied": "主題色已應用,可能需要重啓來生效。",
|
||||
"postFeaturedComment": "精選評論",
|
||||
"postCategory": "分類",
|
||||
"postCategoryTechnology": "技術",
|
||||
"postCategoryGaming": "遊戲",
|
||||
"postCategoryLife": "生活",
|
||||
@ -624,6 +626,7 @@
|
||||
"realmJoin": "加入領域",
|
||||
"realmCommunityHint": "該領域是一個社區領域,你可以自由加入。",
|
||||
"realmCommunityPublicChannelsHint": "該領域包含的公共頻道",
|
||||
"realmCommunityPublishersHint": "該領域的發佈者",
|
||||
"realmJoined": "已加入領域 {}。",
|
||||
"join": "加入",
|
||||
"pollEditorNew": "新投票",
|
||||
@ -664,5 +667,53 @@
|
||||
"zero": "{} 次瀏覽",
|
||||
"one": "{} 次瀏覽",
|
||||
"other": "{} 次瀏覽"
|
||||
}
|
||||
},
|
||||
"attachmentBillingUploaded": "已佔用的字節數",
|
||||
"attachmentBillingDiscount": "免費的字節數",
|
||||
"attachmentBillingHint": "滑動窗口計價®\n在24小時內上傳的文件大小超出免費空間才會適用扣費。",
|
||||
"postThumbnail": "帖子縮略圖",
|
||||
"accountRealms": "領域",
|
||||
"postInGlobal": "全站",
|
||||
"postInGlobalDescription": "不關聯此帖子與任何領域。",
|
||||
"postChannelGlobal": "全站",
|
||||
"postChannelFriends": "好友",
|
||||
"postChannelFollowing": "關注",
|
||||
"postChannelRealm": "領域",
|
||||
"postFilterReset": "重置過濾器",
|
||||
"postFilterResetDescription": "清除過濾器並顯示所有帖子。",
|
||||
"postFilterWithCategory": "查看{}區中的帖子",
|
||||
"databaseSize": "數據庫大小",
|
||||
"databaseDelete": "刪除數據庫",
|
||||
"databaseDeleteDescription": "刪除本地數據庫,內容將從服務器重新獲取。",
|
||||
"databaseDeleted": "本地數據庫已被刪除。",
|
||||
"settingsEnablePushNotifications": "啓用推送數據",
|
||||
"settingsEnablePushNotificationsDescription": "重新啓用並請求推送權限,以防自動激活失敗。",
|
||||
"settingsEnabledPushNotifications": "推送通知已經註冊。",
|
||||
"screenStickers": "貼圖",
|
||||
"stickersDiscovery": "發現",
|
||||
"stickersOwned": "由我擁有",
|
||||
"stickersCreated": "由我發佈",
|
||||
"stickersAdd": "添加貼圖包",
|
||||
"stickersAdded": "貼圖包已添加。",
|
||||
"add": "添加",
|
||||
"stickersRemoved": "貼圖包已被移除,你可以隨時再次添加回來。",
|
||||
"stickersReload": "重載貼圖包",
|
||||
"stickersReloadDescription": "從服務器重新加載添加過的貼圖,更新貼圖選擇器。",
|
||||
"stickersReloaded": "貼圖包已重載。",
|
||||
"stickersPackDelete": "刪除貼圖包 {}",
|
||||
"stickersPackDeleteDescription": "你確定要刪除這個貼圖包嗎?這個操作不可撤銷。",
|
||||
"stickersPackDeleted": "貼圖包已被刪除。",
|
||||
"stickersDelete": "刪除貼圖 {}",
|
||||
"stickersDeleteDescription": "你確定要刪除這個貼圖嗎?這個操作不可撤銷。",
|
||||
"stickersDeleted": "貼圖已被刪除。",
|
||||
"fieldStickerName": "貼圖名稱",
|
||||
"fieldStickerAlias": "貼圖別名",
|
||||
"fieldStickerAliasHint": "和貼圖包前綴組合成為本貼圖的唯一佔位符。",
|
||||
"fieldStickerPackName": "名稱",
|
||||
"fieldStickerPackDescription": "描述",
|
||||
"fieldStickerPackPrefix": "貼圖包前綴",
|
||||
"fieldStickerAttachment": "附件",
|
||||
"stickersNew": "新建貼圖",
|
||||
"stickersNewDescription": "創建一個新的貼圖。",
|
||||
"stickersPackNew": "新建貼圖包"
|
||||
}
|
||||
|
@ -546,6 +546,7 @@
|
||||
"termAcceptNextWithAgree": "點擊 “下一步”,即表示你同意我們的各項條款,包括其之後的更新。",
|
||||
"unauthorized": "未登陸",
|
||||
"unauthorizedDescription": "登陸以探索整個 Solar Network。",
|
||||
"projectDetail": "項目詳情",
|
||||
"serviceStatus": "服務狀態",
|
||||
"termRelated": "相關條款",
|
||||
"appDetails": "應用程序詳情",
|
||||
@ -581,6 +582,7 @@
|
||||
"colorSchemeBlack": "黑色",
|
||||
"colorSchemeApplied": "主題色已應用,可能需要重啟來生效。",
|
||||
"postFeaturedComment": "精選評論",
|
||||
"postCategory": "分類",
|
||||
"postCategoryTechnology": "技術",
|
||||
"postCategoryGaming": "遊戲",
|
||||
"postCategoryLife": "生活",
|
||||
@ -624,6 +626,7 @@
|
||||
"realmJoin": "加入領域",
|
||||
"realmCommunityHint": "該領域是一個社區領域,你可以自由加入。",
|
||||
"realmCommunityPublicChannelsHint": "該領域包含的公共頻道",
|
||||
"realmCommunityPublishersHint": "該領域的發佈者",
|
||||
"realmJoined": "已加入領域 {}。",
|
||||
"join": "加入",
|
||||
"pollEditorNew": "新投票",
|
||||
@ -664,5 +667,53 @@
|
||||
"zero": "{} 次瀏覽",
|
||||
"one": "{} 次瀏覽",
|
||||
"other": "{} 次瀏覽"
|
||||
}
|
||||
},
|
||||
"attachmentBillingUploaded": "已佔用的字節數",
|
||||
"attachmentBillingDiscount": "免費的字節數",
|
||||
"attachmentBillingHint": "滑動窗口計價®\n在24小時內上傳的文件大小超出免費空間才會適用扣費。",
|
||||
"postThumbnail": "帖子縮略圖",
|
||||
"accountRealms": "領域",
|
||||
"postInGlobal": "全站",
|
||||
"postInGlobalDescription": "不關聯此帖子與任何領域。",
|
||||
"postChannelGlobal": "全站",
|
||||
"postChannelFriends": "好友",
|
||||
"postChannelFollowing": "關注",
|
||||
"postChannelRealm": "領域",
|
||||
"postFilterReset": "重置過濾器",
|
||||
"postFilterResetDescription": "清除過濾器並顯示所有帖子。",
|
||||
"postFilterWithCategory": "查看{}區中的帖子",
|
||||
"databaseSize": "數據庫大小",
|
||||
"databaseDelete": "刪除數據庫",
|
||||
"databaseDeleteDescription": "刪除本地數據庫,內容將從服務器重新獲取。",
|
||||
"databaseDeleted": "本地數據庫已被刪除。",
|
||||
"settingsEnablePushNotifications": "啟用推送數據",
|
||||
"settingsEnablePushNotificationsDescription": "重新啟用並請求推送權限,以防自動激活失敗。",
|
||||
"settingsEnabledPushNotifications": "推送通知已經註冊。",
|
||||
"screenStickers": "貼圖",
|
||||
"stickersDiscovery": "發現",
|
||||
"stickersOwned": "由我擁有",
|
||||
"stickersCreated": "由我發佈",
|
||||
"stickersAdd": "添加貼圖包",
|
||||
"stickersAdded": "貼圖包已添加。",
|
||||
"add": "添加",
|
||||
"stickersRemoved": "貼圖包已被移除,你可以隨時再次添加回來。",
|
||||
"stickersReload": "重載貼圖包",
|
||||
"stickersReloadDescription": "從服務器重新加載添加過的貼圖,更新貼圖選擇器。",
|
||||
"stickersReloaded": "貼圖包已重載。",
|
||||
"stickersPackDelete": "刪除貼圖包 {}",
|
||||
"stickersPackDeleteDescription": "你確定要刪除這個貼圖包嗎?這個操作不可撤銷。",
|
||||
"stickersPackDeleted": "貼圖包已被刪除。",
|
||||
"stickersDelete": "刪除貼圖 {}",
|
||||
"stickersDeleteDescription": "你確定要刪除這個貼圖嗎?這個操作不可撤銷。",
|
||||
"stickersDeleted": "貼圖已被刪除。",
|
||||
"fieldStickerName": "貼圖名稱",
|
||||
"fieldStickerAlias": "貼圖別名",
|
||||
"fieldStickerAliasHint": "和貼圖包前綴組合成為本貼圖的唯一佔位符。",
|
||||
"fieldStickerPackName": "名稱",
|
||||
"fieldStickerPackDescription": "描述",
|
||||
"fieldStickerPackPrefix": "貼圖包前綴",
|
||||
"fieldStickerAttachment": "附件",
|
||||
"stickersNew": "新建貼圖",
|
||||
"stickersNewDescription": "創建一個新的貼圖。",
|
||||
"stickersPackNew": "新建貼圖包"
|
||||
}
|
||||
|
117
ios/Podfile.lock
117
ios/Podfile.lock
@ -42,58 +42,58 @@ PODS:
|
||||
- Flutter
|
||||
- file_saver (0.0.1):
|
||||
- Flutter
|
||||
- Firebase/Analytics (11.7.0):
|
||||
- Firebase/Analytics (11.8.0):
|
||||
- Firebase/Core
|
||||
- Firebase/Core (11.7.0):
|
||||
- Firebase/Core (11.8.0):
|
||||
- Firebase/CoreOnly
|
||||
- FirebaseAnalytics (~> 11.7.0)
|
||||
- Firebase/CoreOnly (11.7.0):
|
||||
- FirebaseCore (~> 11.7.0)
|
||||
- Firebase/Messaging (11.7.0):
|
||||
- FirebaseAnalytics (~> 11.8.0)
|
||||
- Firebase/CoreOnly (11.8.0):
|
||||
- FirebaseCore (~> 11.8.0)
|
||||
- Firebase/Messaging (11.8.0):
|
||||
- Firebase/CoreOnly
|
||||
- FirebaseMessaging (~> 11.7.0)
|
||||
- firebase_analytics (11.4.2):
|
||||
- Firebase/Analytics (= 11.7.0)
|
||||
- FirebaseMessaging (~> 11.8.0)
|
||||
- firebase_analytics (11.4.3):
|
||||
- Firebase/Analytics (= 11.8.0)
|
||||
- firebase_core
|
||||
- Flutter
|
||||
- firebase_core (3.11.0):
|
||||
- Firebase/CoreOnly (= 11.7.0)
|
||||
- firebase_core (3.12.0):
|
||||
- Firebase/CoreOnly (= 11.8.0)
|
||||
- Flutter
|
||||
- firebase_messaging (15.2.2):
|
||||
- Firebase/Messaging (= 11.7.0)
|
||||
- firebase_messaging (15.2.3):
|
||||
- Firebase/Messaging (= 11.8.0)
|
||||
- firebase_core
|
||||
- Flutter
|
||||
- FirebaseAnalytics (11.7.0):
|
||||
- FirebaseAnalytics/AdIdSupport (= 11.7.0)
|
||||
- FirebaseCore (~> 11.7.0)
|
||||
- FirebaseAnalytics (11.8.0):
|
||||
- FirebaseAnalytics/AdIdSupport (= 11.8.0)
|
||||
- FirebaseCore (~> 11.8.0)
|
||||
- FirebaseInstallations (~> 11.0)
|
||||
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
|
||||
- GoogleUtilities/MethodSwizzler (~> 8.0)
|
||||
- GoogleUtilities/Network (~> 8.0)
|
||||
- "GoogleUtilities/NSData+zlib (~> 8.0)"
|
||||
- nanopb (~> 3.30910.0)
|
||||
- FirebaseAnalytics/AdIdSupport (11.7.0):
|
||||
- FirebaseCore (~> 11.7.0)
|
||||
- FirebaseAnalytics/AdIdSupport (11.8.0):
|
||||
- FirebaseCore (~> 11.8.0)
|
||||
- FirebaseInstallations (~> 11.0)
|
||||
- GoogleAppMeasurement (= 11.7.0)
|
||||
- GoogleAppMeasurement (= 11.8.0)
|
||||
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
|
||||
- GoogleUtilities/MethodSwizzler (~> 8.0)
|
||||
- GoogleUtilities/Network (~> 8.0)
|
||||
- "GoogleUtilities/NSData+zlib (~> 8.0)"
|
||||
- nanopb (~> 3.30910.0)
|
||||
- FirebaseCore (11.7.0):
|
||||
- FirebaseCoreInternal (~> 11.7.0)
|
||||
- FirebaseCore (11.8.1):
|
||||
- FirebaseCoreInternal (~> 11.8.0)
|
||||
- GoogleUtilities/Environment (~> 8.0)
|
||||
- GoogleUtilities/Logger (~> 8.0)
|
||||
- FirebaseCoreInternal (11.7.0):
|
||||
- FirebaseCoreInternal (11.8.0):
|
||||
- "GoogleUtilities/NSData+zlib (~> 8.0)"
|
||||
- FirebaseInstallations (11.7.0):
|
||||
- FirebaseCore (~> 11.7.0)
|
||||
- FirebaseInstallations (11.8.0):
|
||||
- FirebaseCore (~> 11.8.0)
|
||||
- GoogleUtilities/Environment (~> 8.0)
|
||||
- GoogleUtilities/UserDefaults (~> 8.0)
|
||||
- PromisesObjC (~> 2.4)
|
||||
- FirebaseMessaging (11.7.0):
|
||||
- FirebaseCore (~> 11.7.0)
|
||||
- FirebaseMessaging (11.8.0):
|
||||
- FirebaseCore (~> 11.8.0)
|
||||
- FirebaseInstallations (~> 11.0)
|
||||
- GoogleDataTransport (~> 10.0)
|
||||
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
|
||||
@ -122,21 +122,21 @@ PODS:
|
||||
- gal (1.0.0):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- GoogleAppMeasurement (11.7.0):
|
||||
- GoogleAppMeasurement/AdIdSupport (= 11.7.0)
|
||||
- GoogleAppMeasurement (11.8.0):
|
||||
- GoogleAppMeasurement/AdIdSupport (= 11.8.0)
|
||||
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
|
||||
- GoogleUtilities/MethodSwizzler (~> 8.0)
|
||||
- GoogleUtilities/Network (~> 8.0)
|
||||
- "GoogleUtilities/NSData+zlib (~> 8.0)"
|
||||
- nanopb (~> 3.30910.0)
|
||||
- GoogleAppMeasurement/AdIdSupport (11.7.0):
|
||||
- GoogleAppMeasurement/WithoutAdIdSupport (= 11.7.0)
|
||||
- GoogleAppMeasurement/AdIdSupport (11.8.0):
|
||||
- GoogleAppMeasurement/WithoutAdIdSupport (= 11.8.0)
|
||||
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
|
||||
- GoogleUtilities/MethodSwizzler (~> 8.0)
|
||||
- GoogleUtilities/Network (~> 8.0)
|
||||
- "GoogleUtilities/NSData+zlib (~> 8.0)"
|
||||
- nanopb (~> 3.30910.0)
|
||||
- GoogleAppMeasurement/WithoutAdIdSupport (11.7.0):
|
||||
- GoogleAppMeasurement/WithoutAdIdSupport (11.8.0):
|
||||
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
|
||||
- GoogleUtilities/MethodSwizzler (~> 8.0)
|
||||
- GoogleUtilities/Network (~> 8.0)
|
||||
@ -210,9 +210,9 @@ PODS:
|
||||
- SAMKeychain (1.5.3)
|
||||
- screen_brightness_ios (0.1.0):
|
||||
- Flutter
|
||||
- SDWebImage (5.20.0):
|
||||
- SDWebImage/Core (= 5.20.0)
|
||||
- SDWebImage/Core (5.20.0)
|
||||
- SDWebImage (5.20.1):
|
||||
- SDWebImage/Core (= 5.20.1)
|
||||
- SDWebImage/Core (5.20.1)
|
||||
- share_plus (0.0.1):
|
||||
- Flutter
|
||||
- shared_preferences_foundation (0.0.1):
|
||||
@ -221,6 +221,25 @@ PODS:
|
||||
- sqflite_darwin (0.0.4):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- sqlite3 (3.49.1):
|
||||
- sqlite3/common (= 3.49.1)
|
||||
- sqlite3/common (3.49.1)
|
||||
- sqlite3/dbstatvtab (3.49.1):
|
||||
- sqlite3/common
|
||||
- sqlite3/fts5 (3.49.1):
|
||||
- sqlite3/common
|
||||
- sqlite3/perf-threadsafe (3.49.1):
|
||||
- sqlite3/common
|
||||
- sqlite3/rtree (3.49.1):
|
||||
- sqlite3/common
|
||||
- sqlite3_flutter_libs (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- sqlite3 (~> 3.49.0)
|
||||
- sqlite3/dbstatvtab
|
||||
- sqlite3/fts5
|
||||
- sqlite3/perf-threadsafe
|
||||
- sqlite3/rtree
|
||||
- SwiftyGif (5.4.5)
|
||||
- url_launcher_ios (0.0.1):
|
||||
- Flutter
|
||||
@ -268,6 +287,7 @@ DEPENDENCIES:
|
||||
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
||||
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||
- sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`)
|
||||
- sqlite3_flutter_libs (from `.symlinks/plugins/sqlite3_flutter_libs/darwin`)
|
||||
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||
- video_compress (from `.symlinks/plugins/video_compress/ios`)
|
||||
- volume_controller (from `.symlinks/plugins/volume_controller/ios`)
|
||||
@ -294,6 +314,7 @@ SPEC REPOS:
|
||||
- PromisesObjC
|
||||
- SAMKeychain
|
||||
- SDWebImage
|
||||
- sqlite3
|
||||
- SwiftyGif
|
||||
- WebRTC-SDK
|
||||
|
||||
@ -360,6 +381,8 @@ EXTERNAL SOURCES:
|
||||
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
|
||||
sqflite_darwin:
|
||||
:path: ".symlinks/plugins/sqflite_darwin/darwin"
|
||||
sqlite3_flutter_libs:
|
||||
:path: ".symlinks/plugins/sqlite3_flutter_libs/darwin"
|
||||
url_launcher_ios:
|
||||
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
||||
video_compress:
|
||||
@ -380,23 +403,23 @@ SPEC CHECKSUMS:
|
||||
DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
|
||||
file_picker: b159e0c068aef54932bb15dc9fd1571818edaf49
|
||||
file_saver: 503e386464dbe118f630e17b4c2e1190fa0cf808
|
||||
Firebase: a64bf6a8546e6eab54f1c715cd6151f39d2329f4
|
||||
firebase_analytics: 7236e6115c1b4e62c2270faa29c052a317e31107
|
||||
firebase_core: aa979ae726f00b3ef4ccf59dfb96170af84efbd4
|
||||
firebase_messaging: 3af84b6a90aeac4d7a67fbf4c43a91e7083bea1f
|
||||
FirebaseAnalytics: bc9e565af9044ba8d6c6e4157e4edca8e5fdf7ec
|
||||
FirebaseCore: 3227e35f4197a924206fbcdc0349325baf4f5de4
|
||||
FirebaseCoreInternal: d6c17dafc8dc33614733a8b52df78fcb4394c881
|
||||
FirebaseInstallations: 9347e719c3d52d8d7b9074b2c32407dd027305e9
|
||||
FirebaseMessaging: 00ece041b71ddb52a2862ffdee73fb6e9824bd0c
|
||||
Firebase: d80354ed7f6df5f9aca55e9eb47cc4b634735eaf
|
||||
firebase_analytics: 7ec1166af61987fa968766eb11587c562a5650ee
|
||||
firebase_core: 6e223dfa350b2edceb729cea505eaaef59330682
|
||||
firebase_messaging: 07fde77ae28c08616a1d4d870450efc2b38cf40d
|
||||
FirebaseAnalytics: 4fd42def128146e24e480e89f310e3d8534ea42b
|
||||
FirebaseCore: 99fe0c4b44a39f37d99e6404e02009d2db5d718d
|
||||
FirebaseCoreInternal: df24ce5af28864660ecbd13596fc8dd3a8c34629
|
||||
FirebaseInstallations: 6c963bd2a86aca0481eef4f48f5a4df783ae5917
|
||||
FirebaseMessaging: 487b634ccdf6f7b7ff180fdcb2a9935490f764e8
|
||||
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
||||
flutter_app_update: 65f61da626cb111d1b24674abc4b01728d7723bc
|
||||
flutter_inappwebview_ios: 6f63631e2c62a7c350263b13fa5427aedefe81d4
|
||||
flutter_native_splash: f71420956eb811e6d310720fee915f1d42852e7a
|
||||
flutter_native_splash: df59bb2e1421aa0282cb2e95618af4dcb0c56c29
|
||||
flutter_udid: b2417673f287ee62817a1de3d1643f47b9f508ab
|
||||
flutter_webrtc: 90260f83024b1b96d239a575ea4e3708e79344d1
|
||||
gal: 6a522c75909f1244732d4596d11d6a2f86ff37a5
|
||||
GoogleAppMeasurement: 0471a5b5bff51f3a91b1e76df22c952d04c63967
|
||||
GoogleAppMeasurement: fc0817122bd4d4189164f85374e06773b9561896
|
||||
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
|
||||
GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d
|
||||
home_widget: 0434835a4c9a75704264feff6be17ea40e0f0d57
|
||||
@ -417,10 +440,12 @@ SPEC CHECKSUMS:
|
||||
receive_sharing_intent: 79c848f5b045674ad60b9fea3bafea59962ad2c1
|
||||
SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c
|
||||
screen_brightness_ios: 715ca807df953bf676d339f11464e438143ee625
|
||||
SDWebImage: 73c6079366fea25fa4bb9640d5fb58f0893facd8
|
||||
SDWebImage: 33d0f23bddeb5d209ae959153883247be6703713
|
||||
share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f
|
||||
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
|
||||
sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d
|
||||
sqlite3: fc1400008a9b3525f5914ed715a5d1af0b8f4983
|
||||
sqlite3_flutter_libs: 069c435986dd4b63461aecd68f4b30be4a9e9daa
|
||||
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
|
||||
url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe
|
||||
video_compress: fce97e4fb1dfd88175aa07d2ffc8a2f297f87fbe
|
||||
|
@ -59,6 +59,7 @@
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
enableGPUValidationMode = "1"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
|
@ -1,12 +1,14 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:developer';
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:surface/database/database.dart';
|
||||
import 'package:surface/providers/database.dart';
|
||||
import 'package:surface/providers/sn_attachment.dart';
|
||||
import 'package:surface/providers/sn_network.dart';
|
||||
import 'package:surface/providers/user_directory.dart';
|
||||
@ -16,13 +18,13 @@ import 'package:surface/types/websocket.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
class ChatMessageController extends ChangeNotifier {
|
||||
static const kChatMessageBoxPrefix = 'nex_chat_messages_';
|
||||
static const kSingleBatchLoadLimit = 100;
|
||||
|
||||
late final SnNetworkProvider _sn;
|
||||
late final UserDirectoryProvider _ud;
|
||||
late final WebSocketProvider _ws;
|
||||
late final SnAttachmentProvider _attach;
|
||||
late final DatabaseProvider _dt;
|
||||
|
||||
StreamSubscription? _wsSubscription;
|
||||
|
||||
@ -31,6 +33,7 @@ class ChatMessageController extends ChangeNotifier {
|
||||
_ud = context.read<UserDirectoryProvider>();
|
||||
_ws = context.read<WebSocketProvider>();
|
||||
_attach = context.read<SnAttachmentProvider>();
|
||||
_dt = context.read<DatabaseProvider>();
|
||||
}
|
||||
|
||||
bool isPending = true;
|
||||
@ -38,9 +41,9 @@ class ChatMessageController extends ChangeNotifier {
|
||||
|
||||
int? messageTotal;
|
||||
|
||||
bool get isAllLoaded => messageTotal != null && messages.length >= messageTotal!;
|
||||
bool get isAllLoaded =>
|
||||
messageTotal != null && messages.length >= messageTotal!;
|
||||
|
||||
String? _boxKey;
|
||||
SnChannel? channel;
|
||||
SnChannelMember? profile;
|
||||
|
||||
@ -51,25 +54,17 @@ class ChatMessageController extends ChangeNotifier {
|
||||
/// Stored as a list of nonce to provide the loading state
|
||||
final List<String> unconfirmedMessages = List.empty(growable: true);
|
||||
|
||||
Box<SnChatMessage>? get _box => (_boxKey == null || isPending) ? null : Hive.box<SnChatMessage>(_boxKey!);
|
||||
|
||||
final List<SnChannelMember> typingMembers = List.empty(growable: true);
|
||||
final Map<int, Timer> typingInactiveTimer = {};
|
||||
|
||||
Future<void> initialize(SnChannel chan) async {
|
||||
channel = chan;
|
||||
|
||||
// Initialize local data
|
||||
_boxKey = '$kChatMessageBoxPrefix${chan.id}';
|
||||
await Hive.openBox<SnChatMessage>(_boxKey!);
|
||||
|
||||
// Fetch channel profile
|
||||
final resp = await _sn.client.get(
|
||||
'/cgi/im/channels/${chan.keyPath}/me',
|
||||
);
|
||||
profile = SnChannelMember.fromJson(
|
||||
resp.data as Map<String, dynamic>,
|
||||
);
|
||||
profile = SnChannelMember.fromJson(resp.data);
|
||||
|
||||
_wsSubscription = _ws.pk.stream.listen((event) {
|
||||
switch (event.method) {
|
||||
@ -87,7 +82,8 @@ class ChatMessageController extends ChangeNotifier {
|
||||
notifyListeners();
|
||||
}
|
||||
typingInactiveTimer[member.id]?.cancel();
|
||||
typingInactiveTimer[member.id] = Timer(const Duration(seconds: 3), () {
|
||||
typingInactiveTimer[member.id] =
|
||||
Timer(const Duration(seconds: 3), () {
|
||||
typingMembers.removeWhere((x) => x.id == member.id);
|
||||
typingInactiveTimer.remove(member.id);
|
||||
notifyListeners();
|
||||
@ -129,10 +125,16 @@ class ChatMessageController extends ChangeNotifier {
|
||||
}
|
||||
|
||||
Future<void> _saveMessageToLocal(Iterable<SnChatMessage> messages) async {
|
||||
if (_box == null) return;
|
||||
await _box!.putAll({
|
||||
for (final message in messages) message.id: message,
|
||||
});
|
||||
await _dt.db.snLocalChatMessage.insertAll(
|
||||
messages.map(
|
||||
(ele) => SnLocalChatMessageCompanion.insert(
|
||||
id: Value(ele.id),
|
||||
content: ele,
|
||||
channelId: channel!.id,
|
||||
createdAt: Value(ele.createdAt),
|
||||
),
|
||||
),
|
||||
onConflict: DoNothing());
|
||||
}
|
||||
|
||||
Future<void> _addUnconfirmedMessage(SnChatMessage message) async {
|
||||
@ -184,8 +186,21 @@ class ChatMessageController extends ChangeNotifier {
|
||||
await _applyMessage(message);
|
||||
notifyListeners();
|
||||
|
||||
if (_box == null) return;
|
||||
await _box!.put(message.id, message);
|
||||
if (isCheckedUpdate) {
|
||||
await _dt.db.snLocalChatMessage.insertOne(
|
||||
SnLocalChatMessageCompanion.insert(
|
||||
id: Value(message.id),
|
||||
content: message,
|
||||
channelId: channel!.id,
|
||||
createdAt: Value(message.createdAt),
|
||||
),
|
||||
onConflict: DoUpdate((_) => SnLocalChatMessageCompanion.custom(
|
||||
content: Constant(jsonEncode(message.toJson())),
|
||||
)),
|
||||
);
|
||||
} else {
|
||||
incomeStrandedQueue.add(message);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _applyMessage(SnChatMessage message) async {
|
||||
@ -194,7 +209,8 @@ class ChatMessageController extends ChangeNotifier {
|
||||
switch (message.type) {
|
||||
case 'messages.edit':
|
||||
if (message.relatedEventId != null) {
|
||||
final idx = messages.indexWhere((x) => x.id == message.relatedEventId);
|
||||
final idx =
|
||||
messages.indexWhere((x) => x.id == message.relatedEventId);
|
||||
if (idx != -1) {
|
||||
final newBody = message.body;
|
||||
newBody.remove('related_event');
|
||||
@ -202,16 +218,24 @@ class ChatMessageController extends ChangeNotifier {
|
||||
body: newBody,
|
||||
updatedAt: message.updatedAt,
|
||||
);
|
||||
if (_box!.containsKey(message.relatedEventId)) {
|
||||
await _box!.put(message.relatedEventId, messages[idx]);
|
||||
if (message.relatedEventId != null) {
|
||||
await (_dt.db.snLocalChatMessage.update()
|
||||
..where((e) => e.id.equals(message.relatedEventId!)))
|
||||
.write(
|
||||
SnLocalChatMessageCompanion.custom(
|
||||
content: Constant(jsonEncode(messages[idx].toJson())),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
case 'messages.delete':
|
||||
if (message.relatedEventId != null) {
|
||||
messages.removeWhere((x) => x.id == message.relatedEventId);
|
||||
if (_box!.containsKey(message.relatedEventId)) {
|
||||
await _box!.delete(message.relatedEventId);
|
||||
if (message.relatedEventId != null) {
|
||||
await (_dt.db.snLocalChatMessage.delete()
|
||||
..where((e) => e.id.equals(message.relatedEventId!)))
|
||||
.go();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -233,7 +257,8 @@ class ChatMessageController extends ChangeNotifier {
|
||||
'algorithm': 'plain',
|
||||
if (quoteId != null) 'quote_event': quoteId,
|
||||
if (relatedId != null) 'related_event': relatedId,
|
||||
if (attachments != null && attachments.isNotEmpty) 'attachments': attachments,
|
||||
if (attachments != null && attachments.isNotEmpty)
|
||||
'attachments': attachments,
|
||||
};
|
||||
|
||||
// Mock the message locally
|
||||
@ -287,20 +312,34 @@ class ChatMessageController extends ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
bool isCheckedUpdate = false;
|
||||
List<SnChatMessage> incomeStrandedQueue = List.empty(growable: true);
|
||||
|
||||
/// Check the local storage is up to date with the server.
|
||||
/// If the local storage is not up to date, it will be updated.
|
||||
Future<void> checkUpdate() async {
|
||||
if (_box == null) return;
|
||||
if (_box!.isEmpty) return;
|
||||
|
||||
isLoading = true;
|
||||
notifyListeners();
|
||||
|
||||
final mostRecentMessage = await (_dt.db.snLocalChatMessage.select()
|
||||
..limit(1)
|
||||
..orderBy([
|
||||
(e) =>
|
||||
OrderingTerm(expression: e.createdAt, mode: OrderingMode.desc)
|
||||
]))
|
||||
.getSingleOrNull();
|
||||
if (mostRecentMessage == null) {
|
||||
// Initial load
|
||||
await loadMessages(take: 20);
|
||||
isCheckedUpdate = true;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
final resp = await _sn.client.get(
|
||||
'/cgi/im/channels/${channel!.keyPath}/events/update',
|
||||
queryParameters: {
|
||||
'pivot': _box!.values.last.id,
|
||||
'pivot': mostRecentMessage.content.id,
|
||||
},
|
||||
);
|
||||
if (resp.data['up_to_date'] == true) return;
|
||||
@ -316,6 +355,12 @@ class ChatMessageController extends ChangeNotifier {
|
||||
} finally {
|
||||
await loadMessages();
|
||||
isLoading = false;
|
||||
|
||||
isCheckedUpdate = true;
|
||||
_saveMessageToLocal(incomeStrandedQueue).then((_) {
|
||||
incomeStrandedQueue.clear();
|
||||
});
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
@ -324,13 +369,18 @@ class ChatMessageController extends ChangeNotifier {
|
||||
/// If it was not found in local storage we will look it up in remote
|
||||
Future<SnChatMessage?> getMessage(int id) async {
|
||||
SnChatMessage? out;
|
||||
if (_box != null && _box!.containsKey(id)) {
|
||||
out = _box!.get(id);
|
||||
final local = await (_dt.db.snLocalChatMessage.select()
|
||||
..limit(1)
|
||||
..where((e) => e.id.equals(id)))
|
||||
.getSingleOrNull();
|
||||
if (local != null) {
|
||||
out = local.content;
|
||||
}
|
||||
|
||||
if (out == null) {
|
||||
try {
|
||||
final resp = await _sn.client.get('/cgi/im/channels/${channel!.keyPath}/events/$id');
|
||||
final resp = await _sn.client
|
||||
.get('/cgi/im/channels/${channel!.keyPath}/events/$id');
|
||||
out = SnChatMessage.fromJson(resp.data);
|
||||
_saveMessageToLocal([out]);
|
||||
} catch (_) {
|
||||
@ -364,16 +414,21 @@ class ChatMessageController extends ChangeNotifier {
|
||||
bool forceLocal = false,
|
||||
bool forceRemote = false,
|
||||
}) async {
|
||||
final localTotal = await _dt.db.snLocalChatMessage
|
||||
.count(where: (e) => e.channelId.equals(channel!.id))
|
||||
.getSingle();
|
||||
|
||||
late List<SnChatMessage> out;
|
||||
if (_box != null && (_box!.length >= take + offset || forceLocal) && !forceRemote) {
|
||||
out = _box!.keys
|
||||
.toList()
|
||||
.cast<int>()
|
||||
.sorted((a, b) => b.compareTo(a))
|
||||
.skip(offset)
|
||||
.take(take)
|
||||
.map((key) => _box!.get(key)!)
|
||||
.toList();
|
||||
if ((localTotal >= take + offset || forceLocal) && !forceRemote) {
|
||||
final result = await (_dt.db.snLocalChatMessage.select()
|
||||
..where((e) => e.channelId.equals(channel!.id))
|
||||
..orderBy([
|
||||
(e) =>
|
||||
OrderingTerm(expression: e.createdAt, mode: OrderingMode.desc)
|
||||
])
|
||||
..limit(take, offset: offset))
|
||||
.get();
|
||||
out = result.map((e) => e.content).toList();
|
||||
} else {
|
||||
final resp = await _sn.client.get(
|
||||
'/cgi/im/channels/${channel!.keyPath}/events',
|
||||
@ -408,7 +463,8 @@ class ChatMessageController extends ChangeNotifier {
|
||||
quoteEvent: quoteEvent,
|
||||
attachments: attachments
|
||||
.where(
|
||||
(ele) => out[i].body['attachments']?.contains(ele?.rid) ?? false,
|
||||
(ele) =>
|
||||
out[i].body['attachments']?.contains(ele?.rid) ?? false,
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
@ -416,7 +472,10 @@ class ChatMessageController extends ChangeNotifier {
|
||||
}
|
||||
|
||||
// Preload sender accounts
|
||||
final accountId = out.where((ele) => ele.sender.accountId >= 0).map((ele) => ele.sender.accountId).toSet();
|
||||
final accountId = out
|
||||
.where((ele) => ele.sender.accountId >= 0)
|
||||
.map((ele) => ele.sender.accountId)
|
||||
.toSet();
|
||||
await _ud.listAccount(accountId);
|
||||
|
||||
return out;
|
||||
@ -441,10 +500,45 @@ class ChatMessageController extends ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
Timer? _readEventDebounce;
|
||||
int? _readEventAnchor;
|
||||
|
||||
void readEvent(int id) {
|
||||
if (_readEventAnchor != null) {
|
||||
_readEventAnchor = math.max(_readEventAnchor!, id);
|
||||
} else {
|
||||
_readEventAnchor = id;
|
||||
}
|
||||
if (_readEventDebounce?.isActive ?? false) {
|
||||
_readEventDebounce?.cancel();
|
||||
}
|
||||
|
||||
_readEventDebounce = Timer(const Duration(milliseconds: 500), () {
|
||||
_sendReadEvent();
|
||||
});
|
||||
}
|
||||
|
||||
void _sendReadEvent() {
|
||||
_ws.conn?.sink.add(jsonEncode(
|
||||
WebSocketPackage(
|
||||
method: 'events.read',
|
||||
endpoint: 'im',
|
||||
payload: {
|
||||
'channel_member_id': profile!.id,
|
||||
'event_id': _readEventAnchor,
|
||||
},
|
||||
).toJson(),
|
||||
));
|
||||
log('[Messaging] Send read event request: $_readEventAnchor');
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_box?.close();
|
||||
_wsSubscription?.cancel();
|
||||
if (_readEventDebounce?.isActive ?? false) {
|
||||
_sendReadEvent();
|
||||
}
|
||||
_readEventDebounce?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ import 'package:surface/providers/sn_network.dart';
|
||||
import 'package:surface/types/attachment.dart';
|
||||
import 'package:surface/types/poll.dart';
|
||||
import 'package:surface/types/post.dart';
|
||||
import 'package:surface/types/realm.dart';
|
||||
import 'package:surface/widgets/dialog.dart';
|
||||
import 'package:surface/widgets/universal_image.dart';
|
||||
import 'package:video_compress/video_compress.dart';
|
||||
@ -159,12 +160,13 @@ class PostWriteController extends ChangeNotifier {
|
||||
final TextEditingController rewardController = TextEditingController();
|
||||
|
||||
ContentInsertionConfiguration get contentInsertionConfiguration => ContentInsertionConfiguration(
|
||||
onContentInserted: (KeyboardInsertedContent content) {
|
||||
if (content.hasData) {
|
||||
addAttachments([PostWriteMedia.fromBytes(content.data!, 'attachmentInsertedImage'.tr(), SnMediaType.image)]);
|
||||
}
|
||||
},
|
||||
);
|
||||
onContentInserted: (KeyboardInsertedContent content) {
|
||||
if (content.hasData) {
|
||||
addAttachments(
|
||||
[PostWriteMedia.fromBytes(content.data!, 'attachmentInsertedImage'.tr(), SnMediaType.image)]);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
bool _temporarySaveActive = false;
|
||||
|
||||
@ -196,6 +198,7 @@ class PostWriteController extends ChangeNotifier {
|
||||
bool isLoading = false, isBusy = false;
|
||||
double? progress;
|
||||
|
||||
SnRealm? realm;
|
||||
SnPublisher? publisher;
|
||||
SnPost? editingPost, repostingPost, replyingPost;
|
||||
|
||||
@ -244,6 +247,9 @@ class PostWriteController extends ChangeNotifier {
|
||||
if (post.preload?.thumbnail != null && (post.preload?.thumbnail?.rid.isNotEmpty ?? false)) {
|
||||
thumbnail = PostWriteMedia(post.preload!.thumbnail);
|
||||
}
|
||||
if (post.preload?.realm != null) {
|
||||
realm = post.preload!.realm!;
|
||||
}
|
||||
|
||||
editingPost = post;
|
||||
}
|
||||
@ -379,6 +385,7 @@ class PostWriteController extends ChangeNotifier {
|
||||
if (replyingPost != null) 'reply_to': replyingPost!.toJson(),
|
||||
if (repostingPost != null) 'repost_to': repostingPost!.toJson(),
|
||||
if (poll != null) 'poll': poll!.toJson(),
|
||||
if (realm != null) 'realm': realm!.toJson(),
|
||||
}),
|
||||
);
|
||||
});
|
||||
@ -409,6 +416,7 @@ class PostWriteController extends ChangeNotifier {
|
||||
replyingPost = data['reply_to'] != null ? SnPost.fromJson(data['reply_to']) : null;
|
||||
repostingPost = data['repost_to'] != null ? SnPost.fromJson(data['repost_to']) : null;
|
||||
poll = data['poll'] != null ? SnPoll.fromJson(data['poll']) : null;
|
||||
realm = data['realm'] != null ? SnRealm.fromJson(data['realm']) : null;
|
||||
temporaryRestored = true;
|
||||
notifyListeners();
|
||||
});
|
||||
@ -525,6 +533,7 @@ class PostWriteController extends ChangeNotifier {
|
||||
if (reward != null) 'reward': reward,
|
||||
if (videoAttachment != null) 'video': videoAttachment!.rid,
|
||||
if (poll != null) 'poll': poll!.id,
|
||||
if (realm != null) 'realm': realm!.id,
|
||||
},
|
||||
onSendProgress: (count, total) {
|
||||
progress = baseProgressVal + (count / total) * (kPostingProgressWeight / 2);
|
||||
@ -571,17 +580,8 @@ class PostWriteController extends ChangeNotifier {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void setThumbnail(int? idx) {
|
||||
if (idx == null) {
|
||||
attachments.add(thumbnail!);
|
||||
thumbnail = null;
|
||||
} else {
|
||||
if (thumbnail != null) {
|
||||
attachments.add(thumbnail!);
|
||||
}
|
||||
thumbnail = attachments[idx];
|
||||
attachments.removeAt(idx);
|
||||
}
|
||||
void setThumbnail(SnAttachment? value) {
|
||||
thumbnail = value == null ? null : PostWriteMedia(value);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@ -633,6 +633,11 @@ class PostWriteController extends ChangeNotifier {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void setRealm(SnRealm? value) {
|
||||
realm = value;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void setProgress(double? value) {
|
||||
progress = value;
|
||||
_temporaryPlanSave();
|
||||
|
74
lib/database/chat.dart
Normal file
74
lib/database/chat.dart
Normal file
@ -0,0 +1,74 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:surface/types/chat.dart';
|
||||
|
||||
class SnChannelConverter extends TypeConverter<SnChannel, String>
|
||||
with JsonTypeConverter2<SnChannel, String, Map<String, Object?>> {
|
||||
const SnChannelConverter();
|
||||
|
||||
@override
|
||||
SnChannel fromSql(String fromDb) {
|
||||
return fromJson(jsonDecode(fromDb) as Map<String, dynamic>);
|
||||
}
|
||||
|
||||
@override
|
||||
String toSql(SnChannel value) {
|
||||
return jsonEncode(toJson(value));
|
||||
}
|
||||
|
||||
@override
|
||||
SnChannel fromJson(Map<String, Object?> json) {
|
||||
return SnChannel.fromJson(json);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, Object?> toJson(SnChannel value) {
|
||||
return value.toJson();
|
||||
}
|
||||
}
|
||||
|
||||
class SnLocalChatChannel extends Table {
|
||||
IntColumn get id => integer().autoIncrement()();
|
||||
|
||||
TextColumn get alias => text()();
|
||||
|
||||
TextColumn get content => text().map(const SnChannelConverter())();
|
||||
|
||||
DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
|
||||
}
|
||||
|
||||
class SnMessageConverter extends TypeConverter<SnChatMessage, String>
|
||||
with JsonTypeConverter2<SnChatMessage, String, Map<String, Object?>> {
|
||||
const SnMessageConverter();
|
||||
|
||||
@override
|
||||
SnChatMessage fromSql(String fromDb) {
|
||||
return fromJson(jsonDecode(fromDb) as Map<String, dynamic>);
|
||||
}
|
||||
|
||||
@override
|
||||
String toSql(SnChatMessage value) {
|
||||
return jsonEncode(toJson(value));
|
||||
}
|
||||
|
||||
@override
|
||||
SnChatMessage fromJson(Map<String, Object?> json) {
|
||||
return SnChatMessage.fromJson(json);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, Object?> toJson(SnChatMessage value) {
|
||||
return value.toJson();
|
||||
}
|
||||
}
|
||||
|
||||
class SnLocalChatMessage extends Table {
|
||||
IntColumn get id => integer().autoIncrement()();
|
||||
|
||||
IntColumn get channelId => integer()();
|
||||
|
||||
TextColumn get content => text().map(const SnMessageConverter())();
|
||||
|
||||
DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
|
||||
}
|
28
lib/database/database.dart
Normal file
28
lib/database/database.dart
Normal file
@ -0,0 +1,28 @@
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:drift_flutter/drift_flutter.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:surface/database/chat.dart';
|
||||
import 'package:surface/types/chat.dart';
|
||||
|
||||
part 'database.g.dart';
|
||||
|
||||
@DriftDatabase(tables: [SnLocalChatChannel, SnLocalChatMessage])
|
||||
class AppDatabase extends _$AppDatabase {
|
||||
AppDatabase() : super(_openConnection());
|
||||
|
||||
@override
|
||||
int get schemaVersion => 1;
|
||||
|
||||
static QueryExecutor _openConnection() {
|
||||
return driftDatabase(
|
||||
name: 'solar_network_data',
|
||||
native: const DriftNativeOptions(
|
||||
databaseDirectory: getApplicationSupportDirectory,
|
||||
),
|
||||
web: DriftWebOptions(
|
||||
sqlite3Wasm: Uri.parse('sqlite3.wasm'),
|
||||
driftWorker: Uri.parse('drift_worker.dart.js'),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
880
lib/database/database.g.dart
Normal file
880
lib/database/database.g.dart
Normal file
@ -0,0 +1,880 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'database.dart';
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
class $SnLocalChatChannelTable extends SnLocalChatChannel
|
||||
with TableInfo<$SnLocalChatChannelTable, SnLocalChatChannelData> {
|
||||
@override
|
||||
final GeneratedDatabase attachedDatabase;
|
||||
final String? _alias;
|
||||
$SnLocalChatChannelTable(this.attachedDatabase, [this._alias]);
|
||||
static const VerificationMeta _idMeta = const VerificationMeta('id');
|
||||
@override
|
||||
late final GeneratedColumn<int> id = GeneratedColumn<int>(
|
||||
'id', aliasedName, false,
|
||||
hasAutoIncrement: true,
|
||||
type: DriftSqlType.int,
|
||||
requiredDuringInsert: false,
|
||||
defaultConstraints:
|
||||
GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT'));
|
||||
static const VerificationMeta _aliasMeta = const VerificationMeta('alias');
|
||||
@override
|
||||
late final GeneratedColumn<String> alias = GeneratedColumn<String>(
|
||||
'alias', aliasedName, false,
|
||||
type: DriftSqlType.string, requiredDuringInsert: true);
|
||||
static const VerificationMeta _contentMeta =
|
||||
const VerificationMeta('content');
|
||||
@override
|
||||
late final GeneratedColumnWithTypeConverter<SnChannel, String> content =
|
||||
GeneratedColumn<String>('content', aliasedName, false,
|
||||
type: DriftSqlType.string, requiredDuringInsert: true)
|
||||
.withConverter<SnChannel>($SnLocalChatChannelTable.$convertercontent);
|
||||
static const VerificationMeta _createdAtMeta =
|
||||
const VerificationMeta('createdAt');
|
||||
@override
|
||||
late final GeneratedColumn<DateTime> createdAt = GeneratedColumn<DateTime>(
|
||||
'created_at', aliasedName, false,
|
||||
type: DriftSqlType.dateTime,
|
||||
requiredDuringInsert: false,
|
||||
defaultValue: currentDateAndTime);
|
||||
@override
|
||||
List<GeneratedColumn> get $columns => [id, alias, content, createdAt];
|
||||
@override
|
||||
String get aliasedName => _alias ?? actualTableName;
|
||||
@override
|
||||
String get actualTableName => $name;
|
||||
static const String $name = 'sn_local_chat_channel';
|
||||
@override
|
||||
VerificationContext validateIntegrity(
|
||||
Insertable<SnLocalChatChannelData> instance,
|
||||
{bool isInserting = false}) {
|
||||
final context = VerificationContext();
|
||||
final data = instance.toColumns(true);
|
||||
if (data.containsKey('id')) {
|
||||
context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta));
|
||||
}
|
||||
if (data.containsKey('alias')) {
|
||||
context.handle(
|
||||
_aliasMeta, alias.isAcceptableOrUnknown(data['alias']!, _aliasMeta));
|
||||
} else if (isInserting) {
|
||||
context.missing(_aliasMeta);
|
||||
}
|
||||
context.handle(_contentMeta, const VerificationResult.success());
|
||||
if (data.containsKey('created_at')) {
|
||||
context.handle(_createdAtMeta,
|
||||
createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta));
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
@override
|
||||
Set<GeneratedColumn> get $primaryKey => {id};
|
||||
@override
|
||||
SnLocalChatChannelData map(Map<String, dynamic> data, {String? tablePrefix}) {
|
||||
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
|
||||
return SnLocalChatChannelData(
|
||||
id: attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.int, data['${effectivePrefix}id'])!,
|
||||
alias: attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.string, data['${effectivePrefix}alias'])!,
|
||||
content: $SnLocalChatChannelTable.$convertercontent.fromSql(
|
||||
attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.string, data['${effectivePrefix}content'])!),
|
||||
createdAt: attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
$SnLocalChatChannelTable createAlias(String alias) {
|
||||
return $SnLocalChatChannelTable(attachedDatabase, alias);
|
||||
}
|
||||
|
||||
static JsonTypeConverter2<SnChannel, String, Map<String, Object?>>
|
||||
$convertercontent = const SnChannelConverter();
|
||||
}
|
||||
|
||||
class SnLocalChatChannelData extends DataClass
|
||||
implements Insertable<SnLocalChatChannelData> {
|
||||
final int id;
|
||||
final String alias;
|
||||
final SnChannel content;
|
||||
final DateTime createdAt;
|
||||
const SnLocalChatChannelData(
|
||||
{required this.id,
|
||||
required this.alias,
|
||||
required this.content,
|
||||
required this.createdAt});
|
||||
@override
|
||||
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||
final map = <String, Expression>{};
|
||||
map['id'] = Variable<int>(id);
|
||||
map['alias'] = Variable<String>(alias);
|
||||
{
|
||||
map['content'] = Variable<String>(
|
||||
$SnLocalChatChannelTable.$convertercontent.toSql(content));
|
||||
}
|
||||
map['created_at'] = Variable<DateTime>(createdAt);
|
||||
return map;
|
||||
}
|
||||
|
||||
SnLocalChatChannelCompanion toCompanion(bool nullToAbsent) {
|
||||
return SnLocalChatChannelCompanion(
|
||||
id: Value(id),
|
||||
alias: Value(alias),
|
||||
content: Value(content),
|
||||
createdAt: Value(createdAt),
|
||||
);
|
||||
}
|
||||
|
||||
factory SnLocalChatChannelData.fromJson(Map<String, dynamic> json,
|
||||
{ValueSerializer? serializer}) {
|
||||
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||
return SnLocalChatChannelData(
|
||||
id: serializer.fromJson<int>(json['id']),
|
||||
alias: serializer.fromJson<String>(json['alias']),
|
||||
content: $SnLocalChatChannelTable.$convertercontent
|
||||
.fromJson(serializer.fromJson<Map<String, Object?>>(json['content'])),
|
||||
createdAt: serializer.fromJson<DateTime>(json['createdAt']),
|
||||
);
|
||||
}
|
||||
@override
|
||||
Map<String, dynamic> toJson({ValueSerializer? serializer}) {
|
||||
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||
return <String, dynamic>{
|
||||
'id': serializer.toJson<int>(id),
|
||||
'alias': serializer.toJson<String>(alias),
|
||||
'content': serializer.toJson<Map<String, Object?>>(
|
||||
$SnLocalChatChannelTable.$convertercontent.toJson(content)),
|
||||
'createdAt': serializer.toJson<DateTime>(createdAt),
|
||||
};
|
||||
}
|
||||
|
||||
SnLocalChatChannelData copyWith(
|
||||
{int? id, String? alias, SnChannel? content, DateTime? createdAt}) =>
|
||||
SnLocalChatChannelData(
|
||||
id: id ?? this.id,
|
||||
alias: alias ?? this.alias,
|
||||
content: content ?? this.content,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
);
|
||||
SnLocalChatChannelData copyWithCompanion(SnLocalChatChannelCompanion data) {
|
||||
return SnLocalChatChannelData(
|
||||
id: data.id.present ? data.id.value : this.id,
|
||||
alias: data.alias.present ? data.alias.value : this.alias,
|
||||
content: data.content.present ? data.content.value : this.content,
|
||||
createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return (StringBuffer('SnLocalChatChannelData(')
|
||||
..write('id: $id, ')
|
||||
..write('alias: $alias, ')
|
||||
..write('content: $content, ')
|
||||
..write('createdAt: $createdAt')
|
||||
..write(')'))
|
||||
.toString();
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(id, alias, content, createdAt);
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
(other is SnLocalChatChannelData &&
|
||||
other.id == this.id &&
|
||||
other.alias == this.alias &&
|
||||
other.content == this.content &&
|
||||
other.createdAt == this.createdAt);
|
||||
}
|
||||
|
||||
class SnLocalChatChannelCompanion
|
||||
extends UpdateCompanion<SnLocalChatChannelData> {
|
||||
final Value<int> id;
|
||||
final Value<String> alias;
|
||||
final Value<SnChannel> content;
|
||||
final Value<DateTime> createdAt;
|
||||
const SnLocalChatChannelCompanion({
|
||||
this.id = const Value.absent(),
|
||||
this.alias = const Value.absent(),
|
||||
this.content = const Value.absent(),
|
||||
this.createdAt = const Value.absent(),
|
||||
});
|
||||
SnLocalChatChannelCompanion.insert({
|
||||
this.id = const Value.absent(),
|
||||
required String alias,
|
||||
required SnChannel content,
|
||||
this.createdAt = const Value.absent(),
|
||||
}) : alias = Value(alias),
|
||||
content = Value(content);
|
||||
static Insertable<SnLocalChatChannelData> custom({
|
||||
Expression<int>? id,
|
||||
Expression<String>? alias,
|
||||
Expression<String>? content,
|
||||
Expression<DateTime>? createdAt,
|
||||
}) {
|
||||
return RawValuesInsertable({
|
||||
if (id != null) 'id': id,
|
||||
if (alias != null) 'alias': alias,
|
||||
if (content != null) 'content': content,
|
||||
if (createdAt != null) 'created_at': createdAt,
|
||||
});
|
||||
}
|
||||
|
||||
SnLocalChatChannelCompanion copyWith(
|
||||
{Value<int>? id,
|
||||
Value<String>? alias,
|
||||
Value<SnChannel>? content,
|
||||
Value<DateTime>? createdAt}) {
|
||||
return SnLocalChatChannelCompanion(
|
||||
id: id ?? this.id,
|
||||
alias: alias ?? this.alias,
|
||||
content: content ?? this.content,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||
final map = <String, Expression>{};
|
||||
if (id.present) {
|
||||
map['id'] = Variable<int>(id.value);
|
||||
}
|
||||
if (alias.present) {
|
||||
map['alias'] = Variable<String>(alias.value);
|
||||
}
|
||||
if (content.present) {
|
||||
map['content'] = Variable<String>(
|
||||
$SnLocalChatChannelTable.$convertercontent.toSql(content.value));
|
||||
}
|
||||
if (createdAt.present) {
|
||||
map['created_at'] = Variable<DateTime>(createdAt.value);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return (StringBuffer('SnLocalChatChannelCompanion(')
|
||||
..write('id: $id, ')
|
||||
..write('alias: $alias, ')
|
||||
..write('content: $content, ')
|
||||
..write('createdAt: $createdAt')
|
||||
..write(')'))
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
||||
class $SnLocalChatMessageTable extends SnLocalChatMessage
|
||||
with TableInfo<$SnLocalChatMessageTable, SnLocalChatMessageData> {
|
||||
@override
|
||||
final GeneratedDatabase attachedDatabase;
|
||||
final String? _alias;
|
||||
$SnLocalChatMessageTable(this.attachedDatabase, [this._alias]);
|
||||
static const VerificationMeta _idMeta = const VerificationMeta('id');
|
||||
@override
|
||||
late final GeneratedColumn<int> id = GeneratedColumn<int>(
|
||||
'id', aliasedName, false,
|
||||
hasAutoIncrement: true,
|
||||
type: DriftSqlType.int,
|
||||
requiredDuringInsert: false,
|
||||
defaultConstraints:
|
||||
GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT'));
|
||||
static const VerificationMeta _channelIdMeta =
|
||||
const VerificationMeta('channelId');
|
||||
@override
|
||||
late final GeneratedColumn<int> channelId = GeneratedColumn<int>(
|
||||
'channel_id', aliasedName, false,
|
||||
type: DriftSqlType.int, requiredDuringInsert: true);
|
||||
static const VerificationMeta _contentMeta =
|
||||
const VerificationMeta('content');
|
||||
@override
|
||||
late final GeneratedColumnWithTypeConverter<SnChatMessage, String> content =
|
||||
GeneratedColumn<String>('content', aliasedName, false,
|
||||
type: DriftSqlType.string, requiredDuringInsert: true)
|
||||
.withConverter<SnChatMessage>(
|
||||
$SnLocalChatMessageTable.$convertercontent);
|
||||
static const VerificationMeta _createdAtMeta =
|
||||
const VerificationMeta('createdAt');
|
||||
@override
|
||||
late final GeneratedColumn<DateTime> createdAt = GeneratedColumn<DateTime>(
|
||||
'created_at', aliasedName, false,
|
||||
type: DriftSqlType.dateTime,
|
||||
requiredDuringInsert: false,
|
||||
defaultValue: currentDateAndTime);
|
||||
@override
|
||||
List<GeneratedColumn> get $columns => [id, channelId, content, createdAt];
|
||||
@override
|
||||
String get aliasedName => _alias ?? actualTableName;
|
||||
@override
|
||||
String get actualTableName => $name;
|
||||
static const String $name = 'sn_local_chat_message';
|
||||
@override
|
||||
VerificationContext validateIntegrity(
|
||||
Insertable<SnLocalChatMessageData> instance,
|
||||
{bool isInserting = false}) {
|
||||
final context = VerificationContext();
|
||||
final data = instance.toColumns(true);
|
||||
if (data.containsKey('id')) {
|
||||
context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta));
|
||||
}
|
||||
if (data.containsKey('channel_id')) {
|
||||
context.handle(_channelIdMeta,
|
||||
channelId.isAcceptableOrUnknown(data['channel_id']!, _channelIdMeta));
|
||||
} else if (isInserting) {
|
||||
context.missing(_channelIdMeta);
|
||||
}
|
||||
context.handle(_contentMeta, const VerificationResult.success());
|
||||
if (data.containsKey('created_at')) {
|
||||
context.handle(_createdAtMeta,
|
||||
createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta));
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
@override
|
||||
Set<GeneratedColumn> get $primaryKey => {id};
|
||||
@override
|
||||
SnLocalChatMessageData map(Map<String, dynamic> data, {String? tablePrefix}) {
|
||||
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
|
||||
return SnLocalChatMessageData(
|
||||
id: attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.int, data['${effectivePrefix}id'])!,
|
||||
channelId: attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.int, data['${effectivePrefix}channel_id'])!,
|
||||
content: $SnLocalChatMessageTable.$convertercontent.fromSql(
|
||||
attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.string, data['${effectivePrefix}content'])!),
|
||||
createdAt: attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
$SnLocalChatMessageTable createAlias(String alias) {
|
||||
return $SnLocalChatMessageTable(attachedDatabase, alias);
|
||||
}
|
||||
|
||||
static JsonTypeConverter2<SnChatMessage, String, Map<String, Object?>>
|
||||
$convertercontent = const SnMessageConverter();
|
||||
}
|
||||
|
||||
class SnLocalChatMessageData extends DataClass
|
||||
implements Insertable<SnLocalChatMessageData> {
|
||||
final int id;
|
||||
final int channelId;
|
||||
final SnChatMessage content;
|
||||
final DateTime createdAt;
|
||||
const SnLocalChatMessageData(
|
||||
{required this.id,
|
||||
required this.channelId,
|
||||
required this.content,
|
||||
required this.createdAt});
|
||||
@override
|
||||
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||
final map = <String, Expression>{};
|
||||
map['id'] = Variable<int>(id);
|
||||
map['channel_id'] = Variable<int>(channelId);
|
||||
{
|
||||
map['content'] = Variable<String>(
|
||||
$SnLocalChatMessageTable.$convertercontent.toSql(content));
|
||||
}
|
||||
map['created_at'] = Variable<DateTime>(createdAt);
|
||||
return map;
|
||||
}
|
||||
|
||||
SnLocalChatMessageCompanion toCompanion(bool nullToAbsent) {
|
||||
return SnLocalChatMessageCompanion(
|
||||
id: Value(id),
|
||||
channelId: Value(channelId),
|
||||
content: Value(content),
|
||||
createdAt: Value(createdAt),
|
||||
);
|
||||
}
|
||||
|
||||
factory SnLocalChatMessageData.fromJson(Map<String, dynamic> json,
|
||||
{ValueSerializer? serializer}) {
|
||||
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||
return SnLocalChatMessageData(
|
||||
id: serializer.fromJson<int>(json['id']),
|
||||
channelId: serializer.fromJson<int>(json['channelId']),
|
||||
content: $SnLocalChatMessageTable.$convertercontent
|
||||
.fromJson(serializer.fromJson<Map<String, Object?>>(json['content'])),
|
||||
createdAt: serializer.fromJson<DateTime>(json['createdAt']),
|
||||
);
|
||||
}
|
||||
@override
|
||||
Map<String, dynamic> toJson({ValueSerializer? serializer}) {
|
||||
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||
return <String, dynamic>{
|
||||
'id': serializer.toJson<int>(id),
|
||||
'channelId': serializer.toJson<int>(channelId),
|
||||
'content': serializer.toJson<Map<String, Object?>>(
|
||||
$SnLocalChatMessageTable.$convertercontent.toJson(content)),
|
||||
'createdAt': serializer.toJson<DateTime>(createdAt),
|
||||
};
|
||||
}
|
||||
|
||||
SnLocalChatMessageData copyWith(
|
||||
{int? id,
|
||||
int? channelId,
|
||||
SnChatMessage? content,
|
||||
DateTime? createdAt}) =>
|
||||
SnLocalChatMessageData(
|
||||
id: id ?? this.id,
|
||||
channelId: channelId ?? this.channelId,
|
||||
content: content ?? this.content,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
);
|
||||
SnLocalChatMessageData copyWithCompanion(SnLocalChatMessageCompanion data) {
|
||||
return SnLocalChatMessageData(
|
||||
id: data.id.present ? data.id.value : this.id,
|
||||
channelId: data.channelId.present ? data.channelId.value : this.channelId,
|
||||
content: data.content.present ? data.content.value : this.content,
|
||||
createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return (StringBuffer('SnLocalChatMessageData(')
|
||||
..write('id: $id, ')
|
||||
..write('channelId: $channelId, ')
|
||||
..write('content: $content, ')
|
||||
..write('createdAt: $createdAt')
|
||||
..write(')'))
|
||||
.toString();
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(id, channelId, content, createdAt);
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
(other is SnLocalChatMessageData &&
|
||||
other.id == this.id &&
|
||||
other.channelId == this.channelId &&
|
||||
other.content == this.content &&
|
||||
other.createdAt == this.createdAt);
|
||||
}
|
||||
|
||||
class SnLocalChatMessageCompanion
|
||||
extends UpdateCompanion<SnLocalChatMessageData> {
|
||||
final Value<int> id;
|
||||
final Value<int> channelId;
|
||||
final Value<SnChatMessage> content;
|
||||
final Value<DateTime> createdAt;
|
||||
const SnLocalChatMessageCompanion({
|
||||
this.id = const Value.absent(),
|
||||
this.channelId = const Value.absent(),
|
||||
this.content = const Value.absent(),
|
||||
this.createdAt = const Value.absent(),
|
||||
});
|
||||
SnLocalChatMessageCompanion.insert({
|
||||
this.id = const Value.absent(),
|
||||
required int channelId,
|
||||
required SnChatMessage content,
|
||||
this.createdAt = const Value.absent(),
|
||||
}) : channelId = Value(channelId),
|
||||
content = Value(content);
|
||||
static Insertable<SnLocalChatMessageData> custom({
|
||||
Expression<int>? id,
|
||||
Expression<int>? channelId,
|
||||
Expression<String>? content,
|
||||
Expression<DateTime>? createdAt,
|
||||
}) {
|
||||
return RawValuesInsertable({
|
||||
if (id != null) 'id': id,
|
||||
if (channelId != null) 'channel_id': channelId,
|
||||
if (content != null) 'content': content,
|
||||
if (createdAt != null) 'created_at': createdAt,
|
||||
});
|
||||
}
|
||||
|
||||
SnLocalChatMessageCompanion copyWith(
|
||||
{Value<int>? id,
|
||||
Value<int>? channelId,
|
||||
Value<SnChatMessage>? content,
|
||||
Value<DateTime>? createdAt}) {
|
||||
return SnLocalChatMessageCompanion(
|
||||
id: id ?? this.id,
|
||||
channelId: channelId ?? this.channelId,
|
||||
content: content ?? this.content,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||
final map = <String, Expression>{};
|
||||
if (id.present) {
|
||||
map['id'] = Variable<int>(id.value);
|
||||
}
|
||||
if (channelId.present) {
|
||||
map['channel_id'] = Variable<int>(channelId.value);
|
||||
}
|
||||
if (content.present) {
|
||||
map['content'] = Variable<String>(
|
||||
$SnLocalChatMessageTable.$convertercontent.toSql(content.value));
|
||||
}
|
||||
if (createdAt.present) {
|
||||
map['created_at'] = Variable<DateTime>(createdAt.value);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return (StringBuffer('SnLocalChatMessageCompanion(')
|
||||
..write('id: $id, ')
|
||||
..write('channelId: $channelId, ')
|
||||
..write('content: $content, ')
|
||||
..write('createdAt: $createdAt')
|
||||
..write(')'))
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _$AppDatabase extends GeneratedDatabase {
|
||||
_$AppDatabase(QueryExecutor e) : super(e);
|
||||
$AppDatabaseManager get managers => $AppDatabaseManager(this);
|
||||
late final $SnLocalChatChannelTable snLocalChatChannel =
|
||||
$SnLocalChatChannelTable(this);
|
||||
late final $SnLocalChatMessageTable snLocalChatMessage =
|
||||
$SnLocalChatMessageTable(this);
|
||||
@override
|
||||
Iterable<TableInfo<Table, Object?>> get allTables =>
|
||||
allSchemaEntities.whereType<TableInfo<Table, Object?>>();
|
||||
@override
|
||||
List<DatabaseSchemaEntity> get allSchemaEntities =>
|
||||
[snLocalChatChannel, snLocalChatMessage];
|
||||
}
|
||||
|
||||
typedef $$SnLocalChatChannelTableCreateCompanionBuilder
|
||||
= SnLocalChatChannelCompanion Function({
|
||||
Value<int> id,
|
||||
required String alias,
|
||||
required SnChannel content,
|
||||
Value<DateTime> createdAt,
|
||||
});
|
||||
typedef $$SnLocalChatChannelTableUpdateCompanionBuilder
|
||||
= SnLocalChatChannelCompanion Function({
|
||||
Value<int> id,
|
||||
Value<String> alias,
|
||||
Value<SnChannel> content,
|
||||
Value<DateTime> createdAt,
|
||||
});
|
||||
|
||||
class $$SnLocalChatChannelTableFilterComposer
|
||||
extends Composer<_$AppDatabase, $SnLocalChatChannelTable> {
|
||||
$$SnLocalChatChannelTableFilterComposer({
|
||||
required super.$db,
|
||||
required super.$table,
|
||||
super.joinBuilder,
|
||||
super.$addJoinBuilderToRootComposer,
|
||||
super.$removeJoinBuilderFromRootComposer,
|
||||
});
|
||||
ColumnFilters<int> get id => $composableBuilder(
|
||||
column: $table.id, builder: (column) => ColumnFilters(column));
|
||||
|
||||
ColumnFilters<String> get alias => $composableBuilder(
|
||||
column: $table.alias, builder: (column) => ColumnFilters(column));
|
||||
|
||||
ColumnWithTypeConverterFilters<SnChannel, SnChannel, String> get content =>
|
||||
$composableBuilder(
|
||||
column: $table.content,
|
||||
builder: (column) => ColumnWithTypeConverterFilters(column));
|
||||
|
||||
ColumnFilters<DateTime> get createdAt => $composableBuilder(
|
||||
column: $table.createdAt, builder: (column) => ColumnFilters(column));
|
||||
}
|
||||
|
||||
class $$SnLocalChatChannelTableOrderingComposer
|
||||
extends Composer<_$AppDatabase, $SnLocalChatChannelTable> {
|
||||
$$SnLocalChatChannelTableOrderingComposer({
|
||||
required super.$db,
|
||||
required super.$table,
|
||||
super.joinBuilder,
|
||||
super.$addJoinBuilderToRootComposer,
|
||||
super.$removeJoinBuilderFromRootComposer,
|
||||
});
|
||||
ColumnOrderings<int> get id => $composableBuilder(
|
||||
column: $table.id, builder: (column) => ColumnOrderings(column));
|
||||
|
||||
ColumnOrderings<String> get alias => $composableBuilder(
|
||||
column: $table.alias, builder: (column) => ColumnOrderings(column));
|
||||
|
||||
ColumnOrderings<String> get content => $composableBuilder(
|
||||
column: $table.content, builder: (column) => ColumnOrderings(column));
|
||||
|
||||
ColumnOrderings<DateTime> get createdAt => $composableBuilder(
|
||||
column: $table.createdAt, builder: (column) => ColumnOrderings(column));
|
||||
}
|
||||
|
||||
class $$SnLocalChatChannelTableAnnotationComposer
|
||||
extends Composer<_$AppDatabase, $SnLocalChatChannelTable> {
|
||||
$$SnLocalChatChannelTableAnnotationComposer({
|
||||
required super.$db,
|
||||
required super.$table,
|
||||
super.joinBuilder,
|
||||
super.$addJoinBuilderToRootComposer,
|
||||
super.$removeJoinBuilderFromRootComposer,
|
||||
});
|
||||
GeneratedColumn<int> get id =>
|
||||
$composableBuilder(column: $table.id, builder: (column) => column);
|
||||
|
||||
GeneratedColumn<String> get alias =>
|
||||
$composableBuilder(column: $table.alias, builder: (column) => column);
|
||||
|
||||
GeneratedColumnWithTypeConverter<SnChannel, String> get content =>
|
||||
$composableBuilder(column: $table.content, builder: (column) => column);
|
||||
|
||||
GeneratedColumn<DateTime> get createdAt =>
|
||||
$composableBuilder(column: $table.createdAt, builder: (column) => column);
|
||||
}
|
||||
|
||||
class $$SnLocalChatChannelTableTableManager extends RootTableManager<
|
||||
_$AppDatabase,
|
||||
$SnLocalChatChannelTable,
|
||||
SnLocalChatChannelData,
|
||||
$$SnLocalChatChannelTableFilterComposer,
|
||||
$$SnLocalChatChannelTableOrderingComposer,
|
||||
$$SnLocalChatChannelTableAnnotationComposer,
|
||||
$$SnLocalChatChannelTableCreateCompanionBuilder,
|
||||
$$SnLocalChatChannelTableUpdateCompanionBuilder,
|
||||
(
|
||||
SnLocalChatChannelData,
|
||||
BaseReferences<_$AppDatabase, $SnLocalChatChannelTable,
|
||||
SnLocalChatChannelData>
|
||||
),
|
||||
SnLocalChatChannelData,
|
||||
PrefetchHooks Function()> {
|
||||
$$SnLocalChatChannelTableTableManager(
|
||||
_$AppDatabase db, $SnLocalChatChannelTable table)
|
||||
: super(TableManagerState(
|
||||
db: db,
|
||||
table: table,
|
||||
createFilteringComposer: () =>
|
||||
$$SnLocalChatChannelTableFilterComposer($db: db, $table: table),
|
||||
createOrderingComposer: () =>
|
||||
$$SnLocalChatChannelTableOrderingComposer($db: db, $table: table),
|
||||
createComputedFieldComposer: () =>
|
||||
$$SnLocalChatChannelTableAnnotationComposer(
|
||||
$db: db, $table: table),
|
||||
updateCompanionCallback: ({
|
||||
Value<int> id = const Value.absent(),
|
||||
Value<String> alias = const Value.absent(),
|
||||
Value<SnChannel> content = const Value.absent(),
|
||||
Value<DateTime> createdAt = const Value.absent(),
|
||||
}) =>
|
||||
SnLocalChatChannelCompanion(
|
||||
id: id,
|
||||
alias: alias,
|
||||
content: content,
|
||||
createdAt: createdAt,
|
||||
),
|
||||
createCompanionCallback: ({
|
||||
Value<int> id = const Value.absent(),
|
||||
required String alias,
|
||||
required SnChannel content,
|
||||
Value<DateTime> createdAt = const Value.absent(),
|
||||
}) =>
|
||||
SnLocalChatChannelCompanion.insert(
|
||||
id: id,
|
||||
alias: alias,
|
||||
content: content,
|
||||
createdAt: createdAt,
|
||||
),
|
||||
withReferenceMapper: (p0) => p0
|
||||
.map((e) => (e.readTable(table), BaseReferences(db, table, e)))
|
||||
.toList(),
|
||||
prefetchHooksCallback: null,
|
||||
));
|
||||
}
|
||||
|
||||
typedef $$SnLocalChatChannelTableProcessedTableManager = ProcessedTableManager<
|
||||
_$AppDatabase,
|
||||
$SnLocalChatChannelTable,
|
||||
SnLocalChatChannelData,
|
||||
$$SnLocalChatChannelTableFilterComposer,
|
||||
$$SnLocalChatChannelTableOrderingComposer,
|
||||
$$SnLocalChatChannelTableAnnotationComposer,
|
||||
$$SnLocalChatChannelTableCreateCompanionBuilder,
|
||||
$$SnLocalChatChannelTableUpdateCompanionBuilder,
|
||||
(
|
||||
SnLocalChatChannelData,
|
||||
BaseReferences<_$AppDatabase, $SnLocalChatChannelTable,
|
||||
SnLocalChatChannelData>
|
||||
),
|
||||
SnLocalChatChannelData,
|
||||
PrefetchHooks Function()>;
|
||||
typedef $$SnLocalChatMessageTableCreateCompanionBuilder
|
||||
= SnLocalChatMessageCompanion Function({
|
||||
Value<int> id,
|
||||
required int channelId,
|
||||
required SnChatMessage content,
|
||||
Value<DateTime> createdAt,
|
||||
});
|
||||
typedef $$SnLocalChatMessageTableUpdateCompanionBuilder
|
||||
= SnLocalChatMessageCompanion Function({
|
||||
Value<int> id,
|
||||
Value<int> channelId,
|
||||
Value<SnChatMessage> content,
|
||||
Value<DateTime> createdAt,
|
||||
});
|
||||
|
||||
class $$SnLocalChatMessageTableFilterComposer
|
||||
extends Composer<_$AppDatabase, $SnLocalChatMessageTable> {
|
||||
$$SnLocalChatMessageTableFilterComposer({
|
||||
required super.$db,
|
||||
required super.$table,
|
||||
super.joinBuilder,
|
||||
super.$addJoinBuilderToRootComposer,
|
||||
super.$removeJoinBuilderFromRootComposer,
|
||||
});
|
||||
ColumnFilters<int> get id => $composableBuilder(
|
||||
column: $table.id, builder: (column) => ColumnFilters(column));
|
||||
|
||||
ColumnFilters<int> get channelId => $composableBuilder(
|
||||
column: $table.channelId, builder: (column) => ColumnFilters(column));
|
||||
|
||||
ColumnWithTypeConverterFilters<SnChatMessage, SnChatMessage, String>
|
||||
get content => $composableBuilder(
|
||||
column: $table.content,
|
||||
builder: (column) => ColumnWithTypeConverterFilters(column));
|
||||
|
||||
ColumnFilters<DateTime> get createdAt => $composableBuilder(
|
||||
column: $table.createdAt, builder: (column) => ColumnFilters(column));
|
||||
}
|
||||
|
||||
class $$SnLocalChatMessageTableOrderingComposer
|
||||
extends Composer<_$AppDatabase, $SnLocalChatMessageTable> {
|
||||
$$SnLocalChatMessageTableOrderingComposer({
|
||||
required super.$db,
|
||||
required super.$table,
|
||||
super.joinBuilder,
|
||||
super.$addJoinBuilderToRootComposer,
|
||||
super.$removeJoinBuilderFromRootComposer,
|
||||
});
|
||||
ColumnOrderings<int> get id => $composableBuilder(
|
||||
column: $table.id, builder: (column) => ColumnOrderings(column));
|
||||
|
||||
ColumnOrderings<int> get channelId => $composableBuilder(
|
||||
column: $table.channelId, builder: (column) => ColumnOrderings(column));
|
||||
|
||||
ColumnOrderings<String> get content => $composableBuilder(
|
||||
column: $table.content, builder: (column) => ColumnOrderings(column));
|
||||
|
||||
ColumnOrderings<DateTime> get createdAt => $composableBuilder(
|
||||
column: $table.createdAt, builder: (column) => ColumnOrderings(column));
|
||||
}
|
||||
|
||||
class $$SnLocalChatMessageTableAnnotationComposer
|
||||
extends Composer<_$AppDatabase, $SnLocalChatMessageTable> {
|
||||
$$SnLocalChatMessageTableAnnotationComposer({
|
||||
required super.$db,
|
||||
required super.$table,
|
||||
super.joinBuilder,
|
||||
super.$addJoinBuilderToRootComposer,
|
||||
super.$removeJoinBuilderFromRootComposer,
|
||||
});
|
||||
GeneratedColumn<int> get id =>
|
||||
$composableBuilder(column: $table.id, builder: (column) => column);
|
||||
|
||||
GeneratedColumn<int> get channelId =>
|
||||
$composableBuilder(column: $table.channelId, builder: (column) => column);
|
||||
|
||||
GeneratedColumnWithTypeConverter<SnChatMessage, String> get content =>
|
||||
$composableBuilder(column: $table.content, builder: (column) => column);
|
||||
|
||||
GeneratedColumn<DateTime> get createdAt =>
|
||||
$composableBuilder(column: $table.createdAt, builder: (column) => column);
|
||||
}
|
||||
|
||||
class $$SnLocalChatMessageTableTableManager extends RootTableManager<
|
||||
_$AppDatabase,
|
||||
$SnLocalChatMessageTable,
|
||||
SnLocalChatMessageData,
|
||||
$$SnLocalChatMessageTableFilterComposer,
|
||||
$$SnLocalChatMessageTableOrderingComposer,
|
||||
$$SnLocalChatMessageTableAnnotationComposer,
|
||||
$$SnLocalChatMessageTableCreateCompanionBuilder,
|
||||
$$SnLocalChatMessageTableUpdateCompanionBuilder,
|
||||
(
|
||||
SnLocalChatMessageData,
|
||||
BaseReferences<_$AppDatabase, $SnLocalChatMessageTable,
|
||||
SnLocalChatMessageData>
|
||||
),
|
||||
SnLocalChatMessageData,
|
||||
PrefetchHooks Function()> {
|
||||
$$SnLocalChatMessageTableTableManager(
|
||||
_$AppDatabase db, $SnLocalChatMessageTable table)
|
||||
: super(TableManagerState(
|
||||
db: db,
|
||||
table: table,
|
||||
createFilteringComposer: () =>
|
||||
$$SnLocalChatMessageTableFilterComposer($db: db, $table: table),
|
||||
createOrderingComposer: () =>
|
||||
$$SnLocalChatMessageTableOrderingComposer($db: db, $table: table),
|
||||
createComputedFieldComposer: () =>
|
||||
$$SnLocalChatMessageTableAnnotationComposer(
|
||||
$db: db, $table: table),
|
||||
updateCompanionCallback: ({
|
||||
Value<int> id = const Value.absent(),
|
||||
Value<int> channelId = const Value.absent(),
|
||||
Value<SnChatMessage> content = const Value.absent(),
|
||||
Value<DateTime> createdAt = const Value.absent(),
|
||||
}) =>
|
||||
SnLocalChatMessageCompanion(
|
||||
id: id,
|
||||
channelId: channelId,
|
||||
content: content,
|
||||
createdAt: createdAt,
|
||||
),
|
||||
createCompanionCallback: ({
|
||||
Value<int> id = const Value.absent(),
|
||||
required int channelId,
|
||||
required SnChatMessage content,
|
||||
Value<DateTime> createdAt = const Value.absent(),
|
||||
}) =>
|
||||
SnLocalChatMessageCompanion.insert(
|
||||
id: id,
|
||||
channelId: channelId,
|
||||
content: content,
|
||||
createdAt: createdAt,
|
||||
),
|
||||
withReferenceMapper: (p0) => p0
|
||||
.map((e) => (e.readTable(table), BaseReferences(db, table, e)))
|
||||
.toList(),
|
||||
prefetchHooksCallback: null,
|
||||
));
|
||||
}
|
||||
|
||||
typedef $$SnLocalChatMessageTableProcessedTableManager = ProcessedTableManager<
|
||||
_$AppDatabase,
|
||||
$SnLocalChatMessageTable,
|
||||
SnLocalChatMessageData,
|
||||
$$SnLocalChatMessageTableFilterComposer,
|
||||
$$SnLocalChatMessageTableOrderingComposer,
|
||||
$$SnLocalChatMessageTableAnnotationComposer,
|
||||
$$SnLocalChatMessageTableCreateCompanionBuilder,
|
||||
$$SnLocalChatMessageTableUpdateCompanionBuilder,
|
||||
(
|
||||
SnLocalChatMessageData,
|
||||
BaseReferences<_$AppDatabase, $SnLocalChatMessageTable,
|
||||
SnLocalChatMessageData>
|
||||
),
|
||||
SnLocalChatMessageData,
|
||||
PrefetchHooks Function()>;
|
||||
|
||||
class $AppDatabaseManager {
|
||||
final _$AppDatabase _db;
|
||||
$AppDatabaseManager(this._db);
|
||||
$$SnLocalChatChannelTableTableManager get snLocalChatChannel =>
|
||||
$$SnLocalChatChannelTableTableManager(_db, _db.snLocalChatChannel);
|
||||
$$SnLocalChatMessageTableTableManager get snLocalChatMessage =>
|
||||
$$SnLocalChatMessageTableTableManager(_db, _db.snLocalChatMessage);
|
||||
}
|
8
lib/database/drift_worker.dart
Normal file
8
lib/database/drift_worker.dart
Normal file
@ -0,0 +1,8 @@
|
||||
import 'package:drift/wasm.dart';
|
||||
|
||||
// Use `dart compile js -O4 ./drift_worker.dart` to compile this file.
|
||||
// And place it in the web/ directory.
|
||||
|
||||
// When compiled with dart2js, this file defines a dedicated or shared web
|
||||
// worker used by drift.
|
||||
void main() => WasmDatabase.workerMainForOpen();
|
@ -13,7 +13,6 @@ import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:hive_flutter/hive_flutter.dart';
|
||||
import 'package:hotkey_manager/hotkey_manager.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
@ -24,6 +23,7 @@ import 'package:surface/firebase_options.dart';
|
||||
import 'package:surface/providers/channel.dart';
|
||||
import 'package:surface/providers/chat_call.dart';
|
||||
import 'package:surface/providers/config.dart';
|
||||
import 'package:surface/providers/database.dart';
|
||||
import 'package:surface/providers/link_preview.dart';
|
||||
import 'package:surface/providers/navigation.dart';
|
||||
import 'package:surface/providers/notification.dart';
|
||||
@ -31,6 +31,7 @@ import 'package:surface/providers/post.dart';
|
||||
import 'package:surface/providers/relationship.dart';
|
||||
import 'package:surface/providers/sn_attachment.dart';
|
||||
import 'package:surface/providers/sn_network.dart';
|
||||
import 'package:surface/providers/sn_realm.dart';
|
||||
import 'package:surface/providers/sn_sticker.dart';
|
||||
import 'package:surface/providers/special_day.dart';
|
||||
import 'package:surface/providers/theme.dart';
|
||||
@ -39,8 +40,6 @@ import 'package:surface/providers/userinfo.dart';
|
||||
import 'package:surface/providers/websocket.dart';
|
||||
import 'package:surface/providers/widget.dart';
|
||||
import 'package:surface/router.dart';
|
||||
import 'package:surface/types/chat.dart';
|
||||
import 'package:surface/types/realm.dart';
|
||||
import 'package:flutter_web_plugins/url_strategy.dart' show usePathUrlStrategy;
|
||||
import 'package:surface/widgets/dialog.dart';
|
||||
import 'package:tray_manager/tray_manager.dart';
|
||||
@ -49,6 +48,7 @@ import 'package:workmanager/workmanager.dart';
|
||||
import 'package:in_app_review/in_app_review.dart';
|
||||
import 'package:image_picker_android/image_picker_android.dart';
|
||||
import 'package:image_picker_platform_interface/image_picker_platform_interface.dart';
|
||||
import 'package:local_notifier/local_notifier.dart';
|
||||
|
||||
@pragma('vm:entry-point')
|
||||
void appBackgroundDispatcher() {
|
||||
@ -81,13 +81,7 @@ void main() async {
|
||||
|
||||
await EasyLocalization.ensureInitialized();
|
||||
|
||||
await Hive.initFlutter();
|
||||
Hive.registerAdapter(SnChannelImplAdapter());
|
||||
Hive.registerAdapter(SnRealmImplAdapter());
|
||||
Hive.registerAdapter(SnChannelMemberImplAdapter());
|
||||
Hive.registerAdapter(SnChatMessageImplAdapter());
|
||||
|
||||
if (kIsWeb && !Platform.isLinux) {
|
||||
if (!kIsWeb && !Platform.isLinux) {
|
||||
await Firebase.initializeApp(
|
||||
options: DefaultFirebaseOptions.currentPlatform,
|
||||
);
|
||||
@ -113,7 +107,8 @@ void main() async {
|
||||
}
|
||||
|
||||
if (!kIsWeb && Platform.isAndroid) {
|
||||
final ImagePickerPlatform imagePickerImplementation = ImagePickerPlatform.instance;
|
||||
final ImagePickerPlatform imagePickerImplementation =
|
||||
ImagePickerPlatform.instance;
|
||||
if (imagePickerImplementation is ImagePickerAndroid) {
|
||||
imagePickerImplementation.useAndroidPhotoPicker = true;
|
||||
}
|
||||
@ -141,6 +136,9 @@ class SolianApp extends StatelessWidget {
|
||||
assetLoader: JsonAssetLoader(),
|
||||
child: MultiProvider(
|
||||
providers: [
|
||||
// Infrastructure layer
|
||||
Provider(create: (ctx) => DatabaseProvider(ctx)),
|
||||
|
||||
// System extensions layer
|
||||
Provider(create: (ctx) => HomeWidgetProvider(ctx)),
|
||||
|
||||
@ -155,6 +153,7 @@ class SolianApp extends StatelessWidget {
|
||||
Provider(create: (ctx) => SnNetworkProvider(ctx)),
|
||||
Provider(create: (ctx) => UserDirectoryProvider(ctx)),
|
||||
Provider(create: (ctx) => SnAttachmentProvider(ctx)),
|
||||
Provider(create: (ctx) => SnRealmProvider(ctx)),
|
||||
Provider(create: (ctx) => SnPostContentProvider(ctx)),
|
||||
Provider(create: (ctx) => SnRelationshipProvider(ctx)),
|
||||
Provider(create: (ctx) => SnLinkPreviewProvider(ctx)),
|
||||
@ -228,7 +227,8 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener {
|
||||
if (prefs.containsKey('first_boot_time')) {
|
||||
final rawTime = prefs.getString('first_boot_time');
|
||||
final time = DateTime.tryParse(rawTime ?? '');
|
||||
if (time != null && time.isBefore(DateTime.now().subtract(const Duration(days: 3)))) {
|
||||
if (time != null &&
|
||||
time.isBefore(DateTime.now().subtract(const Duration(days: 3)))) {
|
||||
final inAppReview = InAppReview.instance;
|
||||
if (prefs.getBool('rating_requested') == true) return;
|
||||
if (await inAppReview.isAvailable()) {
|
||||
@ -256,13 +256,18 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener {
|
||||
).get(
|
||||
'https://git.solsynth.dev/api/v1/repos/HyperNet/Surface/tags?page=1&limit=1',
|
||||
);
|
||||
final remoteVersionString = (resp.data as List).firstOrNull?['name'] ?? '0.0.0+0';
|
||||
final remoteVersionString =
|
||||
(resp.data as List).firstOrNull?['name'] ?? '0.0.0+0';
|
||||
final remoteVersion = Version.parse(remoteVersionString.split('+').first);
|
||||
final localVersion = Version.parse(localVersionString.split('+').first);
|
||||
final remoteBuildNumber = int.tryParse(remoteVersionString.split('+').last) ?? 0;
|
||||
final localBuildNumber = int.tryParse(localVersionString.split('+').last) ?? 0;
|
||||
final remoteBuildNumber =
|
||||
int.tryParse(remoteVersionString.split('+').last) ?? 0;
|
||||
final localBuildNumber =
|
||||
int.tryParse(localVersionString.split('+').last) ?? 0;
|
||||
log("[Update] Local: $localVersionString, Remote: $remoteVersionString");
|
||||
if ((remoteVersion > localVersion || remoteBuildNumber > localBuildNumber) && mounted) {
|
||||
if ((remoteVersion > localVersion ||
|
||||
remoteBuildNumber > localBuildNumber) &&
|
||||
mounted) {
|
||||
final config = context.read<ConfigProvider>();
|
||||
config.setUpdate(remoteVersionString);
|
||||
log("[Update] Update available: $remoteVersionString");
|
||||
@ -299,7 +304,8 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener {
|
||||
await notify.registerPushNotifications();
|
||||
if (!mounted) return;
|
||||
final sticker = context.read<SnStickerProvider>();
|
||||
await sticker.listStickerEagerly();
|
||||
await sticker.listSticker();
|
||||
log('[Bootstrap] Everything initialized!');
|
||||
} catch (err) {
|
||||
if (!mounted) return;
|
||||
await context.showErrorDialog(err);
|
||||
@ -329,7 +335,9 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener {
|
||||
Future<void> _trayInitialization() async {
|
||||
if (kIsWeb || Platform.isAndroid || Platform.isIOS) return;
|
||||
|
||||
final icon = Platform.isWindows ? 'assets/icon/tray-icon.ico' : 'assets/icon/tray-icon.png';
|
||||
final icon = Platform.isWindows
|
||||
? 'assets/icon/tray-icon.ico'
|
||||
: 'assets/icon/tray-icon.png';
|
||||
final appVersion = await PackageInfo.fromPlatform();
|
||||
|
||||
trayManager.addListener(this);
|
||||
@ -343,6 +351,10 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener {
|
||||
disabled: true,
|
||||
),
|
||||
MenuItem.separator(),
|
||||
MenuItem(
|
||||
key: 'window_show',
|
||||
label: 'trayMenuShow'.tr(),
|
||||
),
|
||||
MenuItem(
|
||||
key: 'exit',
|
||||
label: 'trayMenuExit'.tr(),
|
||||
@ -352,6 +364,15 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener {
|
||||
await trayManager.setContextMenu(menu);
|
||||
}
|
||||
|
||||
Future<void> _notifyInitialization() async {
|
||||
if (kIsWeb || Platform.isAndroid || Platform.isIOS) return;
|
||||
|
||||
await localNotifier.setup(
|
||||
appName: 'solian',
|
||||
shortcutPolicy: ShortcutPolicy.requireCreate,
|
||||
);
|
||||
}
|
||||
|
||||
AppLifecycleListener? _appLifecycleListener;
|
||||
|
||||
@override
|
||||
@ -366,6 +387,7 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener {
|
||||
|
||||
_trayInitialization();
|
||||
_hotkeyInitialization();
|
||||
_notifyInitialization();
|
||||
_initialize().then((_) {
|
||||
_postInitialization();
|
||||
_tryRequestRating();
|
||||
@ -401,6 +423,9 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener {
|
||||
@override
|
||||
void onTrayMenuItemClick(MenuItem menuItem) {
|
||||
switch (menuItem.key) {
|
||||
case 'window_show':
|
||||
appWindow.show();
|
||||
break;
|
||||
case 'exit':
|
||||
_appLifecycleListener?.dispose();
|
||||
SystemChannels.platform.invokeMethod('SystemNavigator.pop');
|
||||
@ -427,8 +452,16 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener {
|
||||
});
|
||||
return false;
|
||||
},
|
||||
child: SizeChangedLayoutNotifier(
|
||||
child: widget.child,
|
||||
child: OrientationBuilder(
|
||||
builder: (context, orientation) {
|
||||
final cfg = context.read<ConfigProvider>();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
cfg.calcDrawerSize(context);
|
||||
});
|
||||
return SizeChangedLayoutNotifier(
|
||||
child: widget.child,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -1,48 +1,54 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hive_flutter/hive_flutter.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:surface/controllers/chat_message_controller.dart';
|
||||
import 'package:surface/database/database.dart';
|
||||
import 'package:surface/providers/database.dart';
|
||||
import 'package:surface/providers/sn_network.dart';
|
||||
import 'package:surface/providers/sn_realm.dart';
|
||||
import 'package:surface/providers/user_directory.dart';
|
||||
import 'package:surface/types/chat.dart';
|
||||
import 'package:surface/types/realm.dart';
|
||||
|
||||
class ChatChannelProvider extends ChangeNotifier {
|
||||
static const kChatChannelBoxName = 'nex_chat_channels';
|
||||
|
||||
late final SnNetworkProvider _sn;
|
||||
late final UserDirectoryProvider _ud;
|
||||
|
||||
Box<SnChannel>? get _channelBox => Hive.box<SnChannel>(kChatChannelBoxName);
|
||||
late final DatabaseProvider _dt;
|
||||
late final SnRealmProvider _rels;
|
||||
|
||||
ChatChannelProvider(BuildContext context) {
|
||||
_sn = context.read<SnNetworkProvider>();
|
||||
_ud = context.read<UserDirectoryProvider>();
|
||||
_initializeLocalData();
|
||||
}
|
||||
|
||||
Future<void> _initializeLocalData() async {
|
||||
await Hive.openBox<SnChannel>(kChatChannelBoxName);
|
||||
_dt = context.read<DatabaseProvider>();
|
||||
_rels = context.read<SnRealmProvider>();
|
||||
}
|
||||
|
||||
Future<void> _saveChannelToLocal(Iterable<SnChannel> channels) async {
|
||||
if (_channelBox == null) return;
|
||||
await _channelBox!.putAll({
|
||||
for (final channel in channels) channel.key: channel,
|
||||
});
|
||||
await Future.wait(
|
||||
channels.map(
|
||||
(ele) => _dt.db.snLocalChatChannel.insertOne(
|
||||
SnLocalChatChannelCompanion.insert(
|
||||
id: Value(ele.id),
|
||||
alias: ele.key,
|
||||
content: ele,
|
||||
createdAt: Value(ele.createdAt),
|
||||
),
|
||||
onConflict: DoUpdate(
|
||||
(_) => SnLocalChatChannelCompanion.custom(
|
||||
content: Constant(jsonEncode(ele.toJson())),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<List<SnChannel>> _fetchChannelsFromServer({
|
||||
String scope = 'global',
|
||||
bool direct = false,
|
||||
bool doNotSave = false,
|
||||
}) async {
|
||||
final resp = await _sn.client.get(
|
||||
'/cgi/im/channels/$scope/me/available',
|
||||
queryParameters: {
|
||||
'direct': direct,
|
||||
},
|
||||
);
|
||||
final resp = await _sn.client.get('/cgi/im/channels/me/available');
|
||||
final out = List<SnChannel>.from(
|
||||
resp.data?.map((e) => SnChannel.fromJson(e)) ?? [],
|
||||
);
|
||||
@ -54,18 +60,21 @@ class ChatChannelProvider extends ChangeNotifier {
|
||||
/// It will use the local storage as much as possible.
|
||||
/// The alias should include the scope, formatted as `scope:alias`.
|
||||
Future<SnChannel> getChannel(String key) async {
|
||||
if (_channelBox != null) {
|
||||
final local = _channelBox!.get(key);
|
||||
if (local != null) return local;
|
||||
final local = await (_dt.db.snLocalChatChannel.select()
|
||||
..where((e) => e.alias.equals(key)))
|
||||
.getSingleOrNull();
|
||||
if (local != null) {
|
||||
final out = local.content;
|
||||
return out.copyWith(realm: await _rels.getRealm(out.realmId!));
|
||||
}
|
||||
|
||||
var resp = await _sn.client.get('/cgi/im/channels/$key');
|
||||
var resp =
|
||||
await _sn.client.get('/cgi/im/channels/${key.replaceAll(':', '/')}');
|
||||
var out = SnChannel.fromJson(resp.data);
|
||||
|
||||
// Preload realm of the channel
|
||||
if (out.realmId != null) {
|
||||
resp = await _sn.client.get('/cgi/id/realms/${out.realmId}');
|
||||
out = out.copyWith(realm: SnRealm.fromJson(resp.data));
|
||||
out = out.copyWith(realm: await _rels.getRealm(out.realmId!));
|
||||
}
|
||||
|
||||
_saveChannelToLocal([out]);
|
||||
@ -77,66 +86,63 @@ class ChatChannelProvider extends ChangeNotifier {
|
||||
/// And the second time is when the data was fetched from the server.
|
||||
/// But there is some exception that will only cause one of them to be emitted.
|
||||
/// Like the local storage is broken or the server is down.
|
||||
Stream<List<SnChannel>> fetchChannels() async* {
|
||||
if (_channelBox != null) yield _channelBox!.values.toList();
|
||||
|
||||
var resp = await _sn.client.get('/cgi/id/realms/me/available');
|
||||
final realms = List<SnRealm>.from(
|
||||
resp.data?.map((e) => SnRealm.fromJson(e)) ?? [],
|
||||
);
|
||||
final realmMap = {
|
||||
for (final realm in realms) realm.alias: realm,
|
||||
};
|
||||
|
||||
final scopeToFetch = {'global', ...realms.map((e) => e.alias)};
|
||||
|
||||
final List<SnChannel> result = List.empty(growable: true);
|
||||
final directMessages = await _fetchChannelsFromServer(
|
||||
scope: scopeToFetch.first,
|
||||
direct: true,
|
||||
);
|
||||
result.addAll(directMessages);
|
||||
|
||||
final nonBelongsChannels = await _fetchChannelsFromServer(
|
||||
scope: scopeToFetch.first,
|
||||
direct: false,
|
||||
);
|
||||
result.addAll(nonBelongsChannels);
|
||||
|
||||
for (final scope in scopeToFetch.skip(1)) {
|
||||
final channel = await _fetchChannelsFromServer(
|
||||
scope: scope,
|
||||
direct: false,
|
||||
doNotSave: true,
|
||||
);
|
||||
final out = channel.map((ele) => ele.copyWith(realm: realmMap[scope]));
|
||||
_saveChannelToLocal(out);
|
||||
result.addAll(out);
|
||||
Stream<List<SnChannel>> fetchChannels(
|
||||
{bool noRemote = false, bool noLocal = false}) async* {
|
||||
if (!noLocal) {
|
||||
final local = await (_dt.db.snLocalChatChannel.select()
|
||||
..orderBy([
|
||||
(e) =>
|
||||
OrderingTerm(expression: e.createdAt, mode: OrderingMode.desc)
|
||||
]))
|
||||
.get();
|
||||
final out = local.map((e) => e.content).toList();
|
||||
for (var idx = 0; idx < out.length; idx++) {
|
||||
final channel = out[idx];
|
||||
if (channel.realmId != null) {
|
||||
out[idx] = out[idx].copyWith(
|
||||
realm: await _rels.getRealm(channel.realmId!),
|
||||
);
|
||||
}
|
||||
}
|
||||
yield out;
|
||||
}
|
||||
|
||||
if (noRemote) return;
|
||||
final List<SnChannel> result = List.empty(growable: true);
|
||||
final channels = await _fetchChannelsFromServer();
|
||||
for (var idx = 0; idx < channels.length; idx++) {
|
||||
final channel = channels[idx];
|
||||
if (channel.realmId != null) {
|
||||
channels[idx] = channels[idx].copyWith(
|
||||
realm: await _rels.getRealm(channel.realmId!),
|
||||
);
|
||||
}
|
||||
}
|
||||
result.addAll(channels);
|
||||
|
||||
yield result;
|
||||
}
|
||||
|
||||
Future<List<SnChatMessage>> getLastMessages(
|
||||
Iterable<SnChannel> channels,
|
||||
) async {
|
||||
final result = List<SnChatMessage>.empty(growable: true);
|
||||
final result = List<Future<SnLocalChatMessageData?>>.empty(growable: true);
|
||||
for (final channel in channels) {
|
||||
final channelBox = await Hive.openBox<SnChatMessage>(
|
||||
'${ChatMessageController.kChatMessageBoxPrefix}${channel.id}',
|
||||
);
|
||||
final lastMessage =
|
||||
channelBox.isNotEmpty ? channelBox.values.reduce((a, b) => a.createdAt.isAfter(b.createdAt) ? a : b) : null;
|
||||
if (lastMessage != null) result.add(lastMessage);
|
||||
channelBox.close();
|
||||
final out = (_dt.db.snLocalChatMessage.select()
|
||||
..where((e) => e.channelId.equals(channel.id))
|
||||
..orderBy([
|
||||
(e) =>
|
||||
OrderingTerm(expression: e.createdAt, mode: OrderingMode.desc)
|
||||
])
|
||||
..limit(1))
|
||||
.getSingleOrNull();
|
||||
result.add(out);
|
||||
}
|
||||
await _ud.listAccount(result.map((ele) => ele.sender.accountId).toSet());
|
||||
return result;
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_channelBox?.close();
|
||||
super.dispose();
|
||||
final out = (await Future.wait(result))
|
||||
.where((e) => e != null)
|
||||
.map((e) => e!.content)
|
||||
.toList();
|
||||
await _ud.listAccount(out.map((ele) => ele.sender.accountId).toSet());
|
||||
return out;
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ const kAppDrawerPreferCollapse = 'app_drawer_prefer_collapse';
|
||||
const kAppNotifyWithHaptic = 'app_notify_with_haptic';
|
||||
const kAppExpandPostLink = 'app_expand_post_link';
|
||||
const kAppExpandChatLink = 'app_expand_chat_link';
|
||||
const kAppRealmCompactView = 'app_realm_compact_view';
|
||||
|
||||
const Map<String, FilterQuality> kImageQualityLevel = {
|
||||
'settingsImageQualityLowest': FilterQuality.none,
|
||||
@ -72,6 +73,13 @@ class ConfigProvider extends ChangeNotifier {
|
||||
return prefs.getString(kNetworkServerStoreKey) ?? kNetworkServerDefault;
|
||||
}
|
||||
|
||||
bool get realmCompactView {
|
||||
return prefs.getBool(kAppRealmCompactView) ?? false;
|
||||
}
|
||||
set realmCompactView(bool value) {
|
||||
prefs.setBool(kAppRealmCompactView, value);
|
||||
}
|
||||
|
||||
set serverUrl(String url) {
|
||||
prefs.setString(kNetworkServerStoreKey, url);
|
||||
_home.saveWidgetData("nex_server_url", url);
|
||||
|
31
lib/providers/database.dart
Normal file
31
lib/providers/database.dart
Normal file
@ -0,0 +1,31 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:path/path.dart' show join;
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:surface/database/database.dart';
|
||||
|
||||
class DatabaseProvider {
|
||||
late AppDatabase db;
|
||||
|
||||
DatabaseProvider(BuildContext context) {
|
||||
db = AppDatabase();
|
||||
}
|
||||
|
||||
Future<int> getDatabaseSize() async {
|
||||
if (kIsWeb) return 0;
|
||||
final basepath = await getApplicationSupportDirectory();
|
||||
return await File(join(basepath.path, 'solar_network_data.sqlite'))
|
||||
.length();
|
||||
}
|
||||
|
||||
Future<void> removeDatabase() async {
|
||||
if (kIsWeb) return;
|
||||
final basepath = await getApplicationSupportDirectory();
|
||||
final file = File(join(basepath.path, 'solar_network_data.sqlite'));
|
||||
db.close();
|
||||
await file.delete();
|
||||
db = AppDatabase();
|
||||
}
|
||||
}
|
@ -63,6 +63,11 @@ class NavigationProvider extends ChangeNotifier {
|
||||
screen: 'news',
|
||||
label: 'screenNews',
|
||||
),
|
||||
AppNavDestination(
|
||||
icon: Icon(Symbols.emoji_emotions, weight: 400, opticalSize: 20),
|
||||
screen: 'stickers',
|
||||
label: 'screenStickers',
|
||||
),
|
||||
AppNavDestination(
|
||||
icon: Icon(Symbols.photo_library, weight: 400, opticalSize: 20),
|
||||
screen: 'album',
|
||||
@ -88,7 +93,8 @@ class NavigationProvider extends ChangeNotifier {
|
||||
|
||||
List<AppNavDestination> destinations = [];
|
||||
|
||||
int get pinnedDestinationCount => destinations.where((ele) => ele.isPinned).length;
|
||||
int get pinnedDestinationCount =>
|
||||
destinations.where((ele) => ele.isPinned).length;
|
||||
|
||||
NavigationProvider() {
|
||||
buildDestinations(kDefaultPinnedDestination);
|
||||
@ -117,13 +123,17 @@ class NavigationProvider extends ChangeNotifier {
|
||||
}
|
||||
|
||||
bool isIndexInRange(int min, int max) {
|
||||
return _currentIndex != null && _currentIndex! >= min && _currentIndex! < max;
|
||||
return _currentIndex != null &&
|
||||
_currentIndex! >= min &&
|
||||
_currentIndex! < max;
|
||||
}
|
||||
|
||||
void autoDetectIndex(GoRouter? state) {
|
||||
if (state == null) return;
|
||||
final idx = destinations.indexWhere(
|
||||
(ele) => ele.screen == state.routerDelegate.currentConfiguration.last.route.name,
|
||||
(ele) =>
|
||||
ele.screen ==
|
||||
state.routerDelegate.currentConfiguration.last.route.name,
|
||||
);
|
||||
_currentIndex = idx == -1 ? null : idx;
|
||||
notifyListeners();
|
||||
|
@ -1,11 +1,13 @@
|
||||
import 'dart:developer';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:bitsdojo_window/bitsdojo_window.dart';
|
||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_udid/flutter_udid.dart';
|
||||
import 'package:local_notifier/local_notifier.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:surface/providers/config.dart';
|
||||
import 'package:surface/providers/sn_network.dart';
|
||||
@ -84,7 +86,7 @@ class NotificationProvider extends ChangeNotifier {
|
||||
showingCount++;
|
||||
showingTrayCount++;
|
||||
notifications.add(notification);
|
||||
Future.delayed(const Duration(seconds: 3), () {
|
||||
Future.delayed(const Duration(seconds: 5), () {
|
||||
if (showingCount >= 0) showingCount--;
|
||||
notifyListeners();
|
||||
});
|
||||
@ -92,6 +94,20 @@ class NotificationProvider extends ChangeNotifier {
|
||||
updateTray();
|
||||
final doHaptic = _cfg.prefs.getBool(kAppNotifyWithHaptic) ?? true;
|
||||
if (doHaptic) HapticFeedback.mediumImpact();
|
||||
|
||||
if (!kIsWeb) {
|
||||
if (Platform.isWindows || Platform.isLinux || Platform.isMacOS) {
|
||||
LocalNotification notify = LocalNotification(
|
||||
title: notification.title,
|
||||
subtitle: notification.subtitle,
|
||||
body: notification.body,
|
||||
);
|
||||
notify.onClick = () {
|
||||
appWindow.show();
|
||||
};
|
||||
notify.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -2,19 +2,23 @@ import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:surface/providers/sn_attachment.dart';
|
||||
import 'package:surface/providers/sn_network.dart';
|
||||
import 'package:surface/providers/sn_realm.dart';
|
||||
import 'package:surface/providers/user_directory.dart';
|
||||
import 'package:surface/types/poll.dart';
|
||||
import 'package:surface/types/post.dart';
|
||||
import 'package:surface/types/realm.dart';
|
||||
|
||||
class SnPostContentProvider {
|
||||
late final SnNetworkProvider _sn;
|
||||
late final UserDirectoryProvider _ud;
|
||||
late final SnAttachmentProvider _attach;
|
||||
late final SnRealmProvider _realm;
|
||||
|
||||
SnPostContentProvider(BuildContext context) {
|
||||
_sn = context.read<SnNetworkProvider>();
|
||||
_ud = context.read<UserDirectoryProvider>();
|
||||
_attach = context.read<SnAttachmentProvider>();
|
||||
_realm = context.read<SnRealmProvider>();
|
||||
}
|
||||
|
||||
Future<SnPoll> _fetchPoll(int id) async {
|
||||
@ -42,9 +46,13 @@ class SnPostContentProvider {
|
||||
final attachments = await _attach.getMultiple(rids.toList());
|
||||
for (var i = 0; i < out.length; i++) {
|
||||
SnPoll? poll;
|
||||
SnRealm? realm;
|
||||
if (out[i].pollId != null) {
|
||||
poll = await _fetchPoll(out[i].pollId!);
|
||||
}
|
||||
if (out[i].realmId != null) {
|
||||
realm = await _realm.getRealm(out[i].realmId!);
|
||||
}
|
||||
|
||||
out[i] = out[i].copyWith(
|
||||
preload: SnPostPreload(
|
||||
@ -52,6 +60,7 @@ class SnPostContentProvider {
|
||||
attachments: attachments.where((ele) => out[i].body['attachments']?.contains(ele?.rid) ?? false).toList(),
|
||||
video: attachments.where((ele) => ele?.rid == out[i].body['video']).firstOrNull,
|
||||
poll: poll,
|
||||
realm: realm,
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -81,9 +90,13 @@ class SnPostContentProvider {
|
||||
final attachments = await _attach.getMultiple(rids.toList());
|
||||
|
||||
SnPoll? poll;
|
||||
SnRealm? realm;
|
||||
if (out.pollId != null) {
|
||||
poll = await _fetchPoll(out.pollId!);
|
||||
}
|
||||
if (out.realmId != null) {
|
||||
realm = await _realm.getRealm(out.realmId!);
|
||||
}
|
||||
|
||||
out = out.copyWith(
|
||||
preload: SnPostPreload(
|
||||
@ -91,6 +104,7 @@ class SnPostContentProvider {
|
||||
attachments: attachments.where((ele) => out.body['attachments']?.contains(ele?.rid) ?? false).toList(),
|
||||
video: attachments.where((ele) => ele?.rid == out.body['video']).firstOrNull,
|
||||
poll: poll,
|
||||
realm: realm,
|
||||
),
|
||||
);
|
||||
|
||||
@ -112,6 +126,8 @@ class SnPostContentProvider {
|
||||
String? author,
|
||||
Iterable<String>? categories,
|
||||
Iterable<String>? tags,
|
||||
String? realm,
|
||||
String? channel,
|
||||
}) async {
|
||||
final resp = await _sn.client.get('/cgi/co/posts', queryParameters: {
|
||||
'take': take,
|
||||
@ -120,6 +136,8 @@ class SnPostContentProvider {
|
||||
if (author != null) 'author': author,
|
||||
if (tags?.isNotEmpty ?? false) 'tags': tags!.join(','),
|
||||
if (categories?.isNotEmpty ?? false) 'categories': categories!.join(','),
|
||||
if (realm != null) 'realm': realm,
|
||||
if (channel != null) 'channel': channel,
|
||||
});
|
||||
final List<SnPost> out = await _preloadRelatedDataInBatch(
|
||||
List.from(resp.data['data']?.map((e) => SnPost.fromJson(e)) ?? []),
|
||||
|
37
lib/providers/sn_realm.dart
Normal file
37
lib/providers/sn_realm.dart
Normal file
@ -0,0 +1,37 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:surface/providers/sn_network.dart';
|
||||
import 'package:surface/types/realm.dart';
|
||||
|
||||
class SnRealmProvider {
|
||||
late final SnNetworkProvider _sn;
|
||||
|
||||
SnRealmProvider(BuildContext context) {
|
||||
_sn = context.read<SnNetworkProvider>();
|
||||
}
|
||||
|
||||
final Map<String, SnRealm> _cache = {};
|
||||
|
||||
Future<List<SnRealm>> listAvailableRealms() async {
|
||||
final resp = await _sn.client.get('/cgi/id/realms/me/available');
|
||||
final out = List<SnRealm>.from(
|
||||
resp.data?.map((e) => SnRealm.fromJson(e)) ?? [],
|
||||
);
|
||||
for (final realm in out) {
|
||||
_cache[realm.alias] = realm;
|
||||
_cache[realm.id.toString()] = realm;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
Future<SnRealm> getRealm(dynamic aliasOrId) async {
|
||||
if (_cache.containsKey(aliasOrId.toString())) {
|
||||
return _cache[aliasOrId.toString()]!;
|
||||
}
|
||||
final resp = await _sn.client.get('/cgi/id/realms/$aliasOrId');
|
||||
final out = SnRealm.fromJson(resp.data);
|
||||
_cache[out.alias] = out;
|
||||
_cache[out.id.toString()] = out;
|
||||
return out;
|
||||
}
|
||||
}
|
@ -11,7 +11,8 @@ class SnStickerProvider {
|
||||
|
||||
final Map<int, List<SnSticker>> stickersByPack = {};
|
||||
|
||||
List<SnSticker> get stickers => _cache.values.where((ele) => ele != null).cast<SnSticker>().toList();
|
||||
List<SnSticker> get stickers =>
|
||||
_cache.values.where((ele) => ele != null).cast<SnSticker>().toList();
|
||||
|
||||
SnStickerProvider(BuildContext context) {
|
||||
_sn = context.read<SnNetworkProvider>();
|
||||
@ -23,8 +24,18 @@ class SnStickerProvider {
|
||||
|
||||
void _cacheSticker(SnSticker sticker) {
|
||||
_cache['${sticker.pack.prefix}:${sticker.alias}'] = sticker;
|
||||
if (stickersByPack[sticker.pack.id] == null) stickersByPack[sticker.pack.id] = List.empty(growable: true);
|
||||
if (!stickersByPack[sticker.pack.id]!.contains(sticker)) stickersByPack[sticker.pack.id]!.add(sticker);
|
||||
if (stickersByPack[sticker.pack.id] == null) {
|
||||
stickersByPack[sticker.pack.id] = List.empty(growable: true);
|
||||
}
|
||||
if (!stickersByPack[sticker.pack.id]!.contains(sticker)) {
|
||||
stickersByPack[sticker.pack.id]!.add(sticker);
|
||||
}
|
||||
}
|
||||
|
||||
void putSticker(Iterable<SnSticker> sticker) {
|
||||
for (final ele in sticker) {
|
||||
_cacheSticker(ele);
|
||||
}
|
||||
}
|
||||
|
||||
Future<SnSticker?> lookupSticker(String alias) async {
|
||||
@ -46,26 +57,14 @@ class SnStickerProvider {
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<void> listStickerEagerly() async {
|
||||
var count = await listSticker();
|
||||
for (var page = 1; count > 0; count -= 10) {
|
||||
await listSticker(page: page);
|
||||
page++;
|
||||
}
|
||||
}
|
||||
|
||||
Future<int> listSticker({int page = 0}) async {
|
||||
Future<void> listSticker() async {
|
||||
try {
|
||||
final resp = await _sn.client.get('/cgi/uc/stickers', queryParameters: {
|
||||
'take': 10,
|
||||
'offset': page * 10,
|
||||
});
|
||||
final resp = await _sn.client.get('/cgi/uc/stickers');
|
||||
final data = resp.data;
|
||||
final stickers = List.from(data['data']).map((ele) => SnSticker.fromJson(ele));
|
||||
final stickers = List.from(data).map((ele) => SnSticker.fromJson(ele));
|
||||
for (final sticker in stickers) {
|
||||
_cacheSticker(sticker);
|
||||
}
|
||||
return data['count'] as int;
|
||||
} catch (err) {
|
||||
log('[Sticker] Failed to list stickers: $err');
|
||||
rethrow;
|
||||
|
146
lib/router.dart
146
lib/router.dart
@ -34,13 +34,15 @@ import 'package:surface/screens/realm/realm_detail.dart';
|
||||
import 'package:surface/screens/realm/realm_discovery.dart';
|
||||
import 'package:surface/screens/settings.dart';
|
||||
import 'package:surface/screens/sharing.dart';
|
||||
import 'package:surface/screens/stickers.dart';
|
||||
import 'package:surface/screens/stickers/pack_detail.dart';
|
||||
import 'package:surface/screens/wallet.dart';
|
||||
import 'package:surface/types/post.dart';
|
||||
import 'package:surface/widgets/about.dart';
|
||||
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
||||
|
||||
Widget _fadeThroughTransition(
|
||||
BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
|
||||
Widget _fadeThroughTransition(BuildContext context, Animation<double> animation,
|
||||
Animation<double> secondaryAnimation, Widget child) {
|
||||
return FadeThroughTransition(
|
||||
animation: animation,
|
||||
secondaryAnimation: secondaryAnimation,
|
||||
@ -82,13 +84,15 @@ final _appRoutes = [
|
||||
name: 'postSearch',
|
||||
builder: (context, state) => PostSearchScreen(
|
||||
initialTags: state.uri.queryParameters['tags']?.split(','),
|
||||
initialCategories: state.uri.queryParameters['categories']?.split(','),
|
||||
initialCategories:
|
||||
state.uri.queryParameters['categories']?.split(','),
|
||||
),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/publishers/:name',
|
||||
name: 'postPublisher',
|
||||
builder: (context, state) => PostPublisherScreen(name: state.pathParameters['name']!),
|
||||
builder: (context, state) =>
|
||||
PostPublisherScreen(name: state.pathParameters['name']!),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/:slug',
|
||||
@ -100,52 +104,56 @@ final _appRoutes = [
|
||||
),
|
||||
],
|
||||
),
|
||||
GoRoute(path: '/account', name: 'account', builder: (context, state) => const AccountScreen(), routes: [
|
||||
GoRoute(
|
||||
path: '/wallet',
|
||||
name: 'accountWallet',
|
||||
builder: (context, state) => const WalletScreen(),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/settings',
|
||||
name: 'accountSettings',
|
||||
builder: (context, state) => AccountSettingsScreen(),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/settings/factors',
|
||||
name: 'factorSettings',
|
||||
builder: (context, state) => FactorSettingsScreen(),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/profile/edit',
|
||||
name: 'accountProfileEdit',
|
||||
builder: (context, state) => ProfileEditScreen(),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/publishers',
|
||||
name: 'accountPublishers',
|
||||
builder: (context, state) => PublisherScreen(),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/publishers/new',
|
||||
name: 'accountPublisherNew',
|
||||
builder: (context, state) => AccountPublisherNewScreen(),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/publishers/edit/:name',
|
||||
name: 'accountPublisherEdit',
|
||||
builder: (context, state) => AccountPublisherEditScreen(
|
||||
name: state.pathParameters['name']!,
|
||||
),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/:name',
|
||||
name: 'accountProfilePage',
|
||||
pageBuilder: (context, state) => NoTransitionPage(
|
||||
child: UserScreen(name: state.pathParameters['name']!),
|
||||
),
|
||||
),
|
||||
]),
|
||||
GoRoute(
|
||||
path: '/account',
|
||||
name: 'account',
|
||||
builder: (context, state) => const AccountScreen(),
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: '/wallet',
|
||||
name: 'accountWallet',
|
||||
builder: (context, state) => const WalletScreen(),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/settings',
|
||||
name: 'accountSettings',
|
||||
builder: (context, state) => AccountSettingsScreen(),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/settings/factors',
|
||||
name: 'factorSettings',
|
||||
builder: (context, state) => FactorSettingsScreen(),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/profile/edit',
|
||||
name: 'accountProfileEdit',
|
||||
builder: (context, state) => ProfileEditScreen(),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/publishers',
|
||||
name: 'accountPublishers',
|
||||
builder: (context, state) => PublisherScreen(),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/publishers/new',
|
||||
name: 'accountPublisherNew',
|
||||
builder: (context, state) => AccountPublisherNewScreen(),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/publishers/edit/:name',
|
||||
name: 'accountPublisherEdit',
|
||||
builder: (context, state) => AccountPublisherEditScreen(
|
||||
name: state.pathParameters['name']!,
|
||||
),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/:name',
|
||||
name: 'accountProfilePage',
|
||||
pageBuilder: (context, state) => NoTransitionPage(
|
||||
child: UserScreen(name: state.pathParameters['name']!),
|
||||
),
|
||||
),
|
||||
]),
|
||||
GoRoute(
|
||||
path: '/chat',
|
||||
name: 'chat',
|
||||
@ -208,19 +216,39 @@ final _appRoutes = [
|
||||
GoRoute(
|
||||
path: '/:alias',
|
||||
name: 'realmDetail',
|
||||
builder: (context, state) => RealmDetailScreen(alias: state.pathParameters['alias']!),
|
||||
builder: (context, state) =>
|
||||
RealmDetailScreen(alias: state.pathParameters['alias']!),
|
||||
),
|
||||
],
|
||||
),
|
||||
GoRoute(path: '/news', name: 'news', builder: (context, state) => const NewsScreen(), routes: [
|
||||
GoRoute(
|
||||
path: '/:hash',
|
||||
name: 'newsDetail',
|
||||
builder: (context, state) => NewsDetailScreen(
|
||||
hash: state.pathParameters['hash']!,
|
||||
GoRoute(
|
||||
path: '/news',
|
||||
name: 'news',
|
||||
builder: (context, state) => const NewsScreen(),
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: '/:hash',
|
||||
name: 'newsDetail',
|
||||
builder: (context, state) => NewsDetailScreen(
|
||||
hash: state.pathParameters['hash']!,
|
||||
),
|
||||
),
|
||||
),
|
||||
]),
|
||||
],
|
||||
),
|
||||
GoRoute(
|
||||
path: '/stickers',
|
||||
name: 'stickers',
|
||||
builder: (context, state) => const StickerScreen(),
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: '/packs/:id',
|
||||
name: 'stickerPack',
|
||||
builder: (context, state) => StickerPackScreen(
|
||||
id: int.tryParse(state.pathParameters['id']!)!,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
GoRoute(
|
||||
path: '/album',
|
||||
name: 'album',
|
||||
|
@ -4,10 +4,10 @@ import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:hive_flutter/hive_flutter.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
import 'package:surface/providers/database.dart';
|
||||
import 'package:surface/providers/sn_network.dart';
|
||||
import 'package:surface/providers/userinfo.dart';
|
||||
import 'package:surface/providers/websocket.dart';
|
||||
@ -45,7 +45,8 @@ class AccountScreen extends StatelessWidget {
|
||||
? Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
AutoResizeUniversalImage(sn.getAttachmentUrl(ua.user!.banner), fit: BoxFit.cover),
|
||||
AutoResizeUniversalImage(sn.getAttachmentUrl(ua.user!.banner),
|
||||
fit: BoxFit.cover),
|
||||
Positioned(
|
||||
top: 0,
|
||||
left: 0,
|
||||
@ -79,7 +80,9 @@ class AccountScreen extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
child: ua.isAuthorized ? _AuthorizedAccountScreen() : _UnauthorizedAccountScreen(),
|
||||
child: ua.isAuthorized
|
||||
? _AuthorizedAccountScreen()
|
||||
: _UnauthorizedAccountScreen(),
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -115,12 +118,15 @@ class _AuthorizedAccountScreen extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.baseline,
|
||||
textBaseline: TextBaseline.alphabetic,
|
||||
children: [
|
||||
Text(ua.user!.nick).textStyle(Theme.of(context).textTheme.titleLarge!),
|
||||
Text(ua.user!.nick)
|
||||
.textStyle(Theme.of(context).textTheme.titleLarge!),
|
||||
const Gap(4),
|
||||
Text('@${ua.user!.name}').textStyle(Theme.of(context).textTheme.bodySmall!),
|
||||
Text('@${ua.user!.name}')
|
||||
.textStyle(Theme.of(context).textTheme.bodySmall!),
|
||||
],
|
||||
),
|
||||
Text(ua.user!.description).textStyle(Theme.of(context).textTheme.bodyMedium!),
|
||||
Text(ua.user!.description)
|
||||
.textStyle(Theme.of(context).textTheme.bodyMedium!),
|
||||
],
|
||||
),
|
||||
);
|
||||
@ -193,8 +199,7 @@ class _AuthorizedAccountScreen extends StatelessWidget {
|
||||
ua.logoutUser();
|
||||
final ws = context.read<WebSocketProvider>();
|
||||
ws.disconnect();
|
||||
await Hive.deleteFromDisk();
|
||||
await Hive.initFlutter();
|
||||
context.read<DatabaseProvider>().removeDatabase();
|
||||
},
|
||||
),
|
||||
],
|
||||
@ -220,7 +225,9 @@ class _UnauthorizedAccountScreen extends StatelessWidget {
|
||||
child: Icon(Symbols.waving_hand, size: 28),
|
||||
),
|
||||
const Gap(8),
|
||||
Text('accountIntroTitle').tr().textStyle(Theme.of(context).textTheme.titleLarge!),
|
||||
Text('accountIntroTitle')
|
||||
.tr()
|
||||
.textStyle(Theme.of(context).textTheme.titleLarge!),
|
||||
Text('accountIntroSubtitle').tr(),
|
||||
],
|
||||
).padding(all: 20),
|
||||
|
@ -2,6 +2,9 @@ import 'package:dismissible_page/dismissible_page.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
import 'package:surface/providers/sn_network.dart';
|
||||
@ -27,9 +30,23 @@ class _AlbumScreenState extends State<AlbumScreen> {
|
||||
bool _isBusy = false;
|
||||
int? _totalCount;
|
||||
|
||||
SnAttachmentBilling? _billing;
|
||||
|
||||
final List<SnAttachment> _attachments = List.empty(growable: true);
|
||||
final List<String> _heroTags = List.empty(growable: true);
|
||||
|
||||
Future<void> _fetchBillingStatus() async {
|
||||
try {
|
||||
final sn = context.read<SnNetworkProvider>();
|
||||
final resp = await sn.client.get('/cgi/uc/billing');
|
||||
final out = SnAttachmentBilling.fromJson(resp.data);
|
||||
setState(() => _billing = out);
|
||||
} catch (err) {
|
||||
if (!mounted) return;
|
||||
context.showErrorDialog(err);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _fetchAttachments() async {
|
||||
setState(() => _isBusy = true);
|
||||
|
||||
@ -62,6 +79,7 @@ class _AlbumScreenState extends State<AlbumScreen> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_fetchBillingStatus();
|
||||
_fetchAttachments();
|
||||
_scrollController.addListener(() {
|
||||
if (_scrollController.position.atEdge) {
|
||||
@ -91,6 +109,48 @@ class _AlbumScreenState extends State<AlbumScreen> {
|
||||
leading: AutoAppBarLeading(),
|
||||
title: Text('screenAlbum').tr(),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: Card(
|
||||
child: Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 80,
|
||||
height: 80,
|
||||
child: CircularProgressIndicator(
|
||||
value: _billing?.includedRatio ?? 0,
|
||||
strokeWidth: 8,
|
||||
backgroundColor: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||
),
|
||||
).padding(all: 12),
|
||||
const Gap(24),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('attachmentBillingUploaded').tr().bold(),
|
||||
Text(
|
||||
(_billing?.currentBytes ?? 0).formatBytes(decimals: 4),
|
||||
style: GoogleFonts.robotoMono(),
|
||||
),
|
||||
Text('attachmentBillingDiscount').tr().bold(),
|
||||
Text(
|
||||
'${(_billing?.discountFileSize ?? 0).formatBytes(decimals: 2)} · ${((_billing?.includedRatio ?? 0) * 100).toStringAsFixed(2)}%',
|
||||
style: GoogleFonts.robotoMono(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Tooltip(
|
||||
message: 'attachmentBillingHint'.tr(),
|
||||
child: IconButton(
|
||||
icon: const Icon(Symbols.info),
|
||||
onPressed: () {},
|
||||
),
|
||||
),
|
||||
],
|
||||
).padding(horizontal: 24, vertical: 8),
|
||||
),
|
||||
),
|
||||
SliverMasonryGrid.extent(
|
||||
childCount: _attachments.length,
|
||||
maxCrossAxisExtent: 320,
|
||||
|
@ -5,21 +5,23 @@ import 'package:gap/gap.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:responsive_framework/responsive_framework.dart';
|
||||
import 'package:surface/providers/channel.dart';
|
||||
import 'package:surface/providers/sn_network.dart';
|
||||
import 'package:surface/providers/user_directory.dart';
|
||||
import 'package:surface/providers/userinfo.dart';
|
||||
import 'package:surface/screens/chat/room.dart';
|
||||
import 'package:surface/types/chat.dart';
|
||||
import 'package:surface/widgets/account/account_image.dart';
|
||||
import 'package:surface/widgets/account/account_select.dart';
|
||||
import 'package:surface/widgets/app_bar_leading.dart';
|
||||
import 'package:surface/widgets/dialog.dart';
|
||||
import 'package:surface/widgets/loading_indicator.dart';
|
||||
import 'package:surface/widgets/navigation/app_background.dart';
|
||||
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
||||
import 'package:surface/widgets/unauthorized_hint.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
import '../providers/sn_network.dart';
|
||||
import '../providers/userinfo.dart';
|
||||
|
||||
class ChatScreen extends StatefulWidget {
|
||||
const ChatScreen({super.key});
|
||||
|
||||
@ -34,8 +36,18 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
|
||||
List<SnChannel>? _channels;
|
||||
Map<int, SnChatMessage>? _lastMessages;
|
||||
Map<int, int>? _unreadCounts;
|
||||
|
||||
void _refreshChannels() {
|
||||
Future<void> _fetchWhatsNew() async {
|
||||
final sn = context.read<SnNetworkProvider>();
|
||||
final resp = await sn.client.get('/cgi/im/whats-new');
|
||||
final List<dynamic> out = resp.data;
|
||||
setState(() {
|
||||
_unreadCounts = {for (var v in out) v['channel_id']: v['count']};
|
||||
});
|
||||
}
|
||||
|
||||
void _refreshChannels({bool noRemote = false}) {
|
||||
final ua = context.read<UserProvider>();
|
||||
if (!ua.isAuthorized) {
|
||||
setState(() => _isBusy = false);
|
||||
@ -43,12 +55,15 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
}
|
||||
|
||||
final chan = context.read<ChatChannelProvider>();
|
||||
chan.fetchChannels().listen((channels) async {
|
||||
chan.fetchChannels(noRemote: noRemote).listen((channels) async {
|
||||
final lastMessages = await chan.getLastMessages(channels);
|
||||
_lastMessages = {for (final val in lastMessages) val.channelId: val};
|
||||
channels.sort((a, b) {
|
||||
if (_lastMessages!.containsKey(a.id) && _lastMessages!.containsKey(b.id)) {
|
||||
return _lastMessages![b.id]!.createdAt.compareTo(_lastMessages![a.id]!.createdAt);
|
||||
if (_lastMessages!.containsKey(a.id) &&
|
||||
_lastMessages!.containsKey(b.id)) {
|
||||
return _lastMessages![b.id]!
|
||||
.createdAt
|
||||
.compareTo(_lastMessages![a.id]!.createdAt);
|
||||
}
|
||||
if (_lastMessages!.containsKey(a.id)) return -1;
|
||||
if (_lastMessages!.containsKey(b.id)) return 1;
|
||||
@ -86,7 +101,8 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
void _newDirectMessage() async {
|
||||
final user = await showModalBottomSheet(
|
||||
context: context,
|
||||
builder: (context) => AccountSelect(title: 'channelNewDirectMessage'.tr()),
|
||||
builder: (context) =>
|
||||
AccountSelect(title: 'channelNewDirectMessage'.tr()),
|
||||
);
|
||||
if (user == null) return;
|
||||
if (!mounted) return;
|
||||
@ -98,7 +114,8 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
await sn.client.post('/cgi/im/channels/global/dm', data: {
|
||||
'alias': uuid.v4().replaceAll('-', '').substring(0, 12),
|
||||
'name': 'DM',
|
||||
'description': 'A direct message channel between @${ua.user?.name} and @${user.name}',
|
||||
'description':
|
||||
'A direct message channel between @${ua.user?.name} and @${user.name}',
|
||||
'related_user': user.id,
|
||||
});
|
||||
_fabKey.currentState!.toggle();
|
||||
@ -109,10 +126,13 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
}
|
||||
}
|
||||
|
||||
SnChannel? _focusChannel;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_refreshChannels();
|
||||
_fetchWhatsNew();
|
||||
}
|
||||
|
||||
@override
|
||||
@ -132,7 +152,10 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
return AppScaffold(
|
||||
final doExpand = ResponsiveBreakpoints.of(context).largerOrEqualTo(DESKTOP);
|
||||
|
||||
final chatList = AppScaffold(
|
||||
noBackground: doExpand,
|
||||
appBar: AppBar(
|
||||
leading: AutoAppBarLeading(),
|
||||
title: Text('screenChat').tr(),
|
||||
@ -144,20 +167,27 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
type: ExpandableFabType.up,
|
||||
childrenAnimation: ExpandableFabAnimation.none,
|
||||
overlayStyle: ExpandableFabOverlayStyle(
|
||||
color: Theme.of(context).colorScheme.surface.withAlpha((255 * 0.5).round()),
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.surface
|
||||
.withAlpha((255 * 0.5).round()),
|
||||
),
|
||||
openButtonBuilder: RotateFloatingActionButtonBuilder(
|
||||
child: const Icon(Symbols.add, size: 28),
|
||||
fabSize: ExpandableFabSize.regular,
|
||||
foregroundColor: Theme.of(context).floatingActionButtonTheme.foregroundColor,
|
||||
backgroundColor: Theme.of(context).floatingActionButtonTheme.backgroundColor,
|
||||
foregroundColor:
|
||||
Theme.of(context).floatingActionButtonTheme.foregroundColor,
|
||||
backgroundColor:
|
||||
Theme.of(context).floatingActionButtonTheme.backgroundColor,
|
||||
shape: const CircleBorder(),
|
||||
),
|
||||
closeButtonBuilder: DefaultFloatingActionButtonBuilder(
|
||||
child: const Icon(Symbols.close, size: 28),
|
||||
fabSize: ExpandableFabSize.regular,
|
||||
foregroundColor: Theme.of(context).floatingActionButtonTheme.foregroundColor,
|
||||
backgroundColor: Theme.of(context).floatingActionButtonTheme.backgroundColor,
|
||||
foregroundColor:
|
||||
Theme.of(context).floatingActionButtonTheme.foregroundColor,
|
||||
backgroundColor:
|
||||
Theme.of(context).floatingActionButtonTheme.backgroundColor,
|
||||
shape: const CircleBorder(),
|
||||
),
|
||||
children: [
|
||||
@ -200,7 +230,10 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
context: context,
|
||||
removeTop: true,
|
||||
child: RefreshIndicator(
|
||||
onRefresh: () => Future.sync(() => _refreshChannels()),
|
||||
onRefresh: () => Future.wait([
|
||||
Future.sync(() => _refreshChannels()),
|
||||
_fetchWhatsNew(),
|
||||
]),
|
||||
child: ListView.builder(
|
||||
itemCount: _channels?.length ?? 0,
|
||||
itemBuilder: (context, idx) {
|
||||
@ -208,13 +241,29 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
final lastMessage = _lastMessages?[channel.id];
|
||||
|
||||
if (channel.type == 1) {
|
||||
final otherMember = channel.members?.cast<SnChannelMember?>().firstWhere(
|
||||
(ele) => ele?.accountId != ua.user?.id,
|
||||
orElse: () => null,
|
||||
);
|
||||
final otherMember =
|
||||
channel.members?.cast<SnChannelMember?>().firstWhere(
|
||||
(ele) => ele?.accountId != ua.user?.id,
|
||||
orElse: () => null,
|
||||
);
|
||||
|
||||
return ListTile(
|
||||
title: Text(ud.getAccountFromCache(otherMember?.accountId)?.nick ?? channel.name),
|
||||
title: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(ud
|
||||
.getAccountFromCache(
|
||||
otherMember?.accountId)
|
||||
?.nick ??
|
||||
channel.name),
|
||||
),
|
||||
const Gap(8),
|
||||
if (_unreadCounts?[channel.id] != null)
|
||||
Badge(
|
||||
label: Text('${_unreadCounts![channel.id]}'),
|
||||
),
|
||||
],
|
||||
),
|
||||
subtitle: lastMessage != null
|
||||
? Text(
|
||||
'${ud.getAccountFromCache(lastMessage.sender.accountId)?.nick}: ${lastMessage.body['text'] ?? 'Unable preview'}',
|
||||
@ -222,17 +271,22 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
overflow: TextOverflow.ellipsis,
|
||||
)
|
||||
: Text(
|
||||
'channelDirectMessageDescription'.tr(args: [
|
||||
'@${ud.getAccountFromCache(otherMember?.accountId)?.name}',
|
||||
]),
|
||||
channel.description,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
contentPadding:
|
||||
const EdgeInsets.symmetric(horizontal: 16),
|
||||
leading: AccountImage(
|
||||
content: ud.getAccountFromCache(otherMember?.accountId)?.avatar,
|
||||
content: ud
|
||||
.getAccountFromCache(otherMember?.accountId)
|
||||
?.avatar,
|
||||
),
|
||||
onTap: () {
|
||||
if (doExpand) {
|
||||
setState(() => _focusChannel = channel);
|
||||
return;
|
||||
}
|
||||
GoRouter.of(context).pushNamed(
|
||||
'chatRoom',
|
||||
pathParameters: {
|
||||
@ -240,14 +294,23 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
'alias': channel.alias,
|
||||
},
|
||||
).then((value) {
|
||||
if (mounted) _refreshChannels();
|
||||
if (mounted) _refreshChannels(noRemote: true);
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return ListTile(
|
||||
title: Text(channel.name),
|
||||
title: Row(
|
||||
children: [
|
||||
Expanded(child: Text(channel.name)),
|
||||
const Gap(8),
|
||||
if (_unreadCounts?[channel.id] != null)
|
||||
Badge(
|
||||
label: Text('${_unreadCounts![channel.id]}'),
|
||||
),
|
||||
],
|
||||
),
|
||||
subtitle: lastMessage != null
|
||||
? Text(
|
||||
'${ud.getAccountFromCache(lastMessage.sender.accountId)?.nick}: ${lastMessage.body['text'] ?? 'Unable preview'}',
|
||||
@ -259,12 +322,17 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
contentPadding:
|
||||
const EdgeInsets.symmetric(horizontal: 16),
|
||||
leading: AccountImage(
|
||||
content: null,
|
||||
fallbackWidget: const Icon(Symbols.chat, size: 20),
|
||||
),
|
||||
onTap: () {
|
||||
if (doExpand) {
|
||||
setState(() => _focusChannel = channel);
|
||||
return;
|
||||
}
|
||||
GoRouter.of(context).pushNamed(
|
||||
'chatRoom',
|
||||
pathParameters: {
|
||||
@ -272,7 +340,7 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
'alias': channel.alias,
|
||||
},
|
||||
).then((value) {
|
||||
if (value == true) _refreshChannels();
|
||||
if (value == true) _refreshChannels(noRemote: true);
|
||||
});
|
||||
},
|
||||
);
|
||||
@ -284,5 +352,27 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
if (doExpand) {
|
||||
return AppBackground(
|
||||
isRoot: true,
|
||||
child: Row(
|
||||
children: [
|
||||
SizedBox(width: 340, child: chatList),
|
||||
const VerticalDivider(width: 1),
|
||||
if (_focusChannel != null)
|
||||
Expanded(
|
||||
child: ChatRoomScreen(
|
||||
key: ValueKey(_focusChannel!.id),
|
||||
scope: _focusChannel!.realm?.alias ?? 'global',
|
||||
alias: _focusChannel!.alias,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return chatList;
|
||||
}
|
||||
}
|
||||
|
@ -104,7 +104,7 @@ class _ChannelDetailScreenState extends State<ChannelDetailScreen> {
|
||||
try {
|
||||
final sn = context.read<SnNetworkProvider>();
|
||||
await sn.client.delete(
|
||||
'/cgi/im/channels/${_channel!.realm?.alias ?? 'global'}/${_channel!.id}/members/me',
|
||||
'/cgi/im/channels/${_channel!.realm?.alias ?? 'global'}/${_channel!.alias}/me',
|
||||
);
|
||||
if (!mounted) return;
|
||||
Navigator.pop(context, false);
|
||||
|
@ -95,6 +95,10 @@ class _ChatManageScreenState extends State<ChatManageScreen> {
|
||||
'description': _descriptionController.text,
|
||||
'is_public': _isPublic,
|
||||
'is_community': _isCommunity,
|
||||
if (_editingChannel != null && _belongToRealm == null)
|
||||
'new_belongs_realm': 'global'
|
||||
else if (_editingChannel != null && _belongToRealm?.id != _editingChannel?.realm?.id)
|
||||
'new_belongs_realm': _belongToRealm!.alias,
|
||||
};
|
||||
|
||||
try {
|
||||
@ -171,7 +175,6 @@ class _ChatManageScreenState extends State<ChatManageScreen> {
|
||||
items: [
|
||||
...(_realms?.map(
|
||||
(SnRealm item) => DropdownMenuItem<SnRealm>(
|
||||
enabled: _editingChannel == null || _editingChannel?.realmId == item.id,
|
||||
value: item,
|
||||
child: Row(
|
||||
children: [
|
||||
@ -204,7 +207,6 @@ class _ChatManageScreenState extends State<ChatManageScreen> {
|
||||
) ??
|
||||
[]),
|
||||
DropdownMenuItem<SnRealm>(
|
||||
enabled: _editingChannel == null,
|
||||
value: null,
|
||||
child: Row(
|
||||
children: [
|
||||
|
@ -39,7 +39,8 @@ class ChatRoomScreen extends StatefulWidget {
|
||||
final String alias;
|
||||
final ChatRoomScreenExtra? extra;
|
||||
|
||||
const ChatRoomScreen({super.key, required this.scope, required this.alias, this.extra});
|
||||
const ChatRoomScreen(
|
||||
{super.key, required this.scope, required this.alias, this.extra});
|
||||
|
||||
@override
|
||||
State<ChatRoomScreen> createState() => _ChatRoomScreenState();
|
||||
@ -58,6 +59,7 @@ class _ChatRoomScreenState extends State<ChatRoomScreen> {
|
||||
|
||||
StreamSubscription? _wsSubscription;
|
||||
|
||||
// TODO fetch user identity and ask them to join the channel or not
|
||||
Future<void> _fetchChannel() async {
|
||||
setState(() => _isBusy = true);
|
||||
|
||||
@ -191,10 +193,12 @@ class _ChatRoomScreenState extends State<ChatRoomScreen> {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
log('[ChatInput] Setting initial text and attachments...');
|
||||
if (widget.extra!.initialText != null) {
|
||||
_inputGlobalKey.currentState?.setInitialText(widget.extra!.initialText!);
|
||||
_inputGlobalKey.currentState
|
||||
?.setInitialText(widget.extra!.initialText!);
|
||||
}
|
||||
if (widget.extra!.initialAttachments != null) {
|
||||
_inputGlobalKey.currentState?.setInitialAttachments(widget.extra!.initialAttachments!);
|
||||
_inputGlobalKey.currentState
|
||||
?.setInitialAttachments(widget.extra!.initialAttachments!);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -240,12 +244,15 @@ class _ChatRoomScreenState extends State<ChatRoomScreen> {
|
||||
appBar: AppBar(
|
||||
title: Text(
|
||||
_channel?.type == 1
|
||||
? ud.getAccountFromCache(_otherMember?.accountId)?.nick ?? _channel!.name
|
||||
? ud.getAccountFromCache(_otherMember?.accountId)?.nick ??
|
||||
_channel!.name
|
||||
: _channel?.name ?? 'loading'.tr(),
|
||||
),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: _ongoingCall == null ? const Icon(Symbols.call) : const Icon(Symbols.call_end),
|
||||
icon: _ongoingCall == null
|
||||
? const Icon(Symbols.call)
|
||||
: const Icon(Symbols.call_end),
|
||||
onPressed: _isCalling
|
||||
? null
|
||||
: _ongoingCall == null
|
||||
@ -295,9 +302,9 @@ class _ChatRoomScreenState extends State<ChatRoomScreen> {
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
.height(_ongoingCall != null ? 54 : 0, animate: true)
|
||||
.animate(const Duration(milliseconds: 300), Curves.fastLinearToSlowEaseIn),
|
||||
).height(_ongoingCall != null ? 54 : 0, animate: true).animate(
|
||||
const Duration(milliseconds: 300),
|
||||
Curves.fastLinearToSlowEaseIn),
|
||||
if (_messageController.isPending)
|
||||
Expanded(
|
||||
child: const CircularProgressIndicator().center(),
|
||||
@ -315,6 +322,7 @@ class _ChatRoomScreenState extends State<ChatRoomScreen> {
|
||||
},
|
||||
itemBuilder: (context, idx) {
|
||||
final message = _messageController.messages[idx];
|
||||
_messageController.readEvent(message.id);
|
||||
|
||||
bool canMerge = false, canMergePrevious = false;
|
||||
if (idx > 0) {
|
||||
@ -336,7 +344,8 @@ class _ChatRoomScreenState extends State<ChatRoomScreen> {
|
||||
data: message,
|
||||
isMerged: canMerge,
|
||||
hasMerged: canMergePrevious,
|
||||
isPending: _messageController.unconfirmedMessages.contains(message.uuid),
|
||||
isPending: _messageController.unconfirmedMessages
|
||||
.contains(message.uuid),
|
||||
onReply: (value) {
|
||||
_inputGlobalKey.currentState?.setReply(value);
|
||||
},
|
||||
|
@ -1,3 +1,4 @@
|
||||
import 'package:dropdown_button2/dropdown_button2.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_expandable_fab/flutter_expandable_fab.dart';
|
||||
@ -8,7 +9,10 @@ import 'package:provider/provider.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
import 'package:surface/providers/post.dart';
|
||||
import 'package:surface/providers/sn_network.dart';
|
||||
import 'package:surface/providers/sn_realm.dart';
|
||||
import 'package:surface/types/post.dart';
|
||||
import 'package:surface/types/realm.dart';
|
||||
import 'package:surface/widgets/account/account_image.dart';
|
||||
import 'package:surface/widgets/app_bar_leading.dart';
|
||||
import 'package:surface/widgets/dialog.dart';
|
||||
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
||||
@ -35,61 +39,54 @@ class ExploreScreen extends StatefulWidget {
|
||||
State<ExploreScreen> createState() => _ExploreScreenState();
|
||||
}
|
||||
|
||||
class _ExploreScreenState extends State<ExploreScreen> {
|
||||
// You know what? I'm not going to make this a global variable.
|
||||
// Cuz the global key make the selected category not update to child widget when the category is changed.
|
||||
SnPostCategory? _selectedCategory;
|
||||
|
||||
class _ExploreScreenState extends State<ExploreScreen>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late final TabController _tabController =
|
||||
TabController(length: 4, vsync: this);
|
||||
|
||||
final _fabKey = GlobalKey<ExpandableFabState>();
|
||||
final _listKeys = List.generate(4, (_) => GlobalKey<_PostListWidgetState>());
|
||||
|
||||
bool _isBusy = true;
|
||||
|
||||
final List<SnPost> _posts = List.empty(growable: true);
|
||||
final List<SnPostCategory> _categories = List.empty(growable: true);
|
||||
int? _postCount;
|
||||
|
||||
String? _selectedCategory;
|
||||
|
||||
Future<void> _fetchCategories() async {
|
||||
_categories.clear();
|
||||
try {
|
||||
final sn = context.read<SnNetworkProvider>();
|
||||
final resp = await sn.client.get('/cgi/co/categories?take=100');
|
||||
_categories.addAll(resp.data.map((e) => SnPostCategory.fromJson(e)).cast<SnPostCategory>() ?? []);
|
||||
setState(() {
|
||||
_categories.addAll(resp.data
|
||||
.map((e) => SnPostCategory.fromJson(e))
|
||||
.cast<SnPostCategory>() ??
|
||||
[]);
|
||||
});
|
||||
} catch (err) {
|
||||
if (!mounted) return;
|
||||
context.showErrorDialog(err);
|
||||
if (mounted) context.showErrorDialog(err);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _fetchPosts() async {
|
||||
if (_postCount != null && _posts.length >= _postCount!) return;
|
||||
|
||||
setState(() => _isBusy = true);
|
||||
|
||||
final pt = context.read<SnPostContentProvider>();
|
||||
final result = await pt.listPosts(
|
||||
take: 10,
|
||||
offset: _posts.length,
|
||||
categories: _selectedCategory != null ? [_selectedCategory!] : null,
|
||||
);
|
||||
final out = result.$1;
|
||||
|
||||
if (!mounted) return;
|
||||
|
||||
_postCount = result.$2;
|
||||
_posts.addAll(out);
|
||||
|
||||
if (mounted) setState(() => _isBusy = false);
|
||||
}
|
||||
|
||||
Future<void> _refreshPosts() {
|
||||
_postCount = null;
|
||||
_posts.clear();
|
||||
return _fetchPosts();
|
||||
void _clearFilter() {
|
||||
_selectedCategory = null;
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_fetchPosts();
|
||||
_fetchCategories();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_tabController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> refreshPosts() async {
|
||||
await _listKeys[_tabController.index].currentState?.refreshPosts();
|
||||
}
|
||||
|
||||
@override
|
||||
@ -102,20 +99,27 @@ class _ExploreScreenState extends State<ExploreScreen> {
|
||||
type: ExpandableFabType.up,
|
||||
childrenAnimation: ExpandableFabAnimation.none,
|
||||
overlayStyle: ExpandableFabOverlayStyle(
|
||||
color: Theme.of(context).colorScheme.surface.withAlpha((255 * 0.5).round()),
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.surface
|
||||
.withAlpha((255 * 0.5).round()),
|
||||
),
|
||||
openButtonBuilder: RotateFloatingActionButtonBuilder(
|
||||
child: const Icon(Symbols.add, size: 28),
|
||||
fabSize: ExpandableFabSize.regular,
|
||||
foregroundColor: Theme.of(context).floatingActionButtonTheme.foregroundColor,
|
||||
backgroundColor: Theme.of(context).floatingActionButtonTheme.backgroundColor,
|
||||
foregroundColor:
|
||||
Theme.of(context).floatingActionButtonTheme.foregroundColor,
|
||||
backgroundColor:
|
||||
Theme.of(context).floatingActionButtonTheme.backgroundColor,
|
||||
shape: const CircleBorder(),
|
||||
),
|
||||
closeButtonBuilder: DefaultFloatingActionButtonBuilder(
|
||||
child: const Icon(Symbols.close, size: 28),
|
||||
fabSize: ExpandableFabSize.regular,
|
||||
foregroundColor: Theme.of(context).floatingActionButtonTheme.foregroundColor,
|
||||
backgroundColor: Theme.of(context).floatingActionButtonTheme.backgroundColor,
|
||||
foregroundColor:
|
||||
Theme.of(context).floatingActionButtonTheme.foregroundColor,
|
||||
backgroundColor:
|
||||
Theme.of(context).floatingActionButtonTheme.backgroundColor,
|
||||
shape: const CircleBorder(),
|
||||
),
|
||||
children: [
|
||||
@ -131,7 +135,7 @@ class _ExploreScreenState extends State<ExploreScreen> {
|
||||
'mode': 'stories',
|
||||
}).then((value) {
|
||||
if (value == true) {
|
||||
_refreshPosts();
|
||||
refreshPosts();
|
||||
}
|
||||
});
|
||||
_fabKey.currentState!.toggle();
|
||||
@ -152,7 +156,7 @@ class _ExploreScreenState extends State<ExploreScreen> {
|
||||
'mode': 'articles',
|
||||
}).then((value) {
|
||||
if (value == true) {
|
||||
_refreshPosts();
|
||||
refreshPosts();
|
||||
}
|
||||
});
|
||||
_fabKey.currentState!.toggle();
|
||||
@ -173,7 +177,7 @@ class _ExploreScreenState extends State<ExploreScreen> {
|
||||
'mode': 'questions',
|
||||
}).then((value) {
|
||||
if (value == true) {
|
||||
_refreshPosts();
|
||||
refreshPosts();
|
||||
}
|
||||
});
|
||||
_fabKey.currentState!.toggle();
|
||||
@ -194,7 +198,7 @@ class _ExploreScreenState extends State<ExploreScreen> {
|
||||
'mode': 'videos',
|
||||
}).then((value) {
|
||||
if (value == true) {
|
||||
_refreshPosts();
|
||||
refreshPosts();
|
||||
}
|
||||
});
|
||||
_fabKey.currentState!.toggle();
|
||||
@ -205,74 +209,157 @@ class _ExploreScreenState extends State<ExploreScreen> {
|
||||
),
|
||||
],
|
||||
),
|
||||
body: RefreshIndicator(
|
||||
displacement: 40 + MediaQuery.of(context).padding.top,
|
||||
onRefresh: () => _refreshPosts(),
|
||||
child: CustomScrollView(
|
||||
slivers: [
|
||||
SliverAppBar(
|
||||
leading: AutoAppBarLeading(),
|
||||
title: Text('screenExplore').tr(),
|
||||
floating: true,
|
||||
snap: true,
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Symbols.search),
|
||||
onPressed: () {
|
||||
GoRouter.of(context).pushNamed('postSearch');
|
||||
},
|
||||
),
|
||||
const Gap(8),
|
||||
],
|
||||
bottom: PreferredSize(
|
||||
preferredSize: const Size.fromHeight(50),
|
||||
child: SizedBox(
|
||||
height: 50,
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
padding: const EdgeInsets.only(left: 8, right: 8, bottom: 12),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: _categories.map((ele) {
|
||||
return StyledWidget(ChoiceChip(
|
||||
avatar: Icon(kCategoryIcons[ele.alias] ?? Symbols.question_mark),
|
||||
label: Text(
|
||||
'postCategory${ele.alias.capitalize()}'.trExists()
|
||||
? 'postCategory${ele.alias.capitalize()}'.tr()
|
||||
: ele.name,
|
||||
),
|
||||
selected: _selectedCategory == ele.alias,
|
||||
onSelected: (value) {
|
||||
_selectedCategory = value ? ele.alias : null;
|
||||
_refreshPosts();
|
||||
},
|
||||
)).padding(horizontal: 4);
|
||||
}).toList(),
|
||||
),
|
||||
body: NestedScrollView(
|
||||
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
|
||||
return [
|
||||
SliverOverlapAbsorber(
|
||||
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
||||
sliver: SliverAppBar(
|
||||
leading: AutoAppBarLeading(),
|
||||
title: Text('screenExplore').tr(),
|
||||
floating: true,
|
||||
snap: true,
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Symbols.category),
|
||||
onPressed: () {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
builder: (context) => _PostCategoryPickerPopup(
|
||||
categories: _categories,
|
||||
selected: _selectedCategory,
|
||||
),
|
||||
).then((value) {
|
||||
if (value != null && context.mounted) {
|
||||
_selectedCategory = value == false ? null : value;
|
||||
refreshPosts();
|
||||
}
|
||||
});
|
||||
},
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Symbols.search),
|
||||
onPressed: () {
|
||||
GoRouter.of(context).pushNamed('postSearch');
|
||||
},
|
||||
),
|
||||
const Gap(8),
|
||||
],
|
||||
bottom: TabBar(
|
||||
controller: _tabController,
|
||||
tabs: [
|
||||
Tab(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Icon(Symbols.globe,
|
||||
size: 20,
|
||||
color: Theme.of(context)
|
||||
.appBarTheme
|
||||
.foregroundColor),
|
||||
const Gap(8),
|
||||
Flexible(
|
||||
child: Text(
|
||||
'postChannelGlobal',
|
||||
maxLines: 1,
|
||||
).tr().textColor(
|
||||
Theme.of(context).appBarTheme.foregroundColor),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Tab(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Icon(Symbols.group,
|
||||
size: 20,
|
||||
color: Theme.of(context)
|
||||
.appBarTheme
|
||||
.foregroundColor),
|
||||
const Gap(8),
|
||||
Flexible(
|
||||
child: Text(
|
||||
'postChannelFriends',
|
||||
maxLines: 1,
|
||||
textAlign: TextAlign.center,
|
||||
).tr().textColor(
|
||||
Theme.of(context).appBarTheme.foregroundColor),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Tab(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Icon(Symbols.subscriptions,
|
||||
size: 20,
|
||||
color: Theme.of(context)
|
||||
.appBarTheme
|
||||
.foregroundColor),
|
||||
const Gap(8),
|
||||
Flexible(
|
||||
child: Text(
|
||||
'postChannelFollowing',
|
||||
maxLines: 1,
|
||||
).tr().textColor(
|
||||
Theme.of(context).appBarTheme.foregroundColor),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Tab(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Icon(Symbols.workspaces,
|
||||
size: 20,
|
||||
color: Theme.of(context)
|
||||
.appBarTheme
|
||||
.foregroundColor),
|
||||
const Gap(8),
|
||||
Flexible(
|
||||
child: Text(
|
||||
'postChannelRealm',
|
||||
maxLines: 1,
|
||||
).tr().textColor(
|
||||
Theme.of(context).appBarTheme.foregroundColor),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SliverGap(12),
|
||||
SliverInfiniteList(
|
||||
itemCount: _posts.length,
|
||||
isLoading: _isBusy,
|
||||
centerLoading: true,
|
||||
hasReachedMax: _postCount != null && _posts.length >= _postCount!,
|
||||
onFetchData: _fetchPosts,
|
||||
itemBuilder: (context, idx) {
|
||||
return OpenablePostItem(
|
||||
data: _posts[idx],
|
||||
maxWidth: 640,
|
||||
onChanged: (data) {
|
||||
setState(() => _posts[idx] = data);
|
||||
},
|
||||
onDeleted: () {
|
||||
_refreshPosts();
|
||||
},
|
||||
);
|
||||
},
|
||||
separatorBuilder: (_, __) => const Gap(8),
|
||||
];
|
||||
},
|
||||
body: TabBarView(
|
||||
controller: _tabController,
|
||||
children: [
|
||||
_PostListWidget(
|
||||
key: _listKeys[0],
|
||||
onClearFilter: _clearFilter,
|
||||
),
|
||||
_PostListWidget(
|
||||
key: _listKeys[1],
|
||||
channel: 'friends',
|
||||
onClearFilter: _clearFilter,
|
||||
),
|
||||
_PostListWidget(
|
||||
key: _listKeys[2],
|
||||
channel: 'following',
|
||||
onClearFilter: _clearFilter,
|
||||
),
|
||||
_PostListWidget(
|
||||
key: _listKeys[3],
|
||||
withRealm: true,
|
||||
onClearFilter: _clearFilter,
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -280,3 +367,261 @@ class _ExploreScreenState extends State<ExploreScreen> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _PostListWidget extends StatefulWidget {
|
||||
final String? channel;
|
||||
final bool withRealm;
|
||||
final Function onClearFilter;
|
||||
|
||||
const _PostListWidget(
|
||||
{super.key,
|
||||
this.channel,
|
||||
this.withRealm = false,
|
||||
required this.onClearFilter});
|
||||
|
||||
@override
|
||||
State<_PostListWidget> createState() => _PostListWidgetState();
|
||||
}
|
||||
|
||||
class _PostListWidgetState extends State<_PostListWidget> {
|
||||
bool _isBusy = false;
|
||||
|
||||
final List<SnPost> _posts = List.empty(growable: true);
|
||||
final List<SnRealm> _realms = List.empty(growable: true);
|
||||
SnRealm? _selectedRealm;
|
||||
int? _postCount;
|
||||
|
||||
Future<void> _fetchRealms() async {
|
||||
try {
|
||||
final rels = context.read<SnRealmProvider>();
|
||||
final out = await rels.listAvailableRealms();
|
||||
setState(() {
|
||||
_realms.addAll(out);
|
||||
_selectedRealm = out.firstOrNull;
|
||||
});
|
||||
} catch (err) {
|
||||
if (!mounted) return;
|
||||
context.showErrorDialog(err);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _fetchPosts() async {
|
||||
if (_postCount != null && _posts.length >= _postCount!) return;
|
||||
|
||||
setState(() => _isBusy = true);
|
||||
|
||||
final pt = context.read<SnPostContentProvider>();
|
||||
final result = await pt.listPosts(
|
||||
take: 10,
|
||||
offset: _posts.length,
|
||||
categories: _selectedCategory != null ? [_selectedCategory!.alias] : null,
|
||||
channel: widget.channel,
|
||||
realm: _selectedRealm?.alias,
|
||||
);
|
||||
final out = result.$1;
|
||||
|
||||
if (!mounted) return;
|
||||
|
||||
_postCount = result.$2;
|
||||
_posts.addAll(out);
|
||||
|
||||
if (mounted) setState(() => _isBusy = false);
|
||||
}
|
||||
|
||||
Future<void> refreshPosts() {
|
||||
_postCount = null;
|
||||
_posts.clear();
|
||||
return _fetchPosts();
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
if (widget.withRealm) {
|
||||
_fetchRealms().then((_) {
|
||||
_fetchPosts();
|
||||
});
|
||||
} else {
|
||||
_fetchPosts();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
if (_selectedCategory != null)
|
||||
MaterialBanner(
|
||||
content: Text(
|
||||
'postFilterWithCategory'.tr(args: [
|
||||
'postCategory${_selectedCategory!.alias.capitalize()}'.trExists()
|
||||
? 'postCategory${_selectedCategory!.alias.capitalize()}'
|
||||
.tr()
|
||||
: _selectedCategory!.name,
|
||||
]),
|
||||
),
|
||||
leading: Icon(kCategoryIcons[_selectedCategory!.alias] ??
|
||||
Symbols.question_mark),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Symbols.clear),
|
||||
onPressed: () {
|
||||
widget.onClearFilter.call();
|
||||
refreshPosts();
|
||||
},
|
||||
),
|
||||
],
|
||||
padding: const EdgeInsets.only(left: 20, right: 4),
|
||||
),
|
||||
if (widget.withRealm)
|
||||
DropdownButtonHideUnderline(
|
||||
child: DropdownButton2<SnRealm>(
|
||||
isExpanded: true,
|
||||
items: _realms
|
||||
.map(
|
||||
(ele) => DropdownMenuItem<SnRealm>(
|
||||
value: ele,
|
||||
child: Row(
|
||||
children: [
|
||||
AccountImage(
|
||||
content: ele.avatar,
|
||||
fallbackWidget: const Icon(Symbols.group, size: 16),
|
||||
radius: 14,
|
||||
),
|
||||
const Gap(8),
|
||||
Text(
|
||||
ele.name,
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
value: _selectedRealm,
|
||||
onChanged: (SnRealm? value) {
|
||||
setState(() => _selectedRealm = value);
|
||||
refreshPosts();
|
||||
},
|
||||
buttonStyleData: const ButtonStyleData(
|
||||
padding: EdgeInsets.only(left: 4, right: 12),
|
||||
),
|
||||
menuItemStyleData: const MenuItemStyleData(
|
||||
height: 48,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (widget.withRealm) const Divider(height: 1),
|
||||
Expanded(
|
||||
child: MediaQuery.removePadding(
|
||||
context: context,
|
||||
removeTop: true,
|
||||
child: RefreshIndicator(
|
||||
displacement: 40 + MediaQuery.of(context).padding.top,
|
||||
onRefresh: () => refreshPosts(),
|
||||
child: InfiniteList(
|
||||
itemCount: _posts.length,
|
||||
isLoading: _isBusy,
|
||||
centerLoading: true,
|
||||
hasReachedMax:
|
||||
_postCount != null && _posts.length >= _postCount!,
|
||||
onFetchData: _fetchPosts,
|
||||
itemBuilder: (context, idx) {
|
||||
return OpenablePostItem(
|
||||
data: _posts[idx],
|
||||
maxWidth: 640,
|
||||
onChanged: (data) {
|
||||
setState(() => _posts[idx] = data);
|
||||
},
|
||||
onDeleted: () {
|
||||
refreshPosts();
|
||||
},
|
||||
);
|
||||
},
|
||||
separatorBuilder: (_, __) => const Gap(8),
|
||||
),
|
||||
),
|
||||
).padding(top: 8),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _PostCategoryPickerPopup extends StatelessWidget {
|
||||
final List<SnPostCategory> categories;
|
||||
final SnPostCategory? selected;
|
||||
|
||||
const _PostCategoryPickerPopup({required this.categories, this.selected});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(Symbols.category, size: 24),
|
||||
const Gap(16),
|
||||
Text('postCategory')
|
||||
.tr()
|
||||
.textStyle(Theme.of(context).textTheme.titleLarge!),
|
||||
],
|
||||
).padding(horizontal: 20, top: 16, bottom: 12),
|
||||
ListTile(
|
||||
leading: const Icon(Symbols.clear),
|
||||
title: Text('postFilterReset').tr(),
|
||||
subtitle: Text('postFilterResetDescription').tr(),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
onTap: () {
|
||||
Navigator.pop(context, false);
|
||||
},
|
||||
),
|
||||
const Divider(height: 1),
|
||||
Expanded(
|
||||
child: GridView.count(
|
||||
crossAxisCount: 4,
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
childAspectRatio: 1,
|
||||
children: categories
|
||||
.map(
|
||||
(ele) => InkWell(
|
||||
onTap: () {
|
||||
_selectedCategory = ele;
|
||||
Navigator.pop(context, ele);
|
||||
},
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
kCategoryIcons[ele.alias] ?? Symbols.question_mark,
|
||||
color: selected == ele
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: null,
|
||||
),
|
||||
const Gap(4),
|
||||
Text(
|
||||
'postCategory${ele.alias.capitalize()}'.trExists()
|
||||
? 'postCategory${ele.alias.capitalize()}'.tr()
|
||||
: ele.name,
|
||||
)
|
||||
.textStyle(Theme.of(context).textTheme.titleMedium!)
|
||||
.textColor(selected == ele
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: null),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ import 'package:surface/providers/sn_attachment.dart';
|
||||
import 'package:surface/providers/sn_network.dart';
|
||||
import 'package:surface/types/attachment.dart';
|
||||
import 'package:surface/types/post.dart';
|
||||
import 'package:surface/types/realm.dart';
|
||||
import 'package:surface/widgets/account/account_image.dart';
|
||||
import 'package:surface/widgets/attachment/attachment_input.dart';
|
||||
import 'package:surface/widgets/attachment/attachment_item.dart';
|
||||
@ -35,6 +36,8 @@ import 'package:provider/provider.dart';
|
||||
import 'package:surface/widgets/post/post_poll_editor.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
import '../../providers/sn_realm.dart';
|
||||
|
||||
class PostEditorExtra {
|
||||
final String? text;
|
||||
final String? title;
|
||||
@ -79,6 +82,7 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
||||
bool get _isLoading => _isFetching || _writeController.isLoading;
|
||||
|
||||
List<SnPublisher>? _publishers;
|
||||
List<SnRealm>? _realms;
|
||||
|
||||
Future<void> _fetchPublishers() async {
|
||||
setState(() => _isFetching = true);
|
||||
@ -101,6 +105,16 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _fetchRealms() async {
|
||||
final rels = context.read<SnRealmProvider>();
|
||||
try {
|
||||
_realms = await rels.listAvailableRealms();
|
||||
} catch (err) {
|
||||
if (!mounted) return;
|
||||
context.showErrorDialog(err);
|
||||
}
|
||||
}
|
||||
|
||||
void _updateMeta() {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
@ -144,6 +158,19 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
void _showRealmPopup() {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
builder: (context) => _PostRealmPopup(
|
||||
controller: _writeController,
|
||||
realms: _realms,
|
||||
onUpdate: () {
|
||||
_fetchRealms();
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showPollEditorDialog() async {
|
||||
final poll = await showDialog<dynamic>(
|
||||
context: context,
|
||||
@ -161,6 +188,20 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
||||
}
|
||||
}
|
||||
|
||||
void _showThumbnailEditorDialog() async {
|
||||
final attachment = await showDialog<SnAttachment?>(
|
||||
context: context,
|
||||
builder: (context) => AttachmentInputDialog(
|
||||
title: 'postThumbnail'.tr(),
|
||||
pool: 'interactive',
|
||||
mediaType: SnMediaType.image,
|
||||
),
|
||||
);
|
||||
if (!context.mounted) return;
|
||||
if (attachment == null) return;
|
||||
_writeController.setThumbnail(attachment);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_writeController.dispose();
|
||||
@ -180,6 +221,7 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
||||
} else {
|
||||
_writeController.setMode(widget.mode);
|
||||
}
|
||||
_fetchRealms();
|
||||
_fetchPublishers();
|
||||
_writeController.fetchRelatedPost(
|
||||
context,
|
||||
@ -321,18 +363,22 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
||||
'stories' => _PostStoryEditor(
|
||||
controller: _writeController,
|
||||
onTapPublisher: _showPublisherPopup,
|
||||
onTapRealm: _showRealmPopup,
|
||||
),
|
||||
'articles' => _PostArticleEditor(
|
||||
controller: _writeController,
|
||||
onTapPublisher: _showPublisherPopup,
|
||||
onTapRealm: _showRealmPopup,
|
||||
),
|
||||
'questions' => _PostQuestionEditor(
|
||||
controller: _writeController,
|
||||
onTapPublisher: _showPublisherPopup,
|
||||
onTapRealm: _showRealmPopup,
|
||||
),
|
||||
'videos' => _PostVideoEditor(
|
||||
controller: _writeController,
|
||||
onTapPublisher: _showPublisherPopup,
|
||||
onTapRealm: _showRealmPopup,
|
||||
),
|
||||
_ => const Placeholder(),
|
||||
})
|
||||
@ -344,15 +390,11 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: PostMediaPendingList(
|
||||
thumbnail: _writeController.thumbnail,
|
||||
attachments: _writeController.attachments,
|
||||
isBusy: _writeController.isBusy,
|
||||
onUpload: (int idx) async {
|
||||
await _writeController.uploadSingleAttachment(context, idx);
|
||||
},
|
||||
onPostSetThumbnail: (int? idx) {
|
||||
_writeController.setThumbnail(idx);
|
||||
},
|
||||
onInsertLink: (int idx) async {
|
||||
_writeController.contentController.text +=
|
||||
'\n';
|
||||
@ -453,6 +495,22 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
||||
_showPollEditorDialog();
|
||||
},
|
||||
),
|
||||
if (_writeController.mode == 'articles')
|
||||
IconButton(
|
||||
icon: Icon(Symbols.full_coverage, color: Theme.of(context).colorScheme.primary),
|
||||
style: ButtonStyle(
|
||||
backgroundColor: _writeController.thumbnail == null
|
||||
? null
|
||||
: WidgetStatePropertyAll(Theme.of(context).colorScheme.surfaceContainer),
|
||||
),
|
||||
onPressed: () {
|
||||
if (_writeController.thumbnail != null) {
|
||||
_writeController.setThumbnail(null);
|
||||
return;
|
||||
}
|
||||
_showThumbnailEditorDialog();
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -549,11 +607,65 @@ class _PostPublisherPopup extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class _PostRealmPopup extends StatelessWidget {
|
||||
final PostWriteController controller;
|
||||
final List<SnRealm>? realms;
|
||||
final Function onUpdate;
|
||||
|
||||
const _PostRealmPopup({required this.controller, this.realms, required this.onUpdate});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(Symbols.face, size: 24),
|
||||
const Gap(16),
|
||||
Text('accountRealms', style: Theme.of(context).textTheme.titleLarge).tr(),
|
||||
],
|
||||
).padding(horizontal: 20, top: 16, bottom: 12),
|
||||
ListTile(
|
||||
leading: const Icon(Symbols.close),
|
||||
title: Text('postInGlobal').tr(),
|
||||
subtitle: Text('postInGlobalDescription').tr(),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
onTap: () {
|
||||
controller.setRealm(null);
|
||||
Navigator.pop(context, true);
|
||||
},
|
||||
),
|
||||
const Divider(height: 1),
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
itemCount: realms?.length ?? 0,
|
||||
itemBuilder: (context, idx) {
|
||||
final realm = realms![idx];
|
||||
return ListTile(
|
||||
title: Text(realm.name),
|
||||
subtitle: Text('@${realm.alias}'),
|
||||
leading: AccountImage(content: realm.avatar, radius: 18),
|
||||
onTap: () {
|
||||
controller.setRealm(realm);
|
||||
Navigator.pop(context, true);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _PostStoryEditor extends StatelessWidget {
|
||||
final PostWriteController controller;
|
||||
final Function? onTapPublisher;
|
||||
final Function? onTapRealm;
|
||||
|
||||
const _PostStoryEditor({required this.controller, this.onTapPublisher});
|
||||
const _PostStoryEditor({required this.controller, this.onTapPublisher, this.onTapRealm});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -563,17 +675,36 @@ class _PostStoryEditor extends StatelessWidget {
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Material(
|
||||
elevation: 2,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(24)),
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
onTapPublisher?.call();
|
||||
},
|
||||
child: AccountImage(
|
||||
content: controller.publisher?.avatar,
|
||||
Column(
|
||||
children: [
|
||||
Material(
|
||||
elevation: 2,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(24)),
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
onTapPublisher?.call();
|
||||
},
|
||||
child: AccountImage(
|
||||
content: controller.publisher?.avatar,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const Gap(11),
|
||||
Material(
|
||||
elevation: 1,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(24)),
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
onTapRealm?.call();
|
||||
},
|
||||
child: AccountImage(
|
||||
content: controller.realm?.avatar,
|
||||
fallbackWidget: const Icon(Symbols.globe, size: 20),
|
||||
radius: 14,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
@ -616,8 +747,9 @@ class _PostStoryEditor extends StatelessWidget {
|
||||
class _PostArticleEditor extends StatelessWidget {
|
||||
final PostWriteController controller;
|
||||
final Function? onTapPublisher;
|
||||
final Function? onTapRealm;
|
||||
|
||||
const _PostArticleEditor({required this.controller, this.onTapPublisher});
|
||||
const _PostArticleEditor({required this.controller, this.onTapPublisher, this.onTapRealm});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -638,6 +770,21 @@ class _PostArticleEditor extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
),
|
||||
Material(
|
||||
elevation: 1,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(24)),
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
onTapRealm?.call();
|
||||
},
|
||||
child: AccountImage(
|
||||
content: controller.realm?.avatar,
|
||||
fallbackWidget: const Icon(Symbols.globe, size: 20),
|
||||
radius: 14,
|
||||
),
|
||||
),
|
||||
),
|
||||
const Gap(8),
|
||||
],
|
||||
).padding(horizontal: 12, vertical: 8),
|
||||
onTap: () {
|
||||
@ -668,7 +815,24 @@ class _PostArticleEditor extends StatelessWidget {
|
||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||
contentInsertionConfiguration: controller.contentInsertionConfiguration,
|
||||
).padding(horizontal: 16),
|
||||
const Gap(4),
|
||||
if (controller.thumbnail != null)
|
||||
Container(
|
||||
margin: const EdgeInsets.only(left: 12, right: 12, top: 8, bottom: 4),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: Theme.of(context).dividerColor),
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||
child: AspectRatio(
|
||||
aspectRatio: 16 / 9,
|
||||
child: AttachmentItem(
|
||||
data: controller.thumbnail!.attachment!,
|
||||
heroTag: "post-editor-thumbnail-preview",
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
];
|
||||
|
||||
if (ResponsiveBreakpoints.of(context).largerThan(MOBILE)) {
|
||||
@ -740,8 +904,9 @@ class _PostArticleEditor extends StatelessWidget {
|
||||
class _PostQuestionEditor extends StatelessWidget {
|
||||
final PostWriteController controller;
|
||||
final Function? onTapPublisher;
|
||||
final Function? onTapRealm;
|
||||
|
||||
const _PostQuestionEditor({required this.controller, this.onTapPublisher});
|
||||
const _PostQuestionEditor({required this.controller, this.onTapPublisher, this.onTapRealm});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -751,17 +916,36 @@ class _PostQuestionEditor extends StatelessWidget {
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Material(
|
||||
elevation: 1,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(24)),
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
onTapPublisher?.call();
|
||||
},
|
||||
child: AccountImage(
|
||||
content: controller.publisher?.avatar,
|
||||
Column(
|
||||
children: [
|
||||
Material(
|
||||
elevation: 2,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(24)),
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
onTapPublisher?.call();
|
||||
},
|
||||
child: AccountImage(
|
||||
content: controller.publisher?.avatar,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const Gap(11),
|
||||
Material(
|
||||
elevation: 1,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(24)),
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
onTapRealm?.call();
|
||||
},
|
||||
child: AccountImage(
|
||||
content: controller.realm?.avatar,
|
||||
fallbackWidget: const Icon(Symbols.globe, size: 20),
|
||||
radius: 14,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
@ -815,8 +999,9 @@ class _PostQuestionEditor extends StatelessWidget {
|
||||
class _PostVideoEditor extends StatelessWidget {
|
||||
final PostWriteController controller;
|
||||
final Function? onTapPublisher;
|
||||
final Function? onTapRealm;
|
||||
|
||||
const _PostVideoEditor({required this.controller, this.onTapPublisher});
|
||||
const _PostVideoEditor({required this.controller, this.onTapPublisher, this.onTapRealm});
|
||||
|
||||
void _selectVideo(BuildContext context) async {
|
||||
final video = await showDialog<SnAttachment?>(
|
||||
@ -904,28 +1089,36 @@ class _PostVideoEditor extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
Material(
|
||||
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||
child: InkWell(
|
||||
child: Row(
|
||||
children: [
|
||||
AccountImage(content: controller.publisher?.avatar, radius: 20),
|
||||
const Gap(8),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(controller.publisher?.nick ?? 'loading'.tr()).bold(),
|
||||
Text('@${controller.publisher?.name}'),
|
||||
],
|
||||
),
|
||||
Column(
|
||||
children: [
|
||||
Material(
|
||||
elevation: 2,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(24)),
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
onTapPublisher?.call();
|
||||
},
|
||||
child: AccountImage(
|
||||
content: controller.publisher?.avatar,
|
||||
),
|
||||
],
|
||||
).padding(horizontal: 12, vertical: 8),
|
||||
onTap: () {
|
||||
onTapPublisher?.call();
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
const Gap(11),
|
||||
Material(
|
||||
elevation: 1,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(24)),
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
onTapRealm?.call();
|
||||
},
|
||||
child: AccountImage(
|
||||
content: controller.realm?.avatar,
|
||||
fallbackWidget: const Icon(Symbols.globe, size: 20),
|
||||
radius: 14,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const Gap(16),
|
||||
TextField(
|
||||
|
@ -4,17 +4,16 @@ import 'package:gap/gap.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
import 'package:surface/providers/config.dart';
|
||||
import 'package:surface/providers/sn_network.dart';
|
||||
import 'package:surface/providers/userinfo.dart';
|
||||
import 'package:surface/types/realm.dart';
|
||||
import 'package:surface/widgets/account/account_image.dart';
|
||||
import 'package:surface/widgets/app_bar_leading.dart';
|
||||
import 'package:surface/widgets/dialog.dart';
|
||||
import 'package:surface/widgets/loading_indicator.dart';
|
||||
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
||||
import 'package:surface/widgets/realm/realm_item.dart';
|
||||
import 'package:surface/widgets/unauthorized_hint.dart';
|
||||
import 'package:surface/widgets/universal_image.dart';
|
||||
|
||||
class RealmScreen extends StatefulWidget {
|
||||
const RealmScreen({super.key});
|
||||
@ -75,12 +74,12 @@ class _RealmScreenState extends State<RealmScreen> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_isCompactView = context.read<ConfigProvider>().realmCompactView;
|
||||
_fetchRealms();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final sn = context.read<SnNetworkProvider>();
|
||||
final ua = context.read<UserProvider>();
|
||||
|
||||
if (!ua.isAuthorized) {
|
||||
@ -110,6 +109,7 @@ class _RealmScreenState extends State<RealmScreen> {
|
||||
icon: !_isCompactView ? const Icon(Symbols.view_list) : const Icon(Symbols.view_module),
|
||||
onPressed: () {
|
||||
setState(() => _isCompactView = !_isCompactView);
|
||||
context.read<ConfigProvider>().realmCompactView = _isCompactView;
|
||||
},
|
||||
),
|
||||
const Gap(8),
|
||||
@ -134,129 +134,46 @@ class _RealmScreenState extends State<RealmScreen> {
|
||||
itemCount: _realms?.length ?? 0,
|
||||
itemBuilder: (context, idx) {
|
||||
final realm = _realms![idx];
|
||||
if (_isCompactView) {
|
||||
return ListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
leading: AccountImage(
|
||||
content: realm.avatar,
|
||||
fallbackWidget: const Icon(Symbols.group, size: 20),
|
||||
),
|
||||
title: Text(realm.name),
|
||||
subtitle: Text(
|
||||
realm.description,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
trailing: PopupMenuButton(
|
||||
itemBuilder: (BuildContext context) => [
|
||||
PopupMenuItem(
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Symbols.edit),
|
||||
const Gap(16),
|
||||
Text('edit').tr(),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
GoRouter.of(context).pushNamed(
|
||||
'realmManage',
|
||||
queryParameters: {'editing': realm.alias},
|
||||
).then((value) {
|
||||
if (value != null) {
|
||||
_fetchRealms();
|
||||
}
|
||||
});
|
||||
},
|
||||
),
|
||||
PopupMenuItem(
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Symbols.delete),
|
||||
const Gap(16),
|
||||
Text('delete').tr(),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
_deleteRealm(realm);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
GoRouter.of(context).pushNamed(
|
||||
'realmDetail',
|
||||
pathParameters: {'alias': realm.alias},
|
||||
).then((value) {
|
||||
if (value == true) {
|
||||
_fetchRealms();
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return Container(
|
||||
constraints: BoxConstraints(maxWidth: 640),
|
||||
child: Card(
|
||||
margin: const EdgeInsets.all(12),
|
||||
child: InkWell(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
return RealmItemWidget(
|
||||
showPopularity: false,
|
||||
item: realm,
|
||||
isListView: _isCompactView,
|
||||
actionListView: [
|
||||
PopupMenuItem(
|
||||
child: Row(
|
||||
children: [
|
||||
AspectRatio(
|
||||
aspectRatio: 16 / 7,
|
||||
child: Stack(
|
||||
clipBehavior: Clip.none,
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||
child: Container(
|
||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||
child: (realm.banner?.isEmpty ?? true)
|
||||
? const SizedBox.shrink()
|
||||
: AutoResizeUniversalImage(
|
||||
sn.getAttachmentUrl(realm.banner!),
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
bottom: -30,
|
||||
left: 18,
|
||||
child: AccountImage(
|
||||
content: realm.avatar,
|
||||
radius: 24,
|
||||
fallbackWidget: const Icon(Symbols.group, size: 24),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const Gap(20 + 12),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(realm.name).textStyle(Theme.of(context).textTheme.titleMedium!),
|
||||
Text(realm.description).textStyle(Theme.of(context).textTheme.bodySmall!),
|
||||
],
|
||||
).padding(horizontal: 24, bottom: 14),
|
||||
const Icon(Symbols.edit),
|
||||
const Gap(16),
|
||||
Text('edit').tr(),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
GoRouter.of(context).pushNamed(
|
||||
'realmDetail',
|
||||
pathParameters: {'alias': realm.alias},
|
||||
'realmManage',
|
||||
queryParameters: {'editing': realm.alias},
|
||||
).then((value) {
|
||||
if (value == true) {
|
||||
if (value != null) {
|
||||
_fetchRealms();
|
||||
}
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
).center();
|
||||
PopupMenuItem(
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Symbols.delete),
|
||||
const Gap(16),
|
||||
Text('delete').tr(),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
_deleteRealm(realm);
|
||||
},
|
||||
),
|
||||
],
|
||||
onUpdate: _fetchRealms,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
|
@ -5,16 +5,19 @@ 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/providers/user_directory.dart';
|
||||
import 'package:surface/providers/userinfo.dart';
|
||||
import 'package:surface/types/account.dart';
|
||||
import 'package:surface/types/chat.dart';
|
||||
import 'package:surface/types/post.dart';
|
||||
import 'package:surface/types/realm.dart';
|
||||
import 'package:surface/widgets/account/account_image.dart';
|
||||
import 'package:surface/widgets/account/account_select.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 RealmDetailScreen extends StatefulWidget {
|
||||
@ -60,18 +63,36 @@ class _RealmDetailScreenState extends State<RealmDetailScreen> {
|
||||
}
|
||||
}
|
||||
|
||||
List<SnChannel>? _channels;
|
||||
|
||||
Future<void> _fetchChannels() async {
|
||||
try {
|
||||
final sn = context.read<SnNetworkProvider>();
|
||||
final resp = await sn.client.get('/cgi/im/channels/${widget.alias}/public');
|
||||
_channels = List<SnChannel>.from(
|
||||
resp.data.map((e) => SnChannel.fromJson(e)).cast<SnChannel>(),
|
||||
);
|
||||
} catch (err) {
|
||||
if (mounted) context.showErrorDialog(err);
|
||||
rethrow;
|
||||
} finally {
|
||||
setState(() {});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_fetchRealm().then((_) {
|
||||
_fetchPublishers();
|
||||
_fetchChannels();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DefaultTabController(
|
||||
length: 3,
|
||||
length: 4,
|
||||
child: AppScaffold(
|
||||
body: NestedScrollView(
|
||||
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
|
||||
@ -83,6 +104,7 @@ class _RealmDetailScreenState extends State<RealmDetailScreen> {
|
||||
bottom: TabBar(
|
||||
tabs: [
|
||||
Tab(icon: Icon(Symbols.home, color: Theme.of(context).appBarTheme.foregroundColor)),
|
||||
Tab(icon: Icon(Symbols.explore, color: Theme.of(context).appBarTheme.foregroundColor)),
|
||||
Tab(icon: Icon(Symbols.group, color: Theme.of(context).appBarTheme.foregroundColor)),
|
||||
Tab(icon: Icon(Symbols.settings, color: Theme.of(context).appBarTheme.foregroundColor)),
|
||||
],
|
||||
@ -93,7 +115,8 @@ class _RealmDetailScreenState extends State<RealmDetailScreen> {
|
||||
},
|
||||
body: TabBarView(
|
||||
children: [
|
||||
_RealmDetailHomeWidget(realm: _realm, publishers: _publishers),
|
||||
_RealmDetailHomeWidget(realm: _realm, publishers: _publishers, channels: _channels),
|
||||
_RealmPostListWidget(realm: _realm),
|
||||
_RealmMemberListWidget(realm: _realm),
|
||||
_RealmSettingsWidget(
|
||||
realm: _realm,
|
||||
@ -112,8 +135,9 @@ class _RealmDetailScreenState extends State<RealmDetailScreen> {
|
||||
class _RealmDetailHomeWidget extends StatelessWidget {
|
||||
final SnRealm? realm;
|
||||
final List<SnPublisher>? publishers;
|
||||
final List<SnChannel>? channels;
|
||||
|
||||
const _RealmDetailHomeWidget({required this.realm, this.publishers});
|
||||
const _RealmDetailHomeWidget({required this.realm, this.publishers, this.channels});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -135,30 +159,76 @@ class _RealmDetailHomeWidget extends StatelessWidget {
|
||||
],
|
||||
).padding(horizontal: 24),
|
||||
const Gap(16),
|
||||
const Divider(),
|
||||
const Divider(height: 1),
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
padding: EdgeInsets.zero,
|
||||
itemCount: publishers?.length ?? 0,
|
||||
itemBuilder: (context, idx) {
|
||||
final ele = publishers![idx];
|
||||
return ListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
leading: AccountImage(
|
||||
content: ele.avatar,
|
||||
fallbackWidget: const Icon(Symbols.group, size: 24),
|
||||
child: CustomScrollView(
|
||||
slivers: [
|
||||
if (publishers?.isNotEmpty ?? false)
|
||||
SliverToBoxAdapter(
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||
child: Text('realmCommunityPublishersHint'.tr(), style: Theme.of(context).textTheme.bodyMedium)
|
||||
.padding(horizontal: 24, vertical: 8),
|
||||
),
|
||||
),
|
||||
title: Text(ele.nick),
|
||||
subtitle: Text('@${ele.name}'),
|
||||
trailing: const Icon(Symbols.chevron_right),
|
||||
onTap: () {
|
||||
GoRouter.of(context).pushNamed(
|
||||
'postPublisher',
|
||||
pathParameters: {'name': ele.name},
|
||||
SliverList.builder(
|
||||
itemCount: publishers?.length ?? 0,
|
||||
itemBuilder: (context, idx) {
|
||||
final ele = publishers![idx];
|
||||
return ListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
leading: AccountImage(
|
||||
content: ele.avatar,
|
||||
fallbackWidget: const Icon(Symbols.group, size: 24),
|
||||
),
|
||||
title: Text(ele.nick),
|
||||
subtitle: Text('@${ele.name}'),
|
||||
trailing: const Icon(Symbols.chevron_right),
|
||||
onTap: () {
|
||||
GoRouter.of(context).pushNamed(
|
||||
'postPublisher',
|
||||
pathParameters: {'name': ele.name},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
if (channels?.isNotEmpty ?? false)
|
||||
SliverToBoxAdapter(
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||
child: Text('realmCommunityPublicChannelsHint'.tr(), style: Theme.of(context).textTheme.bodyMedium)
|
||||
.padding(horizontal: 24, vertical: 8),
|
||||
),
|
||||
),
|
||||
SliverList.builder(
|
||||
itemCount: channels?.length ?? 0,
|
||||
itemBuilder: (context, idx) {
|
||||
final ele = channels![idx];
|
||||
return ListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
leading: AccountImage(
|
||||
content: null,
|
||||
fallbackWidget: const Icon(Symbols.chat, size: 20),
|
||||
),
|
||||
title: Text(ele.name),
|
||||
subtitle: Text('#${ele.alias}'),
|
||||
trailing: const Icon(Symbols.chevron_right),
|
||||
onTap: () {
|
||||
GoRouter.of(context).pushNamed(
|
||||
'chatRoom',
|
||||
pathParameters: {
|
||||
'scope': realm?.alias ?? 'global',
|
||||
'alias': ele.alias,
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
@ -166,6 +236,72 @@ class _RealmDetailHomeWidget extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class _RealmPostListWidget extends StatefulWidget {
|
||||
final SnRealm? realm;
|
||||
|
||||
const _RealmPostListWidget({this.realm});
|
||||
|
||||
@override
|
||||
State<_RealmPostListWidget> createState() => _RealmPostListWidgetState();
|
||||
}
|
||||
|
||||
class _RealmPostListWidgetState extends State<_RealmPostListWidget> {
|
||||
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: widget.realm?.id.toString(),
|
||||
);
|
||||
_totalCount = out.$2;
|
||||
_posts.addAll(out.$1);
|
||||
} catch (err) {
|
||||
if (!mounted) return;
|
||||
context.showErrorDialog(err);
|
||||
} finally {
|
||||
setState(() => _isBusy = false);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MediaQuery.removePadding(
|
||||
context: context,
|
||||
removeTop: true,
|
||||
child: RefreshIndicator(
|
||||
onRefresh: _fetchPosts,
|
||||
child: InfiniteList(
|
||||
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 Gap(8),
|
||||
),
|
||||
),
|
||||
).padding(top: 8);
|
||||
}
|
||||
}
|
||||
|
||||
class _RealmMemberListWidget extends StatefulWidget {
|
||||
final SnRealm? realm;
|
||||
|
||||
@ -365,7 +501,7 @@ class _RealmSettingsWidgetState extends State<_RealmSettingsWidget> {
|
||||
final sn = context.read<SnNetworkProvider>();
|
||||
|
||||
try {
|
||||
await sn.client.delete('/cgi/id/realms/${widget.realm!.alias}/members/me');
|
||||
await sn.client.delete('/cgi/id/realms/${widget.realm!.alias}/me');
|
||||
if (!mounted) return;
|
||||
Navigator.pop(context, true);
|
||||
} catch (err) {
|
||||
|
@ -4,6 +4,7 @@ import 'package:gap/gap.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
import 'package:surface/providers/config.dart';
|
||||
import 'package:surface/providers/sn_network.dart';
|
||||
import 'package:surface/providers/userinfo.dart';
|
||||
import 'package:surface/types/chat.dart';
|
||||
@ -12,7 +13,7 @@ import 'package:surface/widgets/account/account_image.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/universal_image.dart';
|
||||
import 'package:surface/widgets/realm/realm_item.dart';
|
||||
|
||||
class RealmDiscoveryScreen extends StatefulWidget {
|
||||
const RealmDiscoveryScreen({super.key});
|
||||
@ -24,6 +25,7 @@ class RealmDiscoveryScreen extends StatefulWidget {
|
||||
class _RealmDiscoveryScreenState extends State<RealmDiscoveryScreen> {
|
||||
List<SnRealm>? _realms;
|
||||
bool _isBusy = false;
|
||||
bool _isCompactView = false;
|
||||
|
||||
Future<void> _fetchRealms() async {
|
||||
try {
|
||||
@ -44,16 +46,25 @@ class _RealmDiscoveryScreenState extends State<RealmDiscoveryScreen> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_isCompactView = context.read<ConfigProvider>().realmCompactView;
|
||||
_fetchRealms();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final sn = context.read<SnNetworkProvider>();
|
||||
|
||||
return AppScaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('screenRealmDiscovery').tr(),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: _isCompactView ? const Icon(Symbols.view_list) : const Icon(Symbols.view_module),
|
||||
onPressed: () {
|
||||
setState(() => _isCompactView = !_isCompactView);
|
||||
context.read<ConfigProvider>().realmCompactView = _isCompactView;
|
||||
},
|
||||
),
|
||||
const Gap(8),
|
||||
],
|
||||
),
|
||||
body: Column(
|
||||
children: [
|
||||
@ -66,64 +77,16 @@ class _RealmDiscoveryScreenState extends State<RealmDiscoveryScreen> {
|
||||
itemCount: _realms?.length ?? 0,
|
||||
itemBuilder: (context, idx) {
|
||||
final realm = _realms![idx];
|
||||
return Container(
|
||||
constraints: BoxConstraints(maxWidth: 640),
|
||||
child: Card(
|
||||
margin: const EdgeInsets.all(12),
|
||||
child: InkWell(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
AspectRatio(
|
||||
aspectRatio: 16 / 7,
|
||||
child: Stack(
|
||||
clipBehavior: Clip.none,
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||
child: Container(
|
||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||
child: (realm.banner?.isEmpty ?? true)
|
||||
? const SizedBox.shrink()
|
||||
: AutoResizeUniversalImage(
|
||||
sn.getAttachmentUrl(realm.banner!),
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
bottom: -30,
|
||||
left: 18,
|
||||
child: AccountImage(
|
||||
content: realm.avatar,
|
||||
radius: 24,
|
||||
fallbackWidget: const Icon(Symbols.group, size: 24),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const Gap(20 + 12),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(realm.name).textStyle(Theme.of(context).textTheme.titleMedium!),
|
||||
Text(realm.description).textStyle(Theme.of(context).textTheme.bodySmall!),
|
||||
],
|
||||
).padding(horizontal: 24, bottom: 14),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
builder: (context) => _RealmJoinPopup(realm: realm),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
).center();
|
||||
return RealmItemWidget(
|
||||
item: realm,
|
||||
isListView: _isCompactView,
|
||||
onTap: () {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
builder: (context) => _RealmJoinPopup(realm: realm),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
@ -235,6 +198,8 @@ class _RealmJoinPopupState extends State<_RealmJoinPopup> {
|
||||
),
|
||||
Text(
|
||||
widget.realm.description,
|
||||
maxLines: 3,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
],
|
||||
|
@ -5,8 +5,10 @@ import 'package:dropdown_button2/dropdown_button2.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_colorpicker/flutter_colorpicker.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
@ -14,7 +16,10 @@ import 'package:provider/provider.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
import 'package:surface/providers/config.dart';
|
||||
import 'package:surface/providers/database.dart';
|
||||
import 'package:surface/providers/notification.dart';
|
||||
import 'package:surface/providers/sn_network.dart';
|
||||
import 'package:surface/providers/sn_sticker.dart';
|
||||
import 'package:surface/providers/theme.dart';
|
||||
import 'package:surface/theme.dart';
|
||||
import 'package:surface/widgets/dialog.dart';
|
||||
@ -67,6 +72,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final sn = context.read<SnNetworkProvider>();
|
||||
final dt = context.read<DatabaseProvider>();
|
||||
|
||||
return AppScaffold(
|
||||
appBar: AppBar(
|
||||
@ -81,7 +87,11 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('settingsAppearance').bold().fontSize(17).tr().padding(horizontal: 20, bottom: 4),
|
||||
Text('settingsAppearance')
|
||||
.bold()
|
||||
.fontSize(17)
|
||||
.tr()
|
||||
.padding(horizontal: 20, bottom: 4),
|
||||
ListTile(
|
||||
title: Text('settingsDisplayLanguage').tr(),
|
||||
subtitle: Text('settingsDisplayLanguageDescription').tr(),
|
||||
@ -91,15 +101,21 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
child: DropdownButton2<Locale?>(
|
||||
isExpanded: true,
|
||||
items: [
|
||||
...EasyLocalization.of(context)!.supportedLocales.mapIndexed((idx, ele) {
|
||||
...EasyLocalization.of(context)!
|
||||
.supportedLocales
|
||||
.mapIndexed((idx, ele) {
|
||||
return DropdownMenuItem<Locale?>(
|
||||
value: ele,
|
||||
child: Text('${ele.languageCode}-${ele.countryCode}').fontSize(14),
|
||||
child:
|
||||
Text('${ele.languageCode}-${ele.countryCode}')
|
||||
.fontSize(14),
|
||||
);
|
||||
}),
|
||||
DropdownMenuItem<Locale?>(
|
||||
value: null,
|
||||
child: Text('settingsDisplayLanguageSystem').tr().fontSize(14),
|
||||
child: Text('settingsDisplayLanguageSystem')
|
||||
.tr()
|
||||
.fontSize(14),
|
||||
),
|
||||
],
|
||||
value: EasyLocalization.of(context)!.currentLocale,
|
||||
@ -132,10 +148,12 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
leading: const Icon(Symbols.image),
|
||||
trailing: const Icon(Symbols.chevron_right),
|
||||
onTap: () async {
|
||||
final image = await ImagePicker().pickImage(source: ImageSource.gallery);
|
||||
final image = await ImagePicker()
|
||||
.pickImage(source: ImageSource.gallery);
|
||||
if (image == null) return;
|
||||
|
||||
await File(image.path).copy('$_docBasepath/app_background_image');
|
||||
await File(image.path)
|
||||
.copy('$_docBasepath/app_background_image');
|
||||
_prefs.setBool(kAppBackgroundStoreKey, true);
|
||||
|
||||
setState(() {});
|
||||
@ -143,7 +161,8 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
),
|
||||
if (!kIsWeb)
|
||||
FutureBuilder<bool>(
|
||||
future: File('$_docBasepath/app_background_image').exists(),
|
||||
future:
|
||||
File('$_docBasepath/app_background_image').exists(),
|
||||
builder: (context, snapshot) {
|
||||
if (!snapshot.hasData || !snapshot.data!) {
|
||||
return const SizedBox.shrink();
|
||||
@ -151,12 +170,16 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
|
||||
return ListTile(
|
||||
title: Text('settingsBackgroundImageClear').tr(),
|
||||
subtitle: Text('settingsBackgroundImageClearDescription').tr(),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
subtitle:
|
||||
Text('settingsBackgroundImageClearDescription')
|
||||
.tr(),
|
||||
contentPadding:
|
||||
const EdgeInsets.symmetric(horizontal: 24),
|
||||
leading: const Icon(Symbols.texture),
|
||||
trailing: const Icon(Symbols.chevron_right),
|
||||
onTap: () {
|
||||
File('$_docBasepath/app_background_image').deleteSync();
|
||||
File('$_docBasepath/app_background_image')
|
||||
.deleteSync();
|
||||
_prefs.remove(kAppBackgroundStoreKey);
|
||||
setState(() {});
|
||||
},
|
||||
@ -186,34 +209,35 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
trailing: const Icon(Symbols.chevron_right),
|
||||
onTap: () async {
|
||||
Color pickerColor = Color(_prefs.getInt(kAppColorSchemeStoreKey) ?? Colors.indigo.value);
|
||||
Color pickerColor = Color(
|
||||
_prefs.getInt(kAppColorSchemeStoreKey) ??
|
||||
Colors.indigo.value);
|
||||
final color = await showDialog<Color?>(
|
||||
context: context,
|
||||
builder: (context) =>
|
||||
AlertDialog(
|
||||
content: SingleChildScrollView(
|
||||
child: ColorPicker(
|
||||
pickerColor: pickerColor,
|
||||
onColorChanged: (color) => pickerColor = color,
|
||||
enableAlpha: false,
|
||||
hexInputBar: true,
|
||||
),
|
||||
),
|
||||
actions: <Widget>[
|
||||
TextButton(
|
||||
child: const Text('dialogDismiss').tr(),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
TextButton(
|
||||
child: const Text('dialogConfirm').tr(),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(pickerColor);
|
||||
},
|
||||
),
|
||||
],
|
||||
builder: (context) => AlertDialog(
|
||||
content: SingleChildScrollView(
|
||||
child: ColorPicker(
|
||||
pickerColor: pickerColor,
|
||||
onColorChanged: (color) => pickerColor = color,
|
||||
enableAlpha: false,
|
||||
hexInputBar: true,
|
||||
),
|
||||
),
|
||||
actions: <Widget>[
|
||||
TextButton(
|
||||
child: const Text('dialogDismiss').tr(),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
TextButton(
|
||||
child: const Text('dialogConfirm').tr(),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(pickerColor);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
if (color == null || !context.mounted) return;
|
||||
@ -248,16 +272,17 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
],
|
||||
value: _prefs.getInt(kAppColorSchemeStoreKey) == null
|
||||
? 1
|
||||
: kColorSchemes.values
|
||||
.toList()
|
||||
.indexWhere((ele) => ele.value == _prefs.getInt(kAppColorSchemeStoreKey)),
|
||||
: kColorSchemes.values.toList().indexWhere((ele) =>
|
||||
ele.value ==
|
||||
_prefs.getInt(kAppColorSchemeStoreKey)),
|
||||
onChanged: (int? value) {
|
||||
if (value != null && value != -1) {
|
||||
_prefs.setInt(kAppColorSchemeStoreKey, kColorSchemes.values
|
||||
.elementAt(value)
|
||||
.value);
|
||||
_prefs.setInt(kAppColorSchemeStoreKey,
|
||||
kColorSchemes.values.elementAt(value).value);
|
||||
final th = context.read<ThemeProvider>();
|
||||
th.reloadTheme(seedColorOverride: kColorSchemes.values.elementAt(value));
|
||||
th.reloadTheme(
|
||||
seedColorOverride:
|
||||
kColorSchemes.values.elementAt(value));
|
||||
setState(() {});
|
||||
|
||||
context.showSnackbar('colorSchemeApplied'.tr());
|
||||
@ -293,7 +318,8 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
CheckboxListTile(
|
||||
secondary: const Icon(Symbols.left_panel_close),
|
||||
title: Text('settingsDrawerPreferCollapse').tr(),
|
||||
subtitle: Text('settingsDrawerPreferCollapseDescription').tr(),
|
||||
subtitle:
|
||||
Text('settingsDrawerPreferCollapseDescription').tr(),
|
||||
contentPadding: const EdgeInsets.only(left: 24, right: 17),
|
||||
value: _prefs.getBool(kAppDrawerPreferCollapse) ?? false,
|
||||
onChanged: (value) {
|
||||
@ -308,7 +334,11 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('settingsFeatures').bold().fontSize(17).tr().padding(horizontal: 20, bottom: 4),
|
||||
Text('settingsFeatures')
|
||||
.bold()
|
||||
.fontSize(17)
|
||||
.tr()
|
||||
.padding(horizontal: 20, bottom: 4),
|
||||
CheckboxListTile(
|
||||
secondary: const Icon(Symbols.vibration),
|
||||
contentPadding: const EdgeInsets.only(left: 24, right: 17),
|
||||
@ -350,7 +380,11 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('settingsNetwork').bold().fontSize(17).tr().padding(horizontal: 20, bottom: 4),
|
||||
Text('settingsNetwork')
|
||||
.bold()
|
||||
.fontSize(17)
|
||||
.tr()
|
||||
.padding(horizontal: 20, bottom: 4),
|
||||
TextField(
|
||||
controller: _serverUrlController,
|
||||
decoration: InputDecoration(
|
||||
@ -371,7 +405,8 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
},
|
||||
),
|
||||
),
|
||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||
onTapOutside: (_) =>
|
||||
FocusManager.instance.primaryFocus?.unfocus(),
|
||||
).padding(horizontal: 16, top: 8, bottom: 4),
|
||||
ListTile(
|
||||
title: Text('settingsNetworkServerPreset').tr(),
|
||||
@ -383,12 +418,13 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
isExpanded: true,
|
||||
items: [
|
||||
...kNetworkServerDirectory,
|
||||
if (!kNetworkServerDirectory.map((ele) => ele.$2).contains(_serverUrlController.text))
|
||||
if (!kNetworkServerDirectory
|
||||
.map((ele) => ele.$2)
|
||||
.contains(_serverUrlController.text))
|
||||
('Custom', _serverUrlController.text),
|
||||
]
|
||||
.map(
|
||||
(item) =>
|
||||
DropdownMenuItem<String>(
|
||||
(item) => DropdownMenuItem<String>(
|
||||
value: item.$2,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
@ -396,11 +432,12 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(item.$1).fontSize(14),
|
||||
Text(item.$2, overflow: TextOverflow.ellipsis).fontSize(11)
|
||||
Text(item.$2, overflow: TextOverflow.ellipsis)
|
||||
.fontSize(11)
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
)
|
||||
.toList(),
|
||||
value: _serverUrlController.text,
|
||||
onChanged: (String? value) {
|
||||
@ -442,7 +479,11 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('settingsPerformance').bold().fontSize(17).tr().padding(horizontal: 20, bottom: 4),
|
||||
Text('settingsPerformance')
|
||||
.bold()
|
||||
.fontSize(17)
|
||||
.tr()
|
||||
.padding(horizontal: 20, bottom: 4),
|
||||
ListTile(
|
||||
title: Text('settingsImageQuality').tr(),
|
||||
subtitle: Text('settingsImageQualityDescription').tr(),
|
||||
@ -450,21 +491,22 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
leading: const Icon(Symbols.image),
|
||||
trailing: DropdownButtonHideUnderline(
|
||||
child: DropdownButton2<FilterQuality>(
|
||||
value: kImageQualityLevel.values.elementAtOrNull(_prefs.getInt('app_image_quality') ?? 3) ??
|
||||
value: kImageQualityLevel.values.elementAtOrNull(
|
||||
_prefs.getInt('app_image_quality') ?? 3) ??
|
||||
FilterQuality.high,
|
||||
isExpanded: true,
|
||||
items: kImageQualityLevel.entries
|
||||
.map(
|
||||
(item) =>
|
||||
DropdownMenuItem<FilterQuality>(
|
||||
(item) => DropdownMenuItem<FilterQuality>(
|
||||
value: item.value,
|
||||
child: Text(item.key).tr().fontSize(14),
|
||||
),
|
||||
)
|
||||
)
|
||||
.toList(),
|
||||
onChanged: (FilterQuality? value) {
|
||||
if (value == null) return;
|
||||
_prefs.setInt('app_image_quality', kImageQualityLevel.values.toList().indexOf(value));
|
||||
_prefs.setInt('app_image_quality',
|
||||
kImageQualityLevel.values.toList().indexOf(value));
|
||||
setState(() {});
|
||||
},
|
||||
buttonStyleData: const ButtonStyleData(
|
||||
@ -486,7 +528,82 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('settingsMisc').bold().fontSize(17).tr().padding(horizontal: 20, bottom: 4),
|
||||
Text('settingsMisc')
|
||||
.bold()
|
||||
.fontSize(17)
|
||||
.tr()
|
||||
.padding(horizontal: 20, bottom: 4),
|
||||
ListTile(
|
||||
leading: const Icon(Symbols.database),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
title: Text('databaseSize').tr(),
|
||||
subtitle: FutureBuilder(
|
||||
future: dt.getDatabaseSize(),
|
||||
builder: (context, snapshot) {
|
||||
if (!snapshot.hasData || kIsWeb) {
|
||||
return Text('unknown').tr();
|
||||
}
|
||||
return Text(
|
||||
snapshot.data!.formatBytes(),
|
||||
style: GoogleFonts.robotoMono(),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Symbols.database_off),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
title: Text('databaseDelete').tr(),
|
||||
subtitle: Text('databaseDeleteDescription').tr(),
|
||||
trailing: const Icon(Symbols.chevron_right),
|
||||
onTap: () async {
|
||||
await dt.removeDatabase();
|
||||
if (!context.mounted) return;
|
||||
HapticFeedback.heavyImpact();
|
||||
context.showSnackbar('databaseDeleted'.tr());
|
||||
setState(() {});
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Symbols.notifications),
|
||||
title: Text('settingsEnablePushNotifications').tr(),
|
||||
subtitle:
|
||||
Text('settingsEnablePushNotificationsDescription').tr(),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
trailing: const Icon(Symbols.chevron_right),
|
||||
onTap: () async {
|
||||
final nty = context.read<NotificationProvider>();
|
||||
try {
|
||||
await nty.registerPushNotifications();
|
||||
if (!context.mounted) return;
|
||||
HapticFeedback.heavyImpact();
|
||||
context.showSnackbar(
|
||||
'settingsEnabledPushNotifications'.tr());
|
||||
} catch (err) {
|
||||
if (!mounted) return;
|
||||
context.showErrorDialog(err);
|
||||
}
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Symbols.refresh),
|
||||
title: Text('stickersReload').tr(),
|
||||
subtitle: Text('stickersReloadDescription').tr(),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
trailing: const Icon(Symbols.chevron_right),
|
||||
onTap: () async {
|
||||
final stickers = context.read<SnStickerProvider>();
|
||||
try {
|
||||
await stickers.listSticker();
|
||||
if (!context.mounted) return;
|
||||
HapticFeedback.heavyImpact();
|
||||
context.showSnackbar('stickersReloaded'.tr());
|
||||
} catch (err) {
|
||||
if (!context.mounted) return;
|
||||
context.showErrorDialog(err);
|
||||
}
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
title: Text('settingsMiscAbout').tr(),
|
||||
subtitle: Text('settingsMiscAboutDescription').tr(),
|
||||
|
464
lib/screens/stickers.dart
Normal file
464
lib/screens/stickers.dart
Normal file
@ -0,0 +1,464 @@
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
import 'package:surface/providers/sn_network.dart';
|
||||
import 'package:surface/providers/sn_sticker.dart';
|
||||
import 'package:surface/providers/userinfo.dart';
|
||||
import 'package:surface/types/attachment.dart';
|
||||
import 'package:surface/widgets/app_bar_leading.dart';
|
||||
import 'package:surface/widgets/attachment/attachment_item.dart';
|
||||
import 'package:surface/widgets/dialog.dart';
|
||||
import 'package:surface/widgets/loading_indicator.dart';
|
||||
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
||||
import 'package:very_good_infinite_list/very_good_infinite_list.dart';
|
||||
|
||||
class StickerScreen extends StatefulWidget {
|
||||
const StickerScreen({super.key});
|
||||
|
||||
@override
|
||||
State<StickerScreen> createState() => _StickerScreenState();
|
||||
}
|
||||
|
||||
class _StickerScreenState extends State<StickerScreen>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late final TabController _tabController =
|
||||
TabController(length: 3, vsync: this);
|
||||
|
||||
bool _isBusy = false;
|
||||
int? _totalCount;
|
||||
final List<SnStickerPack> _packs = List.empty(growable: true);
|
||||
|
||||
Future<void> _fetchPacks() async {
|
||||
try {
|
||||
setState(() => _isBusy = true);
|
||||
final sn = context.read<SnNetworkProvider>();
|
||||
final ua = context.read<UserProvider>();
|
||||
final resp = await sn.client.get(
|
||||
_tabController.index == 1
|
||||
? '/cgi/uc/stickers/packs/own'
|
||||
: '/cgi/uc/stickers/packs',
|
||||
queryParameters: {
|
||||
'take': 10,
|
||||
'offset': _packs.length,
|
||||
if (_tabController.index == 2) 'author': ua.user?.id,
|
||||
},
|
||||
);
|
||||
if (resp.data is Map<String, dynamic>) {
|
||||
_totalCount = resp.data['count'] as int?;
|
||||
final out = List<SnStickerPack>.from(
|
||||
resp.data['data'].map((ele) => SnStickerPack.fromJson(ele)),
|
||||
);
|
||||
_packs.addAll(out);
|
||||
} else {
|
||||
_totalCount = 0;
|
||||
final out = List<SnStickerPack>.from(
|
||||
resp.data.map((ele) => SnStickerPack.fromJson(ele)),
|
||||
);
|
||||
_packs.addAll(out);
|
||||
}
|
||||
} catch (err) {
|
||||
if (!mounted) return;
|
||||
context.showErrorDialog(err);
|
||||
} finally {
|
||||
setState(() => _isBusy = false);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _removePack(SnStickerPack pack) async {
|
||||
try {
|
||||
setState(() => _isBusy = true);
|
||||
final sn = context.read<SnNetworkProvider>();
|
||||
await sn.client.delete('/cgi/uc/stickers/packs/${pack.id}/own');
|
||||
if (!mounted) return;
|
||||
context.showSnackbar('stickersRemoved'.tr());
|
||||
_refreshPacks();
|
||||
} catch (err) {
|
||||
if (!mounted) return;
|
||||
context.showErrorDialog(err);
|
||||
} finally {
|
||||
setState(() => _isBusy = false);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _deletePack(SnStickerPack pack) async {
|
||||
final confirm = await context.showConfirmDialog(
|
||||
'stickersPackDelete'.tr(args: [pack.name]),
|
||||
'stickersPackDeleteDescription'.tr(),
|
||||
);
|
||||
if (!confirm) return;
|
||||
if (!mounted) return;
|
||||
|
||||
try {
|
||||
setState(() => _isBusy = true);
|
||||
final sn = context.read<SnNetworkProvider>();
|
||||
await sn.client.delete('/cgi/uc/stickers/packs/${pack.id}');
|
||||
if (!mounted) return;
|
||||
context.showSnackbar('stickersDeleted'.tr());
|
||||
_refreshPacks();
|
||||
} catch (err) {
|
||||
if (!mounted) return;
|
||||
context.showErrorDialog(err);
|
||||
} finally {
|
||||
setState(() => _isBusy = false);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _refreshPacks() async {
|
||||
_packs.clear();
|
||||
_totalCount = null;
|
||||
await _fetchPacks();
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_fetchPacks();
|
||||
_tabController.addListener(() {
|
||||
if (_tabController.indexIsChanging) {
|
||||
_refreshPacks();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_tabController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AppScaffold(
|
||||
appBar: AppBar(
|
||||
leading: AutoAppBarLeading(),
|
||||
title: Text('screenStickers').tr(),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Symbols.add_circle),
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => _StickerPackCreateDialog(),
|
||||
).then((value) {
|
||||
if (value == true) _refreshPacks();
|
||||
});
|
||||
},
|
||||
),
|
||||
const Gap(8),
|
||||
],
|
||||
bottom: TabBar(
|
||||
controller: _tabController,
|
||||
tabs: [
|
||||
Tab(
|
||||
child: Text('stickersDiscovery'.tr()).textColor(
|
||||
Theme.of(context).appBarTheme.foregroundColor,
|
||||
),
|
||||
),
|
||||
Tab(
|
||||
child: Text('stickersOwned'.tr()).textColor(
|
||||
Theme.of(context).appBarTheme.foregroundColor,
|
||||
),
|
||||
),
|
||||
Tab(
|
||||
child: Text('stickersCreated'.tr()).textColor(
|
||||
Theme.of(context).appBarTheme.foregroundColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
body: MediaQuery.removePadding(
|
||||
context: context,
|
||||
removeTop: true,
|
||||
child: RefreshIndicator(
|
||||
onRefresh: _refreshPacks,
|
||||
child: InfiniteList(
|
||||
itemCount: _packs.length,
|
||||
onFetchData: _fetchPacks,
|
||||
hasReachedMax: _totalCount != null && _packs.length >= _totalCount!,
|
||||
isLoading: _isBusy,
|
||||
itemBuilder: (context, idx) {
|
||||
final pack = _packs[idx];
|
||||
return ListTile(
|
||||
title: Text(pack.name),
|
||||
subtitle: Text(
|
||||
pack.description,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
trailing: _tabController.index == 1
|
||||
? IconButton(
|
||||
onPressed: () {
|
||||
_removePack(pack);
|
||||
},
|
||||
icon: const Icon(Symbols.remove),
|
||||
)
|
||||
: _tabController.index == 2
|
||||
? IconButton(
|
||||
onPressed: () {
|
||||
_deletePack(pack);
|
||||
},
|
||||
icon: const Icon(Symbols.delete),
|
||||
)
|
||||
: null,
|
||||
onTap: () {
|
||||
if (_tabController.index == 0) {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
builder: (context) => _StickerPackAddPopup(pack: pack),
|
||||
).then((value) {
|
||||
if (value == true && _tabController.index == 1) {
|
||||
_refreshPacks();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
GoRouter.of(context).pushNamed(
|
||||
'stickerPack',
|
||||
pathParameters: {
|
||||
'id': pack.id.toString(),
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _StickerPackAddPopup extends StatefulWidget {
|
||||
final SnStickerPack pack;
|
||||
const _StickerPackAddPopup({required this.pack});
|
||||
|
||||
@override
|
||||
State<_StickerPackAddPopup> createState() => _StickerPackAddPopupState();
|
||||
}
|
||||
|
||||
class _StickerPackAddPopupState extends State<_StickerPackAddPopup> {
|
||||
SnStickerPack? _pack;
|
||||
|
||||
bool _isBusy = false;
|
||||
|
||||
Future<void> _fetchPack() async {
|
||||
try {
|
||||
setState(() => _isBusy = true);
|
||||
final sn = context.read<SnNetworkProvider>();
|
||||
final resp =
|
||||
await sn.client.get('/cgi/uc/stickers/packs/${widget.pack.id}');
|
||||
_pack = SnStickerPack.fromJson(resp.data);
|
||||
} catch (err) {
|
||||
if (!mounted) return;
|
||||
context.showErrorDialog(err);
|
||||
} finally {
|
||||
setState(() => _isBusy = false);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_fetchPack();
|
||||
}
|
||||
|
||||
bool _isAdding = false;
|
||||
|
||||
Future<void> _addPack() async {
|
||||
if (_pack == null) return;
|
||||
|
||||
try {
|
||||
setState(() => _isAdding = true);
|
||||
final sn = context.read<SnNetworkProvider>();
|
||||
final stickers = context.read<SnStickerProvider>();
|
||||
await sn.client.post(
|
||||
'/cgi/uc/stickers/packs/${widget.pack.id}/own',
|
||||
);
|
||||
if (!mounted) return;
|
||||
context.showSnackbar('stickersAdded'.tr());
|
||||
if (_pack?.stickers != null) stickers.putSticker(_pack!.stickers!);
|
||||
Navigator.pop(context, true);
|
||||
} catch (err) {
|
||||
if (!mounted) return;
|
||||
context.showErrorDialog(err);
|
||||
} finally {
|
||||
setState(() => _isAdding = false);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(Symbols.add, size: 24),
|
||||
const Gap(16),
|
||||
Text('stickersAdd', style: Theme.of(context).textTheme.titleLarge)
|
||||
.tr(),
|
||||
],
|
||||
).padding(horizontal: 20, top: 16, bottom: 12),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(widget.pack.name).bold(),
|
||||
Text(
|
||||
widget.pack.description,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: _isAdding ? null : _addPack,
|
||||
child: Text('add').tr(),
|
||||
),
|
||||
],
|
||||
).padding(horizontal: 24),
|
||||
LoadingIndicator(isActive: _isBusy),
|
||||
if (_pack?.stickers != null)
|
||||
Expanded(
|
||||
child: GridView.extent(
|
||||
padding: EdgeInsets.only(left: 20, right: 20, top: 8),
|
||||
maxCrossAxisExtent: 48,
|
||||
mainAxisSpacing: 8,
|
||||
crossAxisSpacing: 8,
|
||||
children: _pack!.stickers!
|
||||
.map(
|
||||
(ele) => ClipRRect(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Container(
|
||||
color:
|
||||
Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||
child: AttachmentItem(
|
||||
data: ele.attachment,
|
||||
heroTag: 'sticker-pack-${ele.attachment.rid}',
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _StickerPackCreateDialog extends StatefulWidget {
|
||||
const _StickerPackCreateDialog();
|
||||
|
||||
@override
|
||||
State<_StickerPackCreateDialog> createState() =>
|
||||
_StickerPackCreateDialogState();
|
||||
}
|
||||
|
||||
class _StickerPackCreateDialogState extends State<_StickerPackCreateDialog> {
|
||||
final TextEditingController _nameController = TextEditingController();
|
||||
final TextEditingController _prefixController = TextEditingController();
|
||||
final TextEditingController _descriptionController = TextEditingController();
|
||||
|
||||
bool _isBusy = false;
|
||||
|
||||
Future<void> _createPack() async {
|
||||
if (_nameController.text.isEmpty ||
|
||||
_prefixController.text.isEmpty ||
|
||||
_descriptionController.text.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() => _isBusy = true);
|
||||
|
||||
try {
|
||||
final sn = context.read<SnNetworkProvider>();
|
||||
await sn.client.post(
|
||||
'/cgi/uc/stickers/packs',
|
||||
data: {
|
||||
'name': _nameController.text,
|
||||
'prefix': _prefixController.text,
|
||||
'description': _descriptionController.text,
|
||||
},
|
||||
);
|
||||
if (!mounted) return;
|
||||
Navigator.pop(context, true);
|
||||
} catch (err) {
|
||||
if (!mounted) return;
|
||||
context.showErrorDialog(err);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_nameController.dispose();
|
||||
_prefixController.dispose();
|
||||
_descriptionController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text('stickersPackNew').tr(),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
TextField(
|
||||
controller: _nameController,
|
||||
decoration: InputDecoration(
|
||||
border: const UnderlineInputBorder(),
|
||||
labelText: 'fieldStickerPackName'.tr(),
|
||||
),
|
||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||
),
|
||||
const Gap(4),
|
||||
TextField(
|
||||
controller: _prefixController,
|
||||
decoration: InputDecoration(
|
||||
border: const UnderlineInputBorder(),
|
||||
labelText: 'fieldStickerPackPrefix'.tr(),
|
||||
),
|
||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||
),
|
||||
const Gap(4),
|
||||
TextField(
|
||||
controller: _descriptionController,
|
||||
decoration: InputDecoration(
|
||||
border: const UnderlineInputBorder(),
|
||||
labelText: 'fieldStickerPackDescription'.tr(),
|
||||
),
|
||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: _isBusy
|
||||
? null
|
||||
: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: Text('dialogDismiss').tr(),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: _isBusy ? null : () => _createPack(),
|
||||
child: Text('dialogConfirm').tr(),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
266
lib/screens/stickers/pack_detail.dart
Normal file
266
lib/screens/stickers/pack_detail.dart
Normal file
@ -0,0 +1,266 @@
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
import 'package:surface/providers/sn_network.dart';
|
||||
import 'package:surface/types/attachment.dart';
|
||||
import 'package:surface/widgets/attachment/attachment_input.dart';
|
||||
import 'package:surface/widgets/attachment/attachment_item.dart';
|
||||
import 'package:surface/widgets/dialog.dart';
|
||||
import 'package:surface/widgets/loading_indicator.dart';
|
||||
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
||||
|
||||
class StickerPackScreen extends StatefulWidget {
|
||||
final int id;
|
||||
const StickerPackScreen({super.key, required this.id});
|
||||
|
||||
@override
|
||||
State<StickerPackScreen> createState() => _StickerPackScreenState();
|
||||
}
|
||||
|
||||
class _StickerPackScreenState extends State<StickerPackScreen> {
|
||||
SnStickerPack? _pack;
|
||||
|
||||
Future<void> _fetchPack() async {
|
||||
try {
|
||||
setState(() => _isBusy = true);
|
||||
final sn = context.read<SnNetworkProvider>();
|
||||
final resp = await sn.client.get('/cgi/uc/stickers/packs/${widget.id}');
|
||||
_pack = SnStickerPack.fromJson(resp.data);
|
||||
} catch (err) {
|
||||
if (!mounted) return;
|
||||
context.showErrorDialog(err);
|
||||
} finally {
|
||||
setState(() => _isBusy = false);
|
||||
}
|
||||
}
|
||||
|
||||
bool _isBusy = false;
|
||||
|
||||
Future<void> _deleteSticker(SnSticker sticker) async {
|
||||
final confirm = await context.showConfirmDialog(
|
||||
'stickersDelete'.tr(args: [sticker.name]),
|
||||
'stickersDeleteDescription'.tr(),
|
||||
);
|
||||
if (!confirm) return;
|
||||
if (!mounted) return;
|
||||
|
||||
try {
|
||||
setState(() => _isBusy = true);
|
||||
final sn = context.read<SnNetworkProvider>();
|
||||
await sn.client.delete('/cgi/uc/stickers/${sticker.id}');
|
||||
if (!mounted) return;
|
||||
context.showSnackbar('stickersDeleted'.tr());
|
||||
_fetchPack();
|
||||
} catch (err) {
|
||||
if (!mounted) return;
|
||||
context.showErrorDialog(err);
|
||||
} finally {
|
||||
setState(() => _isBusy = false);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_fetchPack();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AppScaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(_pack?.name ?? 'loading'.tr()),
|
||||
),
|
||||
body: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
LoadingIndicator(isActive: _isBusy),
|
||||
if (_pack != null)
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(_pack!.name).bold(),
|
||||
Text(
|
||||
_pack!.description,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
).padding(horizontal: 24, vertical: 16),
|
||||
const Divider(height: 1),
|
||||
ListTile(
|
||||
leading: const Icon(Symbols.add),
|
||||
title: Text('stickersNew').tr(),
|
||||
subtitle: Text('stickersNewDescription').tr(),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
onTap: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => _StickerCreateDialog(pack: _pack!),
|
||||
).then((value) {
|
||||
if (value) _fetchPack();
|
||||
});
|
||||
},
|
||||
),
|
||||
const Divider(height: 1),
|
||||
if (_pack?.stickers != null)
|
||||
Expanded(
|
||||
child: GridView.extent(
|
||||
padding: EdgeInsets.only(left: 20, right: 20, top: 16),
|
||||
maxCrossAxisExtent: 48,
|
||||
mainAxisSpacing: 8,
|
||||
crossAxisSpacing: 8,
|
||||
children: _pack!.stickers!
|
||||
.map(
|
||||
(ele) => GestureDetector(
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Container(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.surfaceContainerHigh,
|
||||
child: AttachmentItem(
|
||||
data: ele.attachment,
|
||||
heroTag: 'sticker-pack-${ele.attachment.rid}',
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
_deleteSticker(ele);
|
||||
},
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _StickerCreateDialog extends StatefulWidget {
|
||||
final SnStickerPack pack;
|
||||
const _StickerCreateDialog({required this.pack});
|
||||
|
||||
@override
|
||||
State<_StickerCreateDialog> createState() => _StickerCreateDialogState();
|
||||
}
|
||||
|
||||
class _StickerCreateDialogState extends State<_StickerCreateDialog> {
|
||||
final TextEditingController _nameController = TextEditingController();
|
||||
final TextEditingController _aliasController = TextEditingController();
|
||||
final TextEditingController _attachmentController = TextEditingController();
|
||||
|
||||
bool _isBusy = false;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_nameController.dispose();
|
||||
_aliasController.dispose();
|
||||
_attachmentController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _createSticker() async {
|
||||
if (_nameController.text.isEmpty ||
|
||||
_aliasController.text.isEmpty ||
|
||||
_attachmentController.text.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() => _isBusy = true);
|
||||
|
||||
try {
|
||||
final sn = context.read<SnNetworkProvider>();
|
||||
await sn.client.post(
|
||||
'/cgi/uc/stickers',
|
||||
data: {
|
||||
'name': _nameController.text,
|
||||
'alias': _aliasController.text,
|
||||
'attachment_id': _attachmentController.text,
|
||||
'pack_id': widget.pack.id,
|
||||
},
|
||||
);
|
||||
if (!mounted) return;
|
||||
Navigator.pop(context, true);
|
||||
} catch (err) {
|
||||
if (!mounted) return;
|
||||
context.showErrorDialog(err);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text('stickersNew'.tr()),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
TextField(
|
||||
controller: _nameController,
|
||||
decoration: InputDecoration(
|
||||
border: const UnderlineInputBorder(),
|
||||
labelText: 'fieldStickerName'.tr(),
|
||||
),
|
||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||
),
|
||||
const Gap(4),
|
||||
TextField(
|
||||
controller: _aliasController,
|
||||
decoration: InputDecoration(
|
||||
border: const UnderlineInputBorder(),
|
||||
labelText: 'fieldStickerAlias'.tr(),
|
||||
helperText: 'fieldStickerAliasHint'.tr(),
|
||||
),
|
||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||
),
|
||||
const Gap(4),
|
||||
TextField(
|
||||
controller: _attachmentController,
|
||||
decoration: InputDecoration(
|
||||
border: const UnderlineInputBorder(),
|
||||
labelText: 'fieldStickerAttachment'.tr(),
|
||||
),
|
||||
readOnly: true,
|
||||
onTap: () async {
|
||||
final attachment = await showDialog<SnAttachment?>(
|
||||
context: context,
|
||||
builder: (context) => AttachmentInputDialog(
|
||||
title: 'fieldStickerAttachment'.tr(),
|
||||
pool: 'sticker',
|
||||
mediaType: SnMediaType.image,
|
||||
),
|
||||
);
|
||||
if (attachment != null) {
|
||||
setState(() {
|
||||
_attachmentController.text = attachment.rid;
|
||||
});
|
||||
}
|
||||
},
|
||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: _isBusy
|
||||
? null
|
||||
: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: Text('dialogDismiss').tr(),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: _isBusy ? null : () => _createSticker(),
|
||||
child: Text('dialogConfirm').tr(),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -1,5 +1,4 @@
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:hive_flutter/hive_flutter.dart';
|
||||
|
||||
part 'account.freezed.dart';
|
||||
part 'account.g.dart';
|
||||
@ -9,7 +8,7 @@ class SnAccount with _$SnAccount {
|
||||
const SnAccount._();
|
||||
|
||||
const factory SnAccount({
|
||||
@HiveField(0) required int id,
|
||||
required int id,
|
||||
required DateTime createdAt,
|
||||
required DateTime updatedAt,
|
||||
required DateTime? deletedAt,
|
||||
|
@ -20,7 +20,6 @@ SnAccount _$SnAccountFromJson(Map<String, dynamic> json) {
|
||||
|
||||
/// @nodoc
|
||||
mixin _$SnAccount {
|
||||
@HiveField(0)
|
||||
int get id => throw _privateConstructorUsedError;
|
||||
DateTime get createdAt => throw _privateConstructorUsedError;
|
||||
DateTime get updatedAt => throw _privateConstructorUsedError;
|
||||
@ -58,7 +57,7 @@ abstract class $SnAccountCopyWith<$Res> {
|
||||
_$SnAccountCopyWithImpl<$Res, SnAccount>;
|
||||
@useResult
|
||||
$Res call(
|
||||
{@HiveField(0) int id,
|
||||
{int id,
|
||||
DateTime createdAt,
|
||||
DateTime updatedAt,
|
||||
DateTime? deletedAt,
|
||||
@ -226,7 +225,7 @@ abstract class _$$SnAccountImplCopyWith<$Res>
|
||||
@override
|
||||
@useResult
|
||||
$Res call(
|
||||
{@HiveField(0) int id,
|
||||
{int id,
|
||||
DateTime createdAt,
|
||||
DateTime updatedAt,
|
||||
DateTime? deletedAt,
|
||||
@ -374,7 +373,7 @@ class __$$SnAccountImplCopyWithImpl<$Res>
|
||||
@JsonSerializable()
|
||||
class _$SnAccountImpl extends _SnAccount {
|
||||
const _$SnAccountImpl(
|
||||
{@HiveField(0) required this.id,
|
||||
{required this.id,
|
||||
required this.createdAt,
|
||||
required this.updatedAt,
|
||||
required this.deletedAt,
|
||||
@ -403,7 +402,6 @@ class _$SnAccountImpl extends _SnAccount {
|
||||
_$$SnAccountImplFromJson(json);
|
||||
|
||||
@override
|
||||
@HiveField(0)
|
||||
final int id;
|
||||
@override
|
||||
final DateTime createdAt;
|
||||
@ -556,7 +554,7 @@ class _$SnAccountImpl extends _SnAccount {
|
||||
|
||||
abstract class _SnAccount extends SnAccount {
|
||||
const factory _SnAccount(
|
||||
{@HiveField(0) required final int id,
|
||||
{required final int id,
|
||||
required final DateTime createdAt,
|
||||
required final DateTime updatedAt,
|
||||
required final DateTime? deletedAt,
|
||||
@ -582,7 +580,6 @@ abstract class _SnAccount extends SnAccount {
|
||||
_$SnAccountImpl.fromJson;
|
||||
|
||||
@override
|
||||
@HiveField(0)
|
||||
int get id;
|
||||
@override
|
||||
DateTime get createdAt;
|
||||
|
@ -177,3 +177,14 @@ class SnStickerPack with _$SnStickerPack {
|
||||
|
||||
factory SnStickerPack.fromJson(Map<String, Object?> json) => _$SnStickerPackFromJson(json);
|
||||
}
|
||||
|
||||
@freezed
|
||||
class SnAttachmentBilling with _$SnAttachmentBilling {
|
||||
const factory SnAttachmentBilling({
|
||||
required int currentBytes,
|
||||
required int discountFileSize,
|
||||
required double includedRatio,
|
||||
}) = _SnAttachmentBilling;
|
||||
|
||||
factory SnAttachmentBilling.fromJson(Map<String, Object?> json) => _$SnAttachmentBillingFromJson(json);
|
||||
}
|
||||
|
@ -3007,3 +3007,195 @@ abstract class _SnStickerPack implements SnStickerPack {
|
||||
_$$SnStickerPackImplCopyWith<_$SnStickerPackImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
SnAttachmentBilling _$SnAttachmentBillingFromJson(Map<String, dynamic> json) {
|
||||
return _SnAttachmentBilling.fromJson(json);
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$SnAttachmentBilling {
|
||||
int get currentBytes => throw _privateConstructorUsedError;
|
||||
int get discountFileSize => throw _privateConstructorUsedError;
|
||||
double get includedRatio => throw _privateConstructorUsedError;
|
||||
|
||||
/// Serializes this SnAttachmentBilling to a JSON map.
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
|
||||
/// Create a copy of SnAttachmentBilling
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
$SnAttachmentBillingCopyWith<SnAttachmentBilling> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $SnAttachmentBillingCopyWith<$Res> {
|
||||
factory $SnAttachmentBillingCopyWith(
|
||||
SnAttachmentBilling value, $Res Function(SnAttachmentBilling) then) =
|
||||
_$SnAttachmentBillingCopyWithImpl<$Res, SnAttachmentBilling>;
|
||||
@useResult
|
||||
$Res call({int currentBytes, int discountFileSize, double includedRatio});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$SnAttachmentBillingCopyWithImpl<$Res, $Val extends SnAttachmentBilling>
|
||||
implements $SnAttachmentBillingCopyWith<$Res> {
|
||||
_$SnAttachmentBillingCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
/// Create a copy of SnAttachmentBilling
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? currentBytes = null,
|
||||
Object? discountFileSize = null,
|
||||
Object? includedRatio = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
currentBytes: null == currentBytes
|
||||
? _value.currentBytes
|
||||
: currentBytes // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
discountFileSize: null == discountFileSize
|
||||
? _value.discountFileSize
|
||||
: discountFileSize // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
includedRatio: null == includedRatio
|
||||
? _value.includedRatio
|
||||
: includedRatio // ignore: cast_nullable_to_non_nullable
|
||||
as double,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$SnAttachmentBillingImplCopyWith<$Res>
|
||||
implements $SnAttachmentBillingCopyWith<$Res> {
|
||||
factory _$$SnAttachmentBillingImplCopyWith(_$SnAttachmentBillingImpl value,
|
||||
$Res Function(_$SnAttachmentBillingImpl) then) =
|
||||
__$$SnAttachmentBillingImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call({int currentBytes, int discountFileSize, double includedRatio});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$SnAttachmentBillingImplCopyWithImpl<$Res>
|
||||
extends _$SnAttachmentBillingCopyWithImpl<$Res, _$SnAttachmentBillingImpl>
|
||||
implements _$$SnAttachmentBillingImplCopyWith<$Res> {
|
||||
__$$SnAttachmentBillingImplCopyWithImpl(_$SnAttachmentBillingImpl _value,
|
||||
$Res Function(_$SnAttachmentBillingImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
/// Create a copy of SnAttachmentBilling
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? currentBytes = null,
|
||||
Object? discountFileSize = null,
|
||||
Object? includedRatio = null,
|
||||
}) {
|
||||
return _then(_$SnAttachmentBillingImpl(
|
||||
currentBytes: null == currentBytes
|
||||
? _value.currentBytes
|
||||
: currentBytes // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
discountFileSize: null == discountFileSize
|
||||
? _value.discountFileSize
|
||||
: discountFileSize // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
includedRatio: null == includedRatio
|
||||
? _value.includedRatio
|
||||
: includedRatio // ignore: cast_nullable_to_non_nullable
|
||||
as double,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _$SnAttachmentBillingImpl implements _SnAttachmentBilling {
|
||||
const _$SnAttachmentBillingImpl(
|
||||
{required this.currentBytes,
|
||||
required this.discountFileSize,
|
||||
required this.includedRatio});
|
||||
|
||||
factory _$SnAttachmentBillingImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$SnAttachmentBillingImplFromJson(json);
|
||||
|
||||
@override
|
||||
final int currentBytes;
|
||||
@override
|
||||
final int discountFileSize;
|
||||
@override
|
||||
final double includedRatio;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SnAttachmentBilling(currentBytes: $currentBytes, discountFileSize: $discountFileSize, includedRatio: $includedRatio)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$SnAttachmentBillingImpl &&
|
||||
(identical(other.currentBytes, currentBytes) ||
|
||||
other.currentBytes == currentBytes) &&
|
||||
(identical(other.discountFileSize, discountFileSize) ||
|
||||
other.discountFileSize == discountFileSize) &&
|
||||
(identical(other.includedRatio, includedRatio) ||
|
||||
other.includedRatio == includedRatio));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode =>
|
||||
Object.hash(runtimeType, currentBytes, discountFileSize, includedRatio);
|
||||
|
||||
/// Create a copy of SnAttachmentBilling
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$SnAttachmentBillingImplCopyWith<_$SnAttachmentBillingImpl> get copyWith =>
|
||||
__$$SnAttachmentBillingImplCopyWithImpl<_$SnAttachmentBillingImpl>(
|
||||
this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$$SnAttachmentBillingImplToJson(
|
||||
this,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _SnAttachmentBilling implements SnAttachmentBilling {
|
||||
const factory _SnAttachmentBilling(
|
||||
{required final int currentBytes,
|
||||
required final int discountFileSize,
|
||||
required final double includedRatio}) = _$SnAttachmentBillingImpl;
|
||||
|
||||
factory _SnAttachmentBilling.fromJson(Map<String, dynamic> json) =
|
||||
_$SnAttachmentBillingImpl.fromJson;
|
||||
|
||||
@override
|
||||
int get currentBytes;
|
||||
@override
|
||||
int get discountFileSize;
|
||||
@override
|
||||
double get includedRatio;
|
||||
|
||||
/// Create a copy of SnAttachmentBilling
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
_$$SnAttachmentBillingImplCopyWith<_$SnAttachmentBillingImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
@ -281,3 +281,19 @@ Map<String, dynamic> _$$SnStickerPackImplToJson(_$SnStickerPackImpl instance) =>
|
||||
'stickers': instance.stickers?.map((e) => e.toJson()).toList(),
|
||||
'account_id': instance.accountId,
|
||||
};
|
||||
|
||||
_$SnAttachmentBillingImpl _$$SnAttachmentBillingImplFromJson(
|
||||
Map<String, dynamic> json) =>
|
||||
_$SnAttachmentBillingImpl(
|
||||
currentBytes: (json['current_bytes'] as num).toInt(),
|
||||
discountFileSize: (json['discount_file_size'] as num).toInt(),
|
||||
includedRatio: (json['included_ratio'] as num).toDouble(),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$SnAttachmentBillingImplToJson(
|
||||
_$SnAttachmentBillingImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'current_bytes': instance.currentBytes,
|
||||
'discount_file_size': instance.discountFileSize,
|
||||
'included_ratio': instance.includedRatio,
|
||||
};
|
||||
|
@ -1,5 +1,4 @@
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:hive_flutter/hive_flutter.dart';
|
||||
import 'package:livekit_client/livekit_client.dart';
|
||||
import 'package:surface/types/account.dart';
|
||||
import 'package:surface/types/attachment.dart';
|
||||
@ -12,23 +11,22 @@ part 'chat.g.dart';
|
||||
class SnChannel with _$SnChannel {
|
||||
const SnChannel._();
|
||||
|
||||
@HiveType(typeId: 2)
|
||||
const factory SnChannel({
|
||||
@HiveField(0) required int id,
|
||||
@HiveField(1) required DateTime createdAt,
|
||||
@HiveField(2) required DateTime updatedAt,
|
||||
@HiveField(3) required dynamic deletedAt,
|
||||
@HiveField(4) required String alias,
|
||||
@HiveField(5) required String name,
|
||||
@HiveField(6) required String description,
|
||||
@HiveField(7) required List<SnChannelMember>? members,
|
||||
required int id,
|
||||
required DateTime createdAt,
|
||||
required DateTime updatedAt,
|
||||
required dynamic deletedAt,
|
||||
required String alias,
|
||||
required String name,
|
||||
required String description,
|
||||
required List<SnChannelMember>? members,
|
||||
List<SnChatMessage>? messages,
|
||||
@HiveField(8) required int type,
|
||||
@HiveField(9) required int accountId,
|
||||
@HiveField(10) required SnRealm? realm,
|
||||
@HiveField(11) required int? realmId,
|
||||
@HiveField(12) required bool isPublic,
|
||||
@HiveField(13) required bool isCommunity,
|
||||
required int type,
|
||||
required int accountId,
|
||||
required SnRealm? realm,
|
||||
required int? realmId,
|
||||
required bool isPublic,
|
||||
required bool isCommunity,
|
||||
}) = _SnChannel;
|
||||
|
||||
factory SnChannel.fromJson(Map<String, dynamic> json) =>
|
||||
@ -42,19 +40,18 @@ class SnChannel with _$SnChannel {
|
||||
class SnChannelMember with _$SnChannelMember {
|
||||
const SnChannelMember._();
|
||||
|
||||
@HiveType(typeId: 3)
|
||||
const factory SnChannelMember({
|
||||
@HiveField(0) required int id,
|
||||
@HiveField(1) required DateTime createdAt,
|
||||
@HiveField(2) required DateTime updatedAt,
|
||||
@HiveField(3) required DateTime? deletedAt,
|
||||
@HiveField(4) required int channelId,
|
||||
@HiveField(5) required int accountId,
|
||||
@HiveField(6) required String? nick,
|
||||
@HiveField(7) required SnChannel? channel,
|
||||
@HiveField(8) required SnAccount? account,
|
||||
required int id,
|
||||
required DateTime createdAt,
|
||||
required DateTime updatedAt,
|
||||
required DateTime? deletedAt,
|
||||
required int channelId,
|
||||
required int accountId,
|
||||
required String? nick,
|
||||
required SnChannel? channel,
|
||||
required SnAccount? account,
|
||||
@Default(0) int notify,
|
||||
@HiveField(9) required int powerLevel,
|
||||
required int powerLevel,
|
||||
dynamic calls,
|
||||
dynamic events,
|
||||
}) = _SnChannelMember;
|
||||
@ -67,21 +64,20 @@ class SnChannelMember with _$SnChannelMember {
|
||||
class SnChatMessage with _$SnChatMessage {
|
||||
const SnChatMessage._();
|
||||
|
||||
@HiveType(typeId: 4)
|
||||
const factory SnChatMessage({
|
||||
@HiveField(0) required int id,
|
||||
@HiveField(1) required DateTime createdAt,
|
||||
@HiveField(2) required DateTime updatedAt,
|
||||
@HiveField(3) required DateTime? deletedAt,
|
||||
@HiveField(4) required String uuid,
|
||||
@HiveField(5) @Default({}) Map<String, dynamic> body,
|
||||
@HiveField(6) required String type,
|
||||
@HiveField(7) required SnChannel channel,
|
||||
@HiveField(8) required SnChannelMember sender,
|
||||
@HiveField(9) required int channelId,
|
||||
@HiveField(10) required int senderId,
|
||||
@HiveField(11) required int? quoteEventId,
|
||||
@HiveField(12) required int? relatedEventId,
|
||||
required int id,
|
||||
required DateTime createdAt,
|
||||
required DateTime updatedAt,
|
||||
required DateTime? deletedAt,
|
||||
required String uuid,
|
||||
@Default({}) Map<String, dynamic> body,
|
||||
required String type,
|
||||
required SnChannel channel,
|
||||
required SnChannelMember sender,
|
||||
required int channelId,
|
||||
required int senderId,
|
||||
required int? quoteEventId,
|
||||
required int? relatedEventId,
|
||||
SnChatMessagePreload? preload,
|
||||
}) = _SnChatMessage;
|
||||
|
||||
|
@ -20,34 +20,20 @@ SnChannel _$SnChannelFromJson(Map<String, dynamic> json) {
|
||||
|
||||
/// @nodoc
|
||||
mixin _$SnChannel {
|
||||
@HiveField(0)
|
||||
int get id => throw _privateConstructorUsedError;
|
||||
@HiveField(1)
|
||||
DateTime get createdAt => throw _privateConstructorUsedError;
|
||||
@HiveField(2)
|
||||
DateTime get updatedAt => throw _privateConstructorUsedError;
|
||||
@HiveField(3)
|
||||
dynamic get deletedAt => throw _privateConstructorUsedError;
|
||||
@HiveField(4)
|
||||
String get alias => throw _privateConstructorUsedError;
|
||||
@HiveField(5)
|
||||
String get name => throw _privateConstructorUsedError;
|
||||
@HiveField(6)
|
||||
String get description => throw _privateConstructorUsedError;
|
||||
@HiveField(7)
|
||||
List<SnChannelMember>? get members => throw _privateConstructorUsedError;
|
||||
List<SnChatMessage>? get messages => throw _privateConstructorUsedError;
|
||||
@HiveField(8)
|
||||
int get type => throw _privateConstructorUsedError;
|
||||
@HiveField(9)
|
||||
int get accountId => throw _privateConstructorUsedError;
|
||||
@HiveField(10)
|
||||
SnRealm? get realm => throw _privateConstructorUsedError;
|
||||
@HiveField(11)
|
||||
int? get realmId => throw _privateConstructorUsedError;
|
||||
@HiveField(12)
|
||||
bool get isPublic => throw _privateConstructorUsedError;
|
||||
@HiveField(13)
|
||||
bool get isCommunity => throw _privateConstructorUsedError;
|
||||
|
||||
/// Serializes this SnChannel to a JSON map.
|
||||
@ -66,21 +52,21 @@ abstract class $SnChannelCopyWith<$Res> {
|
||||
_$SnChannelCopyWithImpl<$Res, SnChannel>;
|
||||
@useResult
|
||||
$Res call(
|
||||
{@HiveField(0) int id,
|
||||
@HiveField(1) DateTime createdAt,
|
||||
@HiveField(2) DateTime updatedAt,
|
||||
@HiveField(3) dynamic deletedAt,
|
||||
@HiveField(4) String alias,
|
||||
@HiveField(5) String name,
|
||||
@HiveField(6) String description,
|
||||
@HiveField(7) List<SnChannelMember>? members,
|
||||
{int id,
|
||||
DateTime createdAt,
|
||||
DateTime updatedAt,
|
||||
dynamic deletedAt,
|
||||
String alias,
|
||||
String name,
|
||||
String description,
|
||||
List<SnChannelMember>? members,
|
||||
List<SnChatMessage>? messages,
|
||||
@HiveField(8) int type,
|
||||
@HiveField(9) int accountId,
|
||||
@HiveField(10) SnRealm? realm,
|
||||
@HiveField(11) int? realmId,
|
||||
@HiveField(12) bool isPublic,
|
||||
@HiveField(13) bool isCommunity});
|
||||
int type,
|
||||
int accountId,
|
||||
SnRealm? realm,
|
||||
int? realmId,
|
||||
bool isPublic,
|
||||
bool isCommunity});
|
||||
|
||||
$SnRealmCopyWith<$Res>? get realm;
|
||||
}
|
||||
@ -204,21 +190,21 @@ abstract class _$$SnChannelImplCopyWith<$Res>
|
||||
@override
|
||||
@useResult
|
||||
$Res call(
|
||||
{@HiveField(0) int id,
|
||||
@HiveField(1) DateTime createdAt,
|
||||
@HiveField(2) DateTime updatedAt,
|
||||
@HiveField(3) dynamic deletedAt,
|
||||
@HiveField(4) String alias,
|
||||
@HiveField(5) String name,
|
||||
@HiveField(6) String description,
|
||||
@HiveField(7) List<SnChannelMember>? members,
|
||||
{int id,
|
||||
DateTime createdAt,
|
||||
DateTime updatedAt,
|
||||
dynamic deletedAt,
|
||||
String alias,
|
||||
String name,
|
||||
String description,
|
||||
List<SnChannelMember>? members,
|
||||
List<SnChatMessage>? messages,
|
||||
@HiveField(8) int type,
|
||||
@HiveField(9) int accountId,
|
||||
@HiveField(10) SnRealm? realm,
|
||||
@HiveField(11) int? realmId,
|
||||
@HiveField(12) bool isPublic,
|
||||
@HiveField(13) bool isCommunity});
|
||||
int type,
|
||||
int accountId,
|
||||
SnRealm? realm,
|
||||
int? realmId,
|
||||
bool isPublic,
|
||||
bool isCommunity});
|
||||
|
||||
@override
|
||||
$SnRealmCopyWith<$Res>? get realm;
|
||||
@ -320,24 +306,23 @@ class __$$SnChannelImplCopyWithImpl<$Res>
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
@HiveType(typeId: 2)
|
||||
class _$SnChannelImpl extends _SnChannel {
|
||||
const _$SnChannelImpl(
|
||||
{@HiveField(0) required this.id,
|
||||
@HiveField(1) required this.createdAt,
|
||||
@HiveField(2) required this.updatedAt,
|
||||
@HiveField(3) required this.deletedAt,
|
||||
@HiveField(4) required this.alias,
|
||||
@HiveField(5) required this.name,
|
||||
@HiveField(6) required this.description,
|
||||
@HiveField(7) required final List<SnChannelMember>? members,
|
||||
{required this.id,
|
||||
required this.createdAt,
|
||||
required this.updatedAt,
|
||||
required this.deletedAt,
|
||||
required this.alias,
|
||||
required this.name,
|
||||
required this.description,
|
||||
required final List<SnChannelMember>? members,
|
||||
final List<SnChatMessage>? messages,
|
||||
@HiveField(8) required this.type,
|
||||
@HiveField(9) required this.accountId,
|
||||
@HiveField(10) required this.realm,
|
||||
@HiveField(11) required this.realmId,
|
||||
@HiveField(12) required this.isPublic,
|
||||
@HiveField(13) required this.isCommunity})
|
||||
required this.type,
|
||||
required this.accountId,
|
||||
required this.realm,
|
||||
required this.realmId,
|
||||
required this.isPublic,
|
||||
required this.isCommunity})
|
||||
: _members = members,
|
||||
_messages = messages,
|
||||
super._();
|
||||
@ -346,29 +331,21 @@ class _$SnChannelImpl extends _SnChannel {
|
||||
_$$SnChannelImplFromJson(json);
|
||||
|
||||
@override
|
||||
@HiveField(0)
|
||||
final int id;
|
||||
@override
|
||||
@HiveField(1)
|
||||
final DateTime createdAt;
|
||||
@override
|
||||
@HiveField(2)
|
||||
final DateTime updatedAt;
|
||||
@override
|
||||
@HiveField(3)
|
||||
final dynamic deletedAt;
|
||||
@override
|
||||
@HiveField(4)
|
||||
final String alias;
|
||||
@override
|
||||
@HiveField(5)
|
||||
final String name;
|
||||
@override
|
||||
@HiveField(6)
|
||||
final String description;
|
||||
final List<SnChannelMember>? _members;
|
||||
@override
|
||||
@HiveField(7)
|
||||
List<SnChannelMember>? get members {
|
||||
final value = _members;
|
||||
if (value == null) return null;
|
||||
@ -388,22 +365,16 @@ class _$SnChannelImpl extends _SnChannel {
|
||||
}
|
||||
|
||||
@override
|
||||
@HiveField(8)
|
||||
final int type;
|
||||
@override
|
||||
@HiveField(9)
|
||||
final int accountId;
|
||||
@override
|
||||
@HiveField(10)
|
||||
final SnRealm? realm;
|
||||
@override
|
||||
@HiveField(11)
|
||||
final int? realmId;
|
||||
@override
|
||||
@HiveField(12)
|
||||
final bool isPublic;
|
||||
@override
|
||||
@HiveField(13)
|
||||
final bool isCommunity;
|
||||
|
||||
@override
|
||||
@ -477,69 +448,55 @@ class _$SnChannelImpl extends _SnChannel {
|
||||
|
||||
abstract class _SnChannel extends SnChannel {
|
||||
const factory _SnChannel(
|
||||
{@HiveField(0) required final int id,
|
||||
@HiveField(1) required final DateTime createdAt,
|
||||
@HiveField(2) required final DateTime updatedAt,
|
||||
@HiveField(3) required final dynamic deletedAt,
|
||||
@HiveField(4) required final String alias,
|
||||
@HiveField(5) required final String name,
|
||||
@HiveField(6) required final String description,
|
||||
@HiveField(7) required final List<SnChannelMember>? members,
|
||||
{required final int id,
|
||||
required final DateTime createdAt,
|
||||
required final DateTime updatedAt,
|
||||
required final dynamic deletedAt,
|
||||
required final String alias,
|
||||
required final String name,
|
||||
required final String description,
|
||||
required final List<SnChannelMember>? members,
|
||||
final List<SnChatMessage>? messages,
|
||||
@HiveField(8) required final int type,
|
||||
@HiveField(9) required final int accountId,
|
||||
@HiveField(10) required final SnRealm? realm,
|
||||
@HiveField(11) required final int? realmId,
|
||||
@HiveField(12) required final bool isPublic,
|
||||
@HiveField(13) required final bool isCommunity}) = _$SnChannelImpl;
|
||||
required final int type,
|
||||
required final int accountId,
|
||||
required final SnRealm? realm,
|
||||
required final int? realmId,
|
||||
required final bool isPublic,
|
||||
required final bool isCommunity}) = _$SnChannelImpl;
|
||||
const _SnChannel._() : super._();
|
||||
|
||||
factory _SnChannel.fromJson(Map<String, dynamic> json) =
|
||||
_$SnChannelImpl.fromJson;
|
||||
|
||||
@override
|
||||
@HiveField(0)
|
||||
int get id;
|
||||
@override
|
||||
@HiveField(1)
|
||||
DateTime get createdAt;
|
||||
@override
|
||||
@HiveField(2)
|
||||
DateTime get updatedAt;
|
||||
@override
|
||||
@HiveField(3)
|
||||
dynamic get deletedAt;
|
||||
@override
|
||||
@HiveField(4)
|
||||
String get alias;
|
||||
@override
|
||||
@HiveField(5)
|
||||
String get name;
|
||||
@override
|
||||
@HiveField(6)
|
||||
String get description;
|
||||
@override
|
||||
@HiveField(7)
|
||||
List<SnChannelMember>? get members;
|
||||
@override
|
||||
List<SnChatMessage>? get messages;
|
||||
@override
|
||||
@HiveField(8)
|
||||
int get type;
|
||||
@override
|
||||
@HiveField(9)
|
||||
int get accountId;
|
||||
@override
|
||||
@HiveField(10)
|
||||
SnRealm? get realm;
|
||||
@override
|
||||
@HiveField(11)
|
||||
int? get realmId;
|
||||
@override
|
||||
@HiveField(12)
|
||||
bool get isPublic;
|
||||
@override
|
||||
@HiveField(13)
|
||||
bool get isCommunity;
|
||||
|
||||
/// Create a copy of SnChannel
|
||||
@ -556,26 +513,16 @@ SnChannelMember _$SnChannelMemberFromJson(Map<String, dynamic> json) {
|
||||
|
||||
/// @nodoc
|
||||
mixin _$SnChannelMember {
|
||||
@HiveField(0)
|
||||
int get id => throw _privateConstructorUsedError;
|
||||
@HiveField(1)
|
||||
DateTime get createdAt => throw _privateConstructorUsedError;
|
||||
@HiveField(2)
|
||||
DateTime get updatedAt => throw _privateConstructorUsedError;
|
||||
@HiveField(3)
|
||||
DateTime? get deletedAt => throw _privateConstructorUsedError;
|
||||
@HiveField(4)
|
||||
int get channelId => throw _privateConstructorUsedError;
|
||||
@HiveField(5)
|
||||
int get accountId => throw _privateConstructorUsedError;
|
||||
@HiveField(6)
|
||||
String? get nick => throw _privateConstructorUsedError;
|
||||
@HiveField(7)
|
||||
SnChannel? get channel => throw _privateConstructorUsedError;
|
||||
@HiveField(8)
|
||||
SnAccount? get account => throw _privateConstructorUsedError;
|
||||
int get notify => throw _privateConstructorUsedError;
|
||||
@HiveField(9)
|
||||
int get powerLevel => throw _privateConstructorUsedError;
|
||||
dynamic get calls => throw _privateConstructorUsedError;
|
||||
dynamic get events => throw _privateConstructorUsedError;
|
||||
@ -597,17 +544,17 @@ abstract class $SnChannelMemberCopyWith<$Res> {
|
||||
_$SnChannelMemberCopyWithImpl<$Res, SnChannelMember>;
|
||||
@useResult
|
||||
$Res call(
|
||||
{@HiveField(0) int id,
|
||||
@HiveField(1) DateTime createdAt,
|
||||
@HiveField(2) DateTime updatedAt,
|
||||
@HiveField(3) DateTime? deletedAt,
|
||||
@HiveField(4) int channelId,
|
||||
@HiveField(5) int accountId,
|
||||
@HiveField(6) String? nick,
|
||||
@HiveField(7) SnChannel? channel,
|
||||
@HiveField(8) SnAccount? account,
|
||||
{int id,
|
||||
DateTime createdAt,
|
||||
DateTime updatedAt,
|
||||
DateTime? deletedAt,
|
||||
int channelId,
|
||||
int accountId,
|
||||
String? nick,
|
||||
SnChannel? channel,
|
||||
SnAccount? account,
|
||||
int notify,
|
||||
@HiveField(9) int powerLevel,
|
||||
int powerLevel,
|
||||
dynamic calls,
|
||||
dynamic events});
|
||||
|
||||
@ -738,17 +685,17 @@ abstract class _$$SnChannelMemberImplCopyWith<$Res>
|
||||
@override
|
||||
@useResult
|
||||
$Res call(
|
||||
{@HiveField(0) int id,
|
||||
@HiveField(1) DateTime createdAt,
|
||||
@HiveField(2) DateTime updatedAt,
|
||||
@HiveField(3) DateTime? deletedAt,
|
||||
@HiveField(4) int channelId,
|
||||
@HiveField(5) int accountId,
|
||||
@HiveField(6) String? nick,
|
||||
@HiveField(7) SnChannel? channel,
|
||||
@HiveField(8) SnAccount? account,
|
||||
{int id,
|
||||
DateTime createdAt,
|
||||
DateTime updatedAt,
|
||||
DateTime? deletedAt,
|
||||
int channelId,
|
||||
int accountId,
|
||||
String? nick,
|
||||
SnChannel? channel,
|
||||
SnAccount? account,
|
||||
int notify,
|
||||
@HiveField(9) int powerLevel,
|
||||
int powerLevel,
|
||||
dynamic calls,
|
||||
dynamic events});
|
||||
|
||||
@ -844,20 +791,19 @@ class __$$SnChannelMemberImplCopyWithImpl<$Res>
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
@HiveType(typeId: 3)
|
||||
class _$SnChannelMemberImpl extends _SnChannelMember {
|
||||
const _$SnChannelMemberImpl(
|
||||
{@HiveField(0) required this.id,
|
||||
@HiveField(1) required this.createdAt,
|
||||
@HiveField(2) required this.updatedAt,
|
||||
@HiveField(3) required this.deletedAt,
|
||||
@HiveField(4) required this.channelId,
|
||||
@HiveField(5) required this.accountId,
|
||||
@HiveField(6) required this.nick,
|
||||
@HiveField(7) required this.channel,
|
||||
@HiveField(8) required this.account,
|
||||
{required this.id,
|
||||
required this.createdAt,
|
||||
required this.updatedAt,
|
||||
required this.deletedAt,
|
||||
required this.channelId,
|
||||
required this.accountId,
|
||||
required this.nick,
|
||||
required this.channel,
|
||||
required this.account,
|
||||
this.notify = 0,
|
||||
@HiveField(9) required this.powerLevel,
|
||||
required this.powerLevel,
|
||||
this.calls,
|
||||
this.events})
|
||||
: super._();
|
||||
@ -866,37 +812,27 @@ class _$SnChannelMemberImpl extends _SnChannelMember {
|
||||
_$$SnChannelMemberImplFromJson(json);
|
||||
|
||||
@override
|
||||
@HiveField(0)
|
||||
final int id;
|
||||
@override
|
||||
@HiveField(1)
|
||||
final DateTime createdAt;
|
||||
@override
|
||||
@HiveField(2)
|
||||
final DateTime updatedAt;
|
||||
@override
|
||||
@HiveField(3)
|
||||
final DateTime? deletedAt;
|
||||
@override
|
||||
@HiveField(4)
|
||||
final int channelId;
|
||||
@override
|
||||
@HiveField(5)
|
||||
final int accountId;
|
||||
@override
|
||||
@HiveField(6)
|
||||
final String? nick;
|
||||
@override
|
||||
@HiveField(7)
|
||||
final SnChannel? channel;
|
||||
@override
|
||||
@HiveField(8)
|
||||
final SnAccount? account;
|
||||
@override
|
||||
@JsonKey()
|
||||
final int notify;
|
||||
@override
|
||||
@HiveField(9)
|
||||
final int powerLevel;
|
||||
@override
|
||||
final dynamic calls;
|
||||
@ -971,17 +907,17 @@ class _$SnChannelMemberImpl extends _SnChannelMember {
|
||||
|
||||
abstract class _SnChannelMember extends SnChannelMember {
|
||||
const factory _SnChannelMember(
|
||||
{@HiveField(0) required final int id,
|
||||
@HiveField(1) required final DateTime createdAt,
|
||||
@HiveField(2) required final DateTime updatedAt,
|
||||
@HiveField(3) required final DateTime? deletedAt,
|
||||
@HiveField(4) required final int channelId,
|
||||
@HiveField(5) required final int accountId,
|
||||
@HiveField(6) required final String? nick,
|
||||
@HiveField(7) required final SnChannel? channel,
|
||||
@HiveField(8) required final SnAccount? account,
|
||||
{required final int id,
|
||||
required final DateTime createdAt,
|
||||
required final DateTime updatedAt,
|
||||
required final DateTime? deletedAt,
|
||||
required final int channelId,
|
||||
required final int accountId,
|
||||
required final String? nick,
|
||||
required final SnChannel? channel,
|
||||
required final SnAccount? account,
|
||||
final int notify,
|
||||
@HiveField(9) required final int powerLevel,
|
||||
required final int powerLevel,
|
||||
final dynamic calls,
|
||||
final dynamic events}) = _$SnChannelMemberImpl;
|
||||
const _SnChannelMember._() : super._();
|
||||
@ -990,36 +926,26 @@ abstract class _SnChannelMember extends SnChannelMember {
|
||||
_$SnChannelMemberImpl.fromJson;
|
||||
|
||||
@override
|
||||
@HiveField(0)
|
||||
int get id;
|
||||
@override
|
||||
@HiveField(1)
|
||||
DateTime get createdAt;
|
||||
@override
|
||||
@HiveField(2)
|
||||
DateTime get updatedAt;
|
||||
@override
|
||||
@HiveField(3)
|
||||
DateTime? get deletedAt;
|
||||
@override
|
||||
@HiveField(4)
|
||||
int get channelId;
|
||||
@override
|
||||
@HiveField(5)
|
||||
int get accountId;
|
||||
@override
|
||||
@HiveField(6)
|
||||
String? get nick;
|
||||
@override
|
||||
@HiveField(7)
|
||||
SnChannel? get channel;
|
||||
@override
|
||||
@HiveField(8)
|
||||
SnAccount? get account;
|
||||
@override
|
||||
int get notify;
|
||||
@override
|
||||
@HiveField(9)
|
||||
int get powerLevel;
|
||||
@override
|
||||
dynamic get calls;
|
||||
@ -1040,31 +966,18 @@ SnChatMessage _$SnChatMessageFromJson(Map<String, dynamic> json) {
|
||||
|
||||
/// @nodoc
|
||||
mixin _$SnChatMessage {
|
||||
@HiveField(0)
|
||||
int get id => throw _privateConstructorUsedError;
|
||||
@HiveField(1)
|
||||
DateTime get createdAt => throw _privateConstructorUsedError;
|
||||
@HiveField(2)
|
||||
DateTime get updatedAt => throw _privateConstructorUsedError;
|
||||
@HiveField(3)
|
||||
DateTime? get deletedAt => throw _privateConstructorUsedError;
|
||||
@HiveField(4)
|
||||
String get uuid => throw _privateConstructorUsedError;
|
||||
@HiveField(5)
|
||||
Map<String, dynamic> get body => throw _privateConstructorUsedError;
|
||||
@HiveField(6)
|
||||
String get type => throw _privateConstructorUsedError;
|
||||
@HiveField(7)
|
||||
SnChannel get channel => throw _privateConstructorUsedError;
|
||||
@HiveField(8)
|
||||
SnChannelMember get sender => throw _privateConstructorUsedError;
|
||||
@HiveField(9)
|
||||
int get channelId => throw _privateConstructorUsedError;
|
||||
@HiveField(10)
|
||||
int get senderId => throw _privateConstructorUsedError;
|
||||
@HiveField(11)
|
||||
int? get quoteEventId => throw _privateConstructorUsedError;
|
||||
@HiveField(12)
|
||||
int? get relatedEventId => throw _privateConstructorUsedError;
|
||||
SnChatMessagePreload? get preload => throw _privateConstructorUsedError;
|
||||
|
||||
@ -1085,19 +998,19 @@ abstract class $SnChatMessageCopyWith<$Res> {
|
||||
_$SnChatMessageCopyWithImpl<$Res, SnChatMessage>;
|
||||
@useResult
|
||||
$Res call(
|
||||
{@HiveField(0) int id,
|
||||
@HiveField(1) DateTime createdAt,
|
||||
@HiveField(2) DateTime updatedAt,
|
||||
@HiveField(3) DateTime? deletedAt,
|
||||
@HiveField(4) String uuid,
|
||||
@HiveField(5) Map<String, dynamic> body,
|
||||
@HiveField(6) String type,
|
||||
@HiveField(7) SnChannel channel,
|
||||
@HiveField(8) SnChannelMember sender,
|
||||
@HiveField(9) int channelId,
|
||||
@HiveField(10) int senderId,
|
||||
@HiveField(11) int? quoteEventId,
|
||||
@HiveField(12) int? relatedEventId,
|
||||
{int id,
|
||||
DateTime createdAt,
|
||||
DateTime updatedAt,
|
||||
DateTime? deletedAt,
|
||||
String uuid,
|
||||
Map<String, dynamic> body,
|
||||
String type,
|
||||
SnChannel channel,
|
||||
SnChannelMember sender,
|
||||
int channelId,
|
||||
int senderId,
|
||||
int? quoteEventId,
|
||||
int? relatedEventId,
|
||||
SnChatMessagePreload? preload});
|
||||
|
||||
$SnChannelCopyWith<$Res> get channel;
|
||||
@ -1239,19 +1152,19 @@ abstract class _$$SnChatMessageImplCopyWith<$Res>
|
||||
@override
|
||||
@useResult
|
||||
$Res call(
|
||||
{@HiveField(0) int id,
|
||||
@HiveField(1) DateTime createdAt,
|
||||
@HiveField(2) DateTime updatedAt,
|
||||
@HiveField(3) DateTime? deletedAt,
|
||||
@HiveField(4) String uuid,
|
||||
@HiveField(5) Map<String, dynamic> body,
|
||||
@HiveField(6) String type,
|
||||
@HiveField(7) SnChannel channel,
|
||||
@HiveField(8) SnChannelMember sender,
|
||||
@HiveField(9) int channelId,
|
||||
@HiveField(10) int senderId,
|
||||
@HiveField(11) int? quoteEventId,
|
||||
@HiveField(12) int? relatedEventId,
|
||||
{int id,
|
||||
DateTime createdAt,
|
||||
DateTime updatedAt,
|
||||
DateTime? deletedAt,
|
||||
String uuid,
|
||||
Map<String, dynamic> body,
|
||||
String type,
|
||||
SnChannel channel,
|
||||
SnChannelMember sender,
|
||||
int channelId,
|
||||
int senderId,
|
||||
int? quoteEventId,
|
||||
int? relatedEventId,
|
||||
SnChatMessagePreload? preload});
|
||||
|
||||
@override
|
||||
@ -1353,22 +1266,21 @@ class __$$SnChatMessageImplCopyWithImpl<$Res>
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
@HiveType(typeId: 4)
|
||||
class _$SnChatMessageImpl extends _SnChatMessage {
|
||||
const _$SnChatMessageImpl(
|
||||
{@HiveField(0) required this.id,
|
||||
@HiveField(1) required this.createdAt,
|
||||
@HiveField(2) required this.updatedAt,
|
||||
@HiveField(3) required this.deletedAt,
|
||||
@HiveField(4) required this.uuid,
|
||||
@HiveField(5) final Map<String, dynamic> body = const {},
|
||||
@HiveField(6) required this.type,
|
||||
@HiveField(7) required this.channel,
|
||||
@HiveField(8) required this.sender,
|
||||
@HiveField(9) required this.channelId,
|
||||
@HiveField(10) required this.senderId,
|
||||
@HiveField(11) required this.quoteEventId,
|
||||
@HiveField(12) required this.relatedEventId,
|
||||
{required this.id,
|
||||
required this.createdAt,
|
||||
required this.updatedAt,
|
||||
required this.deletedAt,
|
||||
required this.uuid,
|
||||
final Map<String, dynamic> body = const {},
|
||||
required this.type,
|
||||
required this.channel,
|
||||
required this.sender,
|
||||
required this.channelId,
|
||||
required this.senderId,
|
||||
required this.quoteEventId,
|
||||
required this.relatedEventId,
|
||||
this.preload})
|
||||
: _body = body,
|
||||
super._();
|
||||
@ -1377,24 +1289,18 @@ class _$SnChatMessageImpl extends _SnChatMessage {
|
||||
_$$SnChatMessageImplFromJson(json);
|
||||
|
||||
@override
|
||||
@HiveField(0)
|
||||
final int id;
|
||||
@override
|
||||
@HiveField(1)
|
||||
final DateTime createdAt;
|
||||
@override
|
||||
@HiveField(2)
|
||||
final DateTime updatedAt;
|
||||
@override
|
||||
@HiveField(3)
|
||||
final DateTime? deletedAt;
|
||||
@override
|
||||
@HiveField(4)
|
||||
final String uuid;
|
||||
final Map<String, dynamic> _body;
|
||||
@override
|
||||
@JsonKey()
|
||||
@HiveField(5)
|
||||
Map<String, dynamic> get body {
|
||||
if (_body is EqualUnmodifiableMapView) return _body;
|
||||
// ignore: implicit_dynamic_type
|
||||
@ -1402,25 +1308,18 @@ class _$SnChatMessageImpl extends _SnChatMessage {
|
||||
}
|
||||
|
||||
@override
|
||||
@HiveField(6)
|
||||
final String type;
|
||||
@override
|
||||
@HiveField(7)
|
||||
final SnChannel channel;
|
||||
@override
|
||||
@HiveField(8)
|
||||
final SnChannelMember sender;
|
||||
@override
|
||||
@HiveField(9)
|
||||
final int channelId;
|
||||
@override
|
||||
@HiveField(10)
|
||||
final int senderId;
|
||||
@override
|
||||
@HiveField(11)
|
||||
final int? quoteEventId;
|
||||
@override
|
||||
@HiveField(12)
|
||||
final int? relatedEventId;
|
||||
@override
|
||||
final SnChatMessagePreload? preload;
|
||||
@ -1495,19 +1394,19 @@ class _$SnChatMessageImpl extends _SnChatMessage {
|
||||
|
||||
abstract class _SnChatMessage extends SnChatMessage {
|
||||
const factory _SnChatMessage(
|
||||
{@HiveField(0) required final int id,
|
||||
@HiveField(1) required final DateTime createdAt,
|
||||
@HiveField(2) required final DateTime updatedAt,
|
||||
@HiveField(3) required final DateTime? deletedAt,
|
||||
@HiveField(4) required final String uuid,
|
||||
@HiveField(5) final Map<String, dynamic> body,
|
||||
@HiveField(6) required final String type,
|
||||
@HiveField(7) required final SnChannel channel,
|
||||
@HiveField(8) required final SnChannelMember sender,
|
||||
@HiveField(9) required final int channelId,
|
||||
@HiveField(10) required final int senderId,
|
||||
@HiveField(11) required final int? quoteEventId,
|
||||
@HiveField(12) required final int? relatedEventId,
|
||||
{required final int id,
|
||||
required final DateTime createdAt,
|
||||
required final DateTime updatedAt,
|
||||
required final DateTime? deletedAt,
|
||||
required final String uuid,
|
||||
final Map<String, dynamic> body,
|
||||
required final String type,
|
||||
required final SnChannel channel,
|
||||
required final SnChannelMember sender,
|
||||
required final int channelId,
|
||||
required final int senderId,
|
||||
required final int? quoteEventId,
|
||||
required final int? relatedEventId,
|
||||
final SnChatMessagePreload? preload}) = _$SnChatMessageImpl;
|
||||
const _SnChatMessage._() : super._();
|
||||
|
||||
@ -1515,43 +1414,30 @@ abstract class _SnChatMessage extends SnChatMessage {
|
||||
_$SnChatMessageImpl.fromJson;
|
||||
|
||||
@override
|
||||
@HiveField(0)
|
||||
int get id;
|
||||
@override
|
||||
@HiveField(1)
|
||||
DateTime get createdAt;
|
||||
@override
|
||||
@HiveField(2)
|
||||
DateTime get updatedAt;
|
||||
@override
|
||||
@HiveField(3)
|
||||
DateTime? get deletedAt;
|
||||
@override
|
||||
@HiveField(4)
|
||||
String get uuid;
|
||||
@override
|
||||
@HiveField(5)
|
||||
Map<String, dynamic> get body;
|
||||
@override
|
||||
@HiveField(6)
|
||||
String get type;
|
||||
@override
|
||||
@HiveField(7)
|
||||
SnChannel get channel;
|
||||
@override
|
||||
@HiveField(8)
|
||||
SnChannelMember get sender;
|
||||
@override
|
||||
@HiveField(9)
|
||||
int get channelId;
|
||||
@override
|
||||
@HiveField(10)
|
||||
int get senderId;
|
||||
@override
|
||||
@HiveField(11)
|
||||
int? get quoteEventId;
|
||||
@override
|
||||
@HiveField(12)
|
||||
int? get relatedEventId;
|
||||
@override
|
||||
SnChatMessagePreload? get preload;
|
||||
|
@ -2,214 +2,6 @@
|
||||
|
||||
part of 'chat.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// TypeAdapterGenerator
|
||||
// **************************************************************************
|
||||
|
||||
class SnChannelImplAdapter extends TypeAdapter<_$SnChannelImpl> {
|
||||
@override
|
||||
final int typeId = 2;
|
||||
|
||||
@override
|
||||
_$SnChannelImpl read(BinaryReader reader) {
|
||||
final numOfFields = reader.readByte();
|
||||
final fields = <int, dynamic>{
|
||||
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||
};
|
||||
return _$SnChannelImpl(
|
||||
id: fields[0] as int,
|
||||
createdAt: fields[1] as DateTime,
|
||||
updatedAt: fields[2] as DateTime,
|
||||
deletedAt: fields[3] as dynamic,
|
||||
alias: fields[4] as String,
|
||||
name: fields[5] as String,
|
||||
description: fields[6] as String,
|
||||
members: (fields[7] as List?)?.cast<SnChannelMember>(),
|
||||
type: fields[8] as int,
|
||||
accountId: fields[9] as int,
|
||||
realm: fields[10] as SnRealm?,
|
||||
realmId: fields[11] as int?,
|
||||
isPublic: fields[12] as bool,
|
||||
isCommunity: fields[13] as bool,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, _$SnChannelImpl obj) {
|
||||
writer
|
||||
..writeByte(14)
|
||||
..writeByte(0)
|
||||
..write(obj.id)
|
||||
..writeByte(1)
|
||||
..write(obj.createdAt)
|
||||
..writeByte(2)
|
||||
..write(obj.updatedAt)
|
||||
..writeByte(3)
|
||||
..write(obj.deletedAt)
|
||||
..writeByte(4)
|
||||
..write(obj.alias)
|
||||
..writeByte(5)
|
||||
..write(obj.name)
|
||||
..writeByte(6)
|
||||
..write(obj.description)
|
||||
..writeByte(8)
|
||||
..write(obj.type)
|
||||
..writeByte(9)
|
||||
..write(obj.accountId)
|
||||
..writeByte(10)
|
||||
..write(obj.realm)
|
||||
..writeByte(11)
|
||||
..write(obj.realmId)
|
||||
..writeByte(12)
|
||||
..write(obj.isPublic)
|
||||
..writeByte(13)
|
||||
..write(obj.isCommunity)
|
||||
..writeByte(7)
|
||||
..write(obj.members);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is SnChannelImplAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
||||
|
||||
class SnChannelMemberImplAdapter extends TypeAdapter<_$SnChannelMemberImpl> {
|
||||
@override
|
||||
final int typeId = 3;
|
||||
|
||||
@override
|
||||
_$SnChannelMemberImpl read(BinaryReader reader) {
|
||||
final numOfFields = reader.readByte();
|
||||
final fields = <int, dynamic>{
|
||||
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||
};
|
||||
return _$SnChannelMemberImpl(
|
||||
id: fields[0] as int,
|
||||
createdAt: fields[1] as DateTime,
|
||||
updatedAt: fields[2] as DateTime,
|
||||
deletedAt: fields[3] as DateTime?,
|
||||
channelId: fields[4] as int,
|
||||
accountId: fields[5] as int,
|
||||
nick: fields[6] as String?,
|
||||
channel: fields[7] as SnChannel?,
|
||||
account: fields[8] as SnAccount?,
|
||||
powerLevel: fields[9] as int,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, _$SnChannelMemberImpl obj) {
|
||||
writer
|
||||
..writeByte(10)
|
||||
..writeByte(0)
|
||||
..write(obj.id)
|
||||
..writeByte(1)
|
||||
..write(obj.createdAt)
|
||||
..writeByte(2)
|
||||
..write(obj.updatedAt)
|
||||
..writeByte(3)
|
||||
..write(obj.deletedAt)
|
||||
..writeByte(4)
|
||||
..write(obj.channelId)
|
||||
..writeByte(5)
|
||||
..write(obj.accountId)
|
||||
..writeByte(6)
|
||||
..write(obj.nick)
|
||||
..writeByte(7)
|
||||
..write(obj.channel)
|
||||
..writeByte(8)
|
||||
..write(obj.account)
|
||||
..writeByte(9)
|
||||
..write(obj.powerLevel);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is SnChannelMemberImplAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
||||
|
||||
class SnChatMessageImplAdapter extends TypeAdapter<_$SnChatMessageImpl> {
|
||||
@override
|
||||
final int typeId = 4;
|
||||
|
||||
@override
|
||||
_$SnChatMessageImpl read(BinaryReader reader) {
|
||||
final numOfFields = reader.readByte();
|
||||
final fields = <int, dynamic>{
|
||||
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||
};
|
||||
return _$SnChatMessageImpl(
|
||||
id: fields[0] as int,
|
||||
createdAt: fields[1] as DateTime,
|
||||
updatedAt: fields[2] as DateTime,
|
||||
deletedAt: fields[3] as DateTime?,
|
||||
uuid: fields[4] as String,
|
||||
body: (fields[5] as Map).cast<String, dynamic>(),
|
||||
type: fields[6] as String,
|
||||
channel: fields[7] as SnChannel,
|
||||
sender: fields[8] as SnChannelMember,
|
||||
channelId: fields[9] as int,
|
||||
senderId: fields[10] as int,
|
||||
quoteEventId: fields[11] as int?,
|
||||
relatedEventId: fields[12] as int?,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, _$SnChatMessageImpl obj) {
|
||||
writer
|
||||
..writeByte(13)
|
||||
..writeByte(0)
|
||||
..write(obj.id)
|
||||
..writeByte(1)
|
||||
..write(obj.createdAt)
|
||||
..writeByte(2)
|
||||
..write(obj.updatedAt)
|
||||
..writeByte(3)
|
||||
..write(obj.deletedAt)
|
||||
..writeByte(4)
|
||||
..write(obj.uuid)
|
||||
..writeByte(6)
|
||||
..write(obj.type)
|
||||
..writeByte(7)
|
||||
..write(obj.channel)
|
||||
..writeByte(8)
|
||||
..write(obj.sender)
|
||||
..writeByte(9)
|
||||
..write(obj.channelId)
|
||||
..writeByte(10)
|
||||
..write(obj.senderId)
|
||||
..writeByte(11)
|
||||
..write(obj.quoteEventId)
|
||||
..writeByte(12)
|
||||
..write(obj.relatedEventId)
|
||||
..writeByte(5)
|
||||
..write(obj.body);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is SnChatMessageImplAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
@ -1,6 +1,7 @@
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:surface/types/attachment.dart';
|
||||
import 'package:surface/types/poll.dart';
|
||||
import 'package:surface/types/realm.dart';
|
||||
|
||||
part 'post.freezed.dart';
|
||||
part 'post.g.dart';
|
||||
@ -24,6 +25,7 @@ class SnPost with _$SnPost {
|
||||
required List<SnPost>? replies,
|
||||
required int? replyId,
|
||||
required int? repostId,
|
||||
required int? realmId,
|
||||
required SnPost? replyTo,
|
||||
required SnPost? repostTo,
|
||||
required List<int>? visibleUsersList,
|
||||
@ -95,6 +97,7 @@ class SnPostPreload with _$SnPostPreload {
|
||||
required List<SnAttachment?>? attachments,
|
||||
required SnAttachment? video,
|
||||
required SnPoll? poll,
|
||||
required SnRealm? realm,
|
||||
}) = _SnPostPreload;
|
||||
|
||||
factory SnPostPreload.fromJson(Map<String, Object?> json) =>
|
||||
|
@ -34,6 +34,7 @@ mixin _$SnPost {
|
||||
List<SnPost>? get replies => throw _privateConstructorUsedError;
|
||||
int? get replyId => throw _privateConstructorUsedError;
|
||||
int? get repostId => throw _privateConstructorUsedError;
|
||||
int? get realmId => throw _privateConstructorUsedError;
|
||||
SnPost? get replyTo => throw _privateConstructorUsedError;
|
||||
SnPost? get repostTo => throw _privateConstructorUsedError;
|
||||
List<int>? get visibleUsersList => throw _privateConstructorUsedError;
|
||||
@ -84,6 +85,7 @@ abstract class $SnPostCopyWith<$Res> {
|
||||
List<SnPost>? replies,
|
||||
int? replyId,
|
||||
int? repostId,
|
||||
int? realmId,
|
||||
SnPost? replyTo,
|
||||
SnPost? repostTo,
|
||||
List<int>? visibleUsersList,
|
||||
@ -141,6 +143,7 @@ class _$SnPostCopyWithImpl<$Res, $Val extends SnPost>
|
||||
Object? replies = freezed,
|
||||
Object? replyId = freezed,
|
||||
Object? repostId = freezed,
|
||||
Object? realmId = freezed,
|
||||
Object? replyTo = freezed,
|
||||
Object? repostTo = freezed,
|
||||
Object? visibleUsersList = freezed,
|
||||
@ -219,6 +222,10 @@ class _$SnPostCopyWithImpl<$Res, $Val extends SnPost>
|
||||
? _value.repostId
|
||||
: repostId // ignore: cast_nullable_to_non_nullable
|
||||
as int?,
|
||||
realmId: freezed == realmId
|
||||
? _value.realmId
|
||||
: realmId // ignore: cast_nullable_to_non_nullable
|
||||
as int?,
|
||||
replyTo: freezed == replyTo
|
||||
? _value.replyTo
|
||||
: replyTo // ignore: cast_nullable_to_non_nullable
|
||||
@ -387,6 +394,7 @@ abstract class _$$SnPostImplCopyWith<$Res> implements $SnPostCopyWith<$Res> {
|
||||
List<SnPost>? replies,
|
||||
int? replyId,
|
||||
int? repostId,
|
||||
int? realmId,
|
||||
SnPost? replyTo,
|
||||
SnPost? repostTo,
|
||||
List<int>? visibleUsersList,
|
||||
@ -447,6 +455,7 @@ class __$$SnPostImplCopyWithImpl<$Res>
|
||||
Object? replies = freezed,
|
||||
Object? replyId = freezed,
|
||||
Object? repostId = freezed,
|
||||
Object? realmId = freezed,
|
||||
Object? replyTo = freezed,
|
||||
Object? repostTo = freezed,
|
||||
Object? visibleUsersList = freezed,
|
||||
@ -525,6 +534,10 @@ class __$$SnPostImplCopyWithImpl<$Res>
|
||||
? _value.repostId
|
||||
: repostId // ignore: cast_nullable_to_non_nullable
|
||||
as int?,
|
||||
realmId: freezed == realmId
|
||||
? _value.realmId
|
||||
: realmId // ignore: cast_nullable_to_non_nullable
|
||||
as int?,
|
||||
replyTo: freezed == replyTo
|
||||
? _value.replyTo
|
||||
: replyTo // ignore: cast_nullable_to_non_nullable
|
||||
@ -627,6 +640,7 @@ class _$SnPostImpl extends _SnPost {
|
||||
required final List<SnPost>? replies,
|
||||
required this.replyId,
|
||||
required this.repostId,
|
||||
required this.realmId,
|
||||
required this.replyTo,
|
||||
required this.repostTo,
|
||||
required final List<int>? visibleUsersList,
|
||||
@ -715,6 +729,8 @@ class _$SnPostImpl extends _SnPost {
|
||||
@override
|
||||
final int? repostId;
|
||||
@override
|
||||
final int? realmId;
|
||||
@override
|
||||
final SnPost? replyTo;
|
||||
@override
|
||||
final SnPost? repostTo;
|
||||
@ -777,7 +793,7 @@ class _$SnPostImpl extends _SnPost {
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SnPost(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, type: $type, body: $body, language: $language, alias: $alias, aliasPrefix: $aliasPrefix, tags: $tags, categories: $categories, replies: $replies, replyId: $replyId, repostId: $repostId, replyTo: $replyTo, repostTo: $repostTo, visibleUsersList: $visibleUsersList, invisibleUsersList: $invisibleUsersList, visibility: $visibility, editedAt: $editedAt, pinnedAt: $pinnedAt, lockedAt: $lockedAt, isDraft: $isDraft, publishedAt: $publishedAt, publishedUntil: $publishedUntil, totalUpvote: $totalUpvote, totalDownvote: $totalDownvote, totalViews: $totalViews, totalAggregatedViews: $totalAggregatedViews, publisherId: $publisherId, pollId: $pollId, publisher: $publisher, metric: $metric, preload: $preload)';
|
||||
return 'SnPost(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, type: $type, body: $body, language: $language, alias: $alias, aliasPrefix: $aliasPrefix, tags: $tags, categories: $categories, replies: $replies, replyId: $replyId, repostId: $repostId, realmId: $realmId, replyTo: $replyTo, repostTo: $repostTo, visibleUsersList: $visibleUsersList, invisibleUsersList: $invisibleUsersList, visibility: $visibility, editedAt: $editedAt, pinnedAt: $pinnedAt, lockedAt: $lockedAt, isDraft: $isDraft, publishedAt: $publishedAt, publishedUntil: $publishedUntil, totalUpvote: $totalUpvote, totalDownvote: $totalDownvote, totalViews: $totalViews, totalAggregatedViews: $totalAggregatedViews, publisherId: $publisherId, pollId: $pollId, publisher: $publisher, metric: $metric, preload: $preload)';
|
||||
}
|
||||
|
||||
@override
|
||||
@ -806,6 +822,7 @@ class _$SnPostImpl extends _SnPost {
|
||||
(identical(other.replyId, replyId) || other.replyId == replyId) &&
|
||||
(identical(other.repostId, repostId) ||
|
||||
other.repostId == repostId) &&
|
||||
(identical(other.realmId, realmId) || other.realmId == realmId) &&
|
||||
(identical(other.replyTo, replyTo) || other.replyTo == replyTo) &&
|
||||
(identical(other.repostTo, repostTo) ||
|
||||
other.repostTo == repostTo) &&
|
||||
@ -861,6 +878,7 @@ class _$SnPostImpl extends _SnPost {
|
||||
const DeepCollectionEquality().hash(_replies),
|
||||
replyId,
|
||||
repostId,
|
||||
realmId,
|
||||
replyTo,
|
||||
repostTo,
|
||||
const DeepCollectionEquality().hash(_visibleUsersList),
|
||||
@ -915,6 +933,7 @@ abstract class _SnPost extends SnPost {
|
||||
required final List<SnPost>? replies,
|
||||
required final int? replyId,
|
||||
required final int? repostId,
|
||||
required final int? realmId,
|
||||
required final SnPost? replyTo,
|
||||
required final SnPost? repostTo,
|
||||
required final List<int>? visibleUsersList,
|
||||
@ -968,6 +987,8 @@ abstract class _SnPost extends SnPost {
|
||||
@override
|
||||
int? get repostId;
|
||||
@override
|
||||
int? get realmId;
|
||||
@override
|
||||
SnPost? get replyTo;
|
||||
@override
|
||||
SnPost? get repostTo;
|
||||
@ -1636,6 +1657,7 @@ mixin _$SnPostPreload {
|
||||
List<SnAttachment?>? get attachments => throw _privateConstructorUsedError;
|
||||
SnAttachment? get video => throw _privateConstructorUsedError;
|
||||
SnPoll? get poll => throw _privateConstructorUsedError;
|
||||
SnRealm? get realm => throw _privateConstructorUsedError;
|
||||
|
||||
/// Serializes this SnPostPreload to a JSON map.
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@ -1657,11 +1679,13 @@ abstract class $SnPostPreloadCopyWith<$Res> {
|
||||
{SnAttachment? thumbnail,
|
||||
List<SnAttachment?>? attachments,
|
||||
SnAttachment? video,
|
||||
SnPoll? poll});
|
||||
SnPoll? poll,
|
||||
SnRealm? realm});
|
||||
|
||||
$SnAttachmentCopyWith<$Res>? get thumbnail;
|
||||
$SnAttachmentCopyWith<$Res>? get video;
|
||||
$SnPollCopyWith<$Res>? get poll;
|
||||
$SnRealmCopyWith<$Res>? get realm;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@ -1683,6 +1707,7 @@ class _$SnPostPreloadCopyWithImpl<$Res, $Val extends SnPostPreload>
|
||||
Object? attachments = freezed,
|
||||
Object? video = freezed,
|
||||
Object? poll = freezed,
|
||||
Object? realm = freezed,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
thumbnail: freezed == thumbnail
|
||||
@ -1701,6 +1726,10 @@ class _$SnPostPreloadCopyWithImpl<$Res, $Val extends SnPostPreload>
|
||||
? _value.poll
|
||||
: poll // ignore: cast_nullable_to_non_nullable
|
||||
as SnPoll?,
|
||||
realm: freezed == realm
|
||||
? _value.realm
|
||||
: realm // ignore: cast_nullable_to_non_nullable
|
||||
as SnRealm?,
|
||||
) as $Val);
|
||||
}
|
||||
|
||||
@ -1745,6 +1774,20 @@ class _$SnPostPreloadCopyWithImpl<$Res, $Val extends SnPostPreload>
|
||||
return _then(_value.copyWith(poll: value) as $Val);
|
||||
});
|
||||
}
|
||||
|
||||
/// Create a copy of SnPostPreload
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$SnRealmCopyWith<$Res>? get realm {
|
||||
if (_value.realm == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $SnRealmCopyWith<$Res>(_value.realm!, (value) {
|
||||
return _then(_value.copyWith(realm: value) as $Val);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@ -1759,7 +1802,8 @@ abstract class _$$SnPostPreloadImplCopyWith<$Res>
|
||||
{SnAttachment? thumbnail,
|
||||
List<SnAttachment?>? attachments,
|
||||
SnAttachment? video,
|
||||
SnPoll? poll});
|
||||
SnPoll? poll,
|
||||
SnRealm? realm});
|
||||
|
||||
@override
|
||||
$SnAttachmentCopyWith<$Res>? get thumbnail;
|
||||
@ -1767,6 +1811,8 @@ abstract class _$$SnPostPreloadImplCopyWith<$Res>
|
||||
$SnAttachmentCopyWith<$Res>? get video;
|
||||
@override
|
||||
$SnPollCopyWith<$Res>? get poll;
|
||||
@override
|
||||
$SnRealmCopyWith<$Res>? get realm;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@ -1786,6 +1832,7 @@ class __$$SnPostPreloadImplCopyWithImpl<$Res>
|
||||
Object? attachments = freezed,
|
||||
Object? video = freezed,
|
||||
Object? poll = freezed,
|
||||
Object? realm = freezed,
|
||||
}) {
|
||||
return _then(_$SnPostPreloadImpl(
|
||||
thumbnail: freezed == thumbnail
|
||||
@ -1804,6 +1851,10 @@ class __$$SnPostPreloadImplCopyWithImpl<$Res>
|
||||
? _value.poll
|
||||
: poll // ignore: cast_nullable_to_non_nullable
|
||||
as SnPoll?,
|
||||
realm: freezed == realm
|
||||
? _value.realm
|
||||
: realm // ignore: cast_nullable_to_non_nullable
|
||||
as SnRealm?,
|
||||
));
|
||||
}
|
||||
}
|
||||
@ -1815,7 +1866,8 @@ class _$SnPostPreloadImpl implements _SnPostPreload {
|
||||
{required this.thumbnail,
|
||||
required final List<SnAttachment?>? attachments,
|
||||
required this.video,
|
||||
required this.poll})
|
||||
required this.poll,
|
||||
required this.realm})
|
||||
: _attachments = attachments;
|
||||
|
||||
factory _$SnPostPreloadImpl.fromJson(Map<String, dynamic> json) =>
|
||||
@ -1837,10 +1889,12 @@ class _$SnPostPreloadImpl implements _SnPostPreload {
|
||||
final SnAttachment? video;
|
||||
@override
|
||||
final SnPoll? poll;
|
||||
@override
|
||||
final SnRealm? realm;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SnPostPreload(thumbnail: $thumbnail, attachments: $attachments, video: $video, poll: $poll)';
|
||||
return 'SnPostPreload(thumbnail: $thumbnail, attachments: $attachments, video: $video, poll: $poll, realm: $realm)';
|
||||
}
|
||||
|
||||
@override
|
||||
@ -1853,13 +1907,14 @@ class _$SnPostPreloadImpl implements _SnPostPreload {
|
||||
const DeepCollectionEquality()
|
||||
.equals(other._attachments, _attachments) &&
|
||||
(identical(other.video, video) || other.video == video) &&
|
||||
(identical(other.poll, poll) || other.poll == poll));
|
||||
(identical(other.poll, poll) || other.poll == poll) &&
|
||||
(identical(other.realm, realm) || other.realm == realm));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, thumbnail,
|
||||
const DeepCollectionEquality().hash(_attachments), video, poll);
|
||||
const DeepCollectionEquality().hash(_attachments), video, poll, realm);
|
||||
|
||||
/// Create a copy of SnPostPreload
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@ -1882,7 +1937,8 @@ abstract class _SnPostPreload implements SnPostPreload {
|
||||
{required final SnAttachment? thumbnail,
|
||||
required final List<SnAttachment?>? attachments,
|
||||
required final SnAttachment? video,
|
||||
required final SnPoll? poll}) = _$SnPostPreloadImpl;
|
||||
required final SnPoll? poll,
|
||||
required final SnRealm? realm}) = _$SnPostPreloadImpl;
|
||||
|
||||
factory _SnPostPreload.fromJson(Map<String, dynamic> json) =
|
||||
_$SnPostPreloadImpl.fromJson;
|
||||
@ -1895,6 +1951,8 @@ abstract class _SnPostPreload implements SnPostPreload {
|
||||
SnAttachment? get video;
|
||||
@override
|
||||
SnPoll? get poll;
|
||||
@override
|
||||
SnRealm? get realm;
|
||||
|
||||
/// Create a copy of SnPostPreload
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
|
@ -31,6 +31,7 @@ _$SnPostImpl _$$SnPostImplFromJson(Map<String, dynamic> json) => _$SnPostImpl(
|
||||
.toList(),
|
||||
replyId: (json['reply_id'] as num?)?.toInt(),
|
||||
repostId: (json['repost_id'] as num?)?.toInt(),
|
||||
realmId: (json['realm_id'] as num?)?.toInt(),
|
||||
replyTo: json['reply_to'] == null
|
||||
? null
|
||||
: SnPost.fromJson(json['reply_to'] as Map<String, dynamic>),
|
||||
@ -91,6 +92,7 @@ Map<String, dynamic> _$$SnPostImplToJson(_$SnPostImpl instance) =>
|
||||
'replies': instance.replies?.map((e) => e.toJson()).toList(),
|
||||
'reply_id': instance.replyId,
|
||||
'repost_id': instance.repostId,
|
||||
'realm_id': instance.realmId,
|
||||
'reply_to': instance.replyTo?.toJson(),
|
||||
'repost_to': instance.repostTo?.toJson(),
|
||||
'visible_users_list': instance.visibleUsersList,
|
||||
@ -178,6 +180,9 @@ _$SnPostPreloadImpl _$$SnPostPreloadImplFromJson(Map<String, dynamic> json) =>
|
||||
poll: json['poll'] == null
|
||||
? null
|
||||
: SnPoll.fromJson(json['poll'] as Map<String, dynamic>),
|
||||
realm: json['realm'] == null
|
||||
? null
|
||||
: SnRealm.fromJson(json['realm'] as Map<String, dynamic>),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$SnPostPreloadImplToJson(_$SnPostPreloadImpl instance) =>
|
||||
@ -186,6 +191,7 @@ Map<String, dynamic> _$$SnPostPreloadImplToJson(_$SnPostPreloadImpl instance) =>
|
||||
'attachments': instance.attachments?.map((e) => e?.toJson()).toList(),
|
||||
'video': instance.video?.toJson(),
|
||||
'poll': instance.poll?.toJson(),
|
||||
'realm': instance.realm?.toJson(),
|
||||
};
|
||||
|
||||
_$SnBodyImpl _$$SnBodyImplFromJson(Map<String, dynamic> json) => _$SnBodyImpl(
|
||||
|
@ -1,5 +1,4 @@
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:hive_flutter/hive_flutter.dart';
|
||||
import 'package:surface/types/account.dart';
|
||||
|
||||
part 'realm.freezed.dart';
|
||||
@ -27,22 +26,22 @@ class SnRealmMember with _$SnRealmMember {
|
||||
class SnRealm with _$SnRealm {
|
||||
const SnRealm._();
|
||||
|
||||
@HiveType(typeId: 1)
|
||||
const factory SnRealm({
|
||||
@HiveField(0) required int id,
|
||||
@HiveField(1) required DateTime createdAt,
|
||||
@HiveField(2) required DateTime updatedAt,
|
||||
@HiveField(3) required DateTime? deletedAt,
|
||||
@HiveField(4) required String alias,
|
||||
@HiveField(5) required String name,
|
||||
@HiveField(6) required String description,
|
||||
required int id,
|
||||
required DateTime createdAt,
|
||||
required DateTime updatedAt,
|
||||
required DateTime? deletedAt,
|
||||
required String alias,
|
||||
required String name,
|
||||
required String description,
|
||||
List<SnRealmMember>? members,
|
||||
@HiveField(7) required String? avatar,
|
||||
@HiveField(8) required String? banner,
|
||||
@HiveField(9) required Map<String, dynamic>? accessPolicy,
|
||||
@HiveField(10) required int accountId,
|
||||
@HiveField(11) required bool isPublic,
|
||||
@HiveField(12) required bool isCommunity,
|
||||
required String? avatar,
|
||||
required String? banner,
|
||||
required Map<String, dynamic>? accessPolicy,
|
||||
required int accountId,
|
||||
required bool isPublic,
|
||||
required bool isCommunity,
|
||||
@Default(0) int popularity,
|
||||
}) = _SnRealm;
|
||||
|
||||
factory SnRealm.fromJson(Map<String, dynamic> json) =>
|
||||
|
@ -367,33 +367,21 @@ SnRealm _$SnRealmFromJson(Map<String, dynamic> json) {
|
||||
|
||||
/// @nodoc
|
||||
mixin _$SnRealm {
|
||||
@HiveField(0)
|
||||
int get id => throw _privateConstructorUsedError;
|
||||
@HiveField(1)
|
||||
DateTime get createdAt => throw _privateConstructorUsedError;
|
||||
@HiveField(2)
|
||||
DateTime get updatedAt => throw _privateConstructorUsedError;
|
||||
@HiveField(3)
|
||||
DateTime? get deletedAt => throw _privateConstructorUsedError;
|
||||
@HiveField(4)
|
||||
String get alias => throw _privateConstructorUsedError;
|
||||
@HiveField(5)
|
||||
String get name => throw _privateConstructorUsedError;
|
||||
@HiveField(6)
|
||||
String get description => throw _privateConstructorUsedError;
|
||||
List<SnRealmMember>? get members => throw _privateConstructorUsedError;
|
||||
@HiveField(7)
|
||||
String? get avatar => throw _privateConstructorUsedError;
|
||||
@HiveField(8)
|
||||
String? get banner => throw _privateConstructorUsedError;
|
||||
@HiveField(9)
|
||||
Map<String, dynamic>? get accessPolicy => throw _privateConstructorUsedError;
|
||||
@HiveField(10)
|
||||
int get accountId => throw _privateConstructorUsedError;
|
||||
@HiveField(11)
|
||||
bool get isPublic => throw _privateConstructorUsedError;
|
||||
@HiveField(12)
|
||||
bool get isCommunity => throw _privateConstructorUsedError;
|
||||
int get popularity => throw _privateConstructorUsedError;
|
||||
|
||||
/// Serializes this SnRealm to a JSON map.
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@ -410,20 +398,21 @@ abstract class $SnRealmCopyWith<$Res> {
|
||||
_$SnRealmCopyWithImpl<$Res, SnRealm>;
|
||||
@useResult
|
||||
$Res call(
|
||||
{@HiveField(0) int id,
|
||||
@HiveField(1) DateTime createdAt,
|
||||
@HiveField(2) DateTime updatedAt,
|
||||
@HiveField(3) DateTime? deletedAt,
|
||||
@HiveField(4) String alias,
|
||||
@HiveField(5) String name,
|
||||
@HiveField(6) String description,
|
||||
{int id,
|
||||
DateTime createdAt,
|
||||
DateTime updatedAt,
|
||||
DateTime? deletedAt,
|
||||
String alias,
|
||||
String name,
|
||||
String description,
|
||||
List<SnRealmMember>? members,
|
||||
@HiveField(7) String? avatar,
|
||||
@HiveField(8) String? banner,
|
||||
@HiveField(9) Map<String, dynamic>? accessPolicy,
|
||||
@HiveField(10) int accountId,
|
||||
@HiveField(11) bool isPublic,
|
||||
@HiveField(12) bool isCommunity});
|
||||
String? avatar,
|
||||
String? banner,
|
||||
Map<String, dynamic>? accessPolicy,
|
||||
int accountId,
|
||||
bool isPublic,
|
||||
bool isCommunity,
|
||||
int popularity});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@ -455,6 +444,7 @@ class _$SnRealmCopyWithImpl<$Res, $Val extends SnRealm>
|
||||
Object? accountId = null,
|
||||
Object? isPublic = null,
|
||||
Object? isCommunity = null,
|
||||
Object? popularity = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
id: null == id
|
||||
@ -513,6 +503,10 @@ class _$SnRealmCopyWithImpl<$Res, $Val extends SnRealm>
|
||||
? _value.isCommunity
|
||||
: isCommunity // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
popularity: null == popularity
|
||||
? _value.popularity
|
||||
: popularity // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
@ -525,20 +519,21 @@ abstract class _$$SnRealmImplCopyWith<$Res> implements $SnRealmCopyWith<$Res> {
|
||||
@override
|
||||
@useResult
|
||||
$Res call(
|
||||
{@HiveField(0) int id,
|
||||
@HiveField(1) DateTime createdAt,
|
||||
@HiveField(2) DateTime updatedAt,
|
||||
@HiveField(3) DateTime? deletedAt,
|
||||
@HiveField(4) String alias,
|
||||
@HiveField(5) String name,
|
||||
@HiveField(6) String description,
|
||||
{int id,
|
||||
DateTime createdAt,
|
||||
DateTime updatedAt,
|
||||
DateTime? deletedAt,
|
||||
String alias,
|
||||
String name,
|
||||
String description,
|
||||
List<SnRealmMember>? members,
|
||||
@HiveField(7) String? avatar,
|
||||
@HiveField(8) String? banner,
|
||||
@HiveField(9) Map<String, dynamic>? accessPolicy,
|
||||
@HiveField(10) int accountId,
|
||||
@HiveField(11) bool isPublic,
|
||||
@HiveField(12) bool isCommunity});
|
||||
String? avatar,
|
||||
String? banner,
|
||||
Map<String, dynamic>? accessPolicy,
|
||||
int accountId,
|
||||
bool isPublic,
|
||||
bool isCommunity,
|
||||
int popularity});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@ -568,6 +563,7 @@ class __$$SnRealmImplCopyWithImpl<$Res>
|
||||
Object? accountId = null,
|
||||
Object? isPublic = null,
|
||||
Object? isCommunity = null,
|
||||
Object? popularity = null,
|
||||
}) {
|
||||
return _then(_$SnRealmImpl(
|
||||
id: null == id
|
||||
@ -626,29 +622,33 @@ class __$$SnRealmImplCopyWithImpl<$Res>
|
||||
? _value.isCommunity
|
||||
: isCommunity // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
popularity: null == popularity
|
||||
? _value.popularity
|
||||
: popularity // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
@HiveType(typeId: 1)
|
||||
class _$SnRealmImpl extends _SnRealm {
|
||||
const _$SnRealmImpl(
|
||||
{@HiveField(0) required this.id,
|
||||
@HiveField(1) required this.createdAt,
|
||||
@HiveField(2) required this.updatedAt,
|
||||
@HiveField(3) required this.deletedAt,
|
||||
@HiveField(4) required this.alias,
|
||||
@HiveField(5) required this.name,
|
||||
@HiveField(6) required this.description,
|
||||
{required this.id,
|
||||
required this.createdAt,
|
||||
required this.updatedAt,
|
||||
required this.deletedAt,
|
||||
required this.alias,
|
||||
required this.name,
|
||||
required this.description,
|
||||
final List<SnRealmMember>? members,
|
||||
@HiveField(7) required this.avatar,
|
||||
@HiveField(8) required this.banner,
|
||||
@HiveField(9) required final Map<String, dynamic>? accessPolicy,
|
||||
@HiveField(10) required this.accountId,
|
||||
@HiveField(11) required this.isPublic,
|
||||
@HiveField(12) required this.isCommunity})
|
||||
required this.avatar,
|
||||
required this.banner,
|
||||
required final Map<String, dynamic>? accessPolicy,
|
||||
required this.accountId,
|
||||
required this.isPublic,
|
||||
required this.isCommunity,
|
||||
this.popularity = 0})
|
||||
: _members = members,
|
||||
_accessPolicy = accessPolicy,
|
||||
super._();
|
||||
@ -657,25 +657,18 @@ class _$SnRealmImpl extends _SnRealm {
|
||||
_$$SnRealmImplFromJson(json);
|
||||
|
||||
@override
|
||||
@HiveField(0)
|
||||
final int id;
|
||||
@override
|
||||
@HiveField(1)
|
||||
final DateTime createdAt;
|
||||
@override
|
||||
@HiveField(2)
|
||||
final DateTime updatedAt;
|
||||
@override
|
||||
@HiveField(3)
|
||||
final DateTime? deletedAt;
|
||||
@override
|
||||
@HiveField(4)
|
||||
final String alias;
|
||||
@override
|
||||
@HiveField(5)
|
||||
final String name;
|
||||
@override
|
||||
@HiveField(6)
|
||||
final String description;
|
||||
final List<SnRealmMember>? _members;
|
||||
@override
|
||||
@ -688,14 +681,11 @@ class _$SnRealmImpl extends _SnRealm {
|
||||
}
|
||||
|
||||
@override
|
||||
@HiveField(7)
|
||||
final String? avatar;
|
||||
@override
|
||||
@HiveField(8)
|
||||
final String? banner;
|
||||
final Map<String, dynamic>? _accessPolicy;
|
||||
@override
|
||||
@HiveField(9)
|
||||
Map<String, dynamic>? get accessPolicy {
|
||||
final value = _accessPolicy;
|
||||
if (value == null) return null;
|
||||
@ -705,18 +695,18 @@ class _$SnRealmImpl extends _SnRealm {
|
||||
}
|
||||
|
||||
@override
|
||||
@HiveField(10)
|
||||
final int accountId;
|
||||
@override
|
||||
@HiveField(11)
|
||||
final bool isPublic;
|
||||
@override
|
||||
@HiveField(12)
|
||||
final bool isCommunity;
|
||||
@override
|
||||
@JsonKey()
|
||||
final int popularity;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SnRealm(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, alias: $alias, name: $name, description: $description, members: $members, avatar: $avatar, banner: $banner, accessPolicy: $accessPolicy, accountId: $accountId, isPublic: $isPublic, isCommunity: $isCommunity)';
|
||||
return 'SnRealm(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, alias: $alias, name: $name, description: $description, members: $members, avatar: $avatar, banner: $banner, accessPolicy: $accessPolicy, accountId: $accountId, isPublic: $isPublic, isCommunity: $isCommunity, popularity: $popularity)';
|
||||
}
|
||||
|
||||
@override
|
||||
@ -745,7 +735,9 @@ class _$SnRealmImpl extends _SnRealm {
|
||||
(identical(other.isPublic, isPublic) ||
|
||||
other.isPublic == isPublic) &&
|
||||
(identical(other.isCommunity, isCommunity) ||
|
||||
other.isCommunity == isCommunity));
|
||||
other.isCommunity == isCommunity) &&
|
||||
(identical(other.popularity, popularity) ||
|
||||
other.popularity == popularity));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@ -765,7 +757,8 @@ class _$SnRealmImpl extends _SnRealm {
|
||||
const DeepCollectionEquality().hash(_accessPolicy),
|
||||
accountId,
|
||||
isPublic,
|
||||
isCommunity);
|
||||
isCommunity,
|
||||
popularity);
|
||||
|
||||
/// Create a copy of SnRealm
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@ -785,65 +778,55 @@ class _$SnRealmImpl extends _SnRealm {
|
||||
|
||||
abstract class _SnRealm extends SnRealm {
|
||||
const factory _SnRealm(
|
||||
{@HiveField(0) required final int id,
|
||||
@HiveField(1) required final DateTime createdAt,
|
||||
@HiveField(2) required final DateTime updatedAt,
|
||||
@HiveField(3) required final DateTime? deletedAt,
|
||||
@HiveField(4) required final String alias,
|
||||
@HiveField(5) required final String name,
|
||||
@HiveField(6) required final String description,
|
||||
{required final int id,
|
||||
required final DateTime createdAt,
|
||||
required final DateTime updatedAt,
|
||||
required final DateTime? deletedAt,
|
||||
required final String alias,
|
||||
required final String name,
|
||||
required final String description,
|
||||
final List<SnRealmMember>? members,
|
||||
@HiveField(7) required final String? avatar,
|
||||
@HiveField(8) required final String? banner,
|
||||
@HiveField(9) required final Map<String, dynamic>? accessPolicy,
|
||||
@HiveField(10) required final int accountId,
|
||||
@HiveField(11) required final bool isPublic,
|
||||
@HiveField(12) required final bool isCommunity}) = _$SnRealmImpl;
|
||||
required final String? avatar,
|
||||
required final String? banner,
|
||||
required final Map<String, dynamic>? accessPolicy,
|
||||
required final int accountId,
|
||||
required final bool isPublic,
|
||||
required final bool isCommunity,
|
||||
final int popularity}) = _$SnRealmImpl;
|
||||
const _SnRealm._() : super._();
|
||||
|
||||
factory _SnRealm.fromJson(Map<String, dynamic> json) = _$SnRealmImpl.fromJson;
|
||||
|
||||
@override
|
||||
@HiveField(0)
|
||||
int get id;
|
||||
@override
|
||||
@HiveField(1)
|
||||
DateTime get createdAt;
|
||||
@override
|
||||
@HiveField(2)
|
||||
DateTime get updatedAt;
|
||||
@override
|
||||
@HiveField(3)
|
||||
DateTime? get deletedAt;
|
||||
@override
|
||||
@HiveField(4)
|
||||
String get alias;
|
||||
@override
|
||||
@HiveField(5)
|
||||
String get name;
|
||||
@override
|
||||
@HiveField(6)
|
||||
String get description;
|
||||
@override
|
||||
List<SnRealmMember>? get members;
|
||||
@override
|
||||
@HiveField(7)
|
||||
String? get avatar;
|
||||
@override
|
||||
@HiveField(8)
|
||||
String? get banner;
|
||||
@override
|
||||
@HiveField(9)
|
||||
Map<String, dynamic>? get accessPolicy;
|
||||
@override
|
||||
@HiveField(10)
|
||||
int get accountId;
|
||||
@override
|
||||
@HiveField(11)
|
||||
bool get isPublic;
|
||||
@override
|
||||
@HiveField(12)
|
||||
bool get isCommunity;
|
||||
@override
|
||||
int get popularity;
|
||||
|
||||
/// Create a copy of SnRealm
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
|
@ -2,80 +2,6 @@
|
||||
|
||||
part of 'realm.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// TypeAdapterGenerator
|
||||
// **************************************************************************
|
||||
|
||||
class SnRealmImplAdapter extends TypeAdapter<_$SnRealmImpl> {
|
||||
@override
|
||||
final int typeId = 1;
|
||||
|
||||
@override
|
||||
_$SnRealmImpl read(BinaryReader reader) {
|
||||
final numOfFields = reader.readByte();
|
||||
final fields = <int, dynamic>{
|
||||
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||
};
|
||||
return _$SnRealmImpl(
|
||||
id: fields[0] as int,
|
||||
createdAt: fields[1] as DateTime,
|
||||
updatedAt: fields[2] as DateTime,
|
||||
deletedAt: fields[3] as DateTime?,
|
||||
alias: fields[4] as String,
|
||||
name: fields[5] as String,
|
||||
description: fields[6] as String,
|
||||
avatar: fields[7] as String?,
|
||||
banner: fields[8] as String?,
|
||||
accessPolicy: (fields[9] as Map?)?.cast<String, dynamic>(),
|
||||
accountId: fields[10] as int,
|
||||
isPublic: fields[11] as bool,
|
||||
isCommunity: fields[12] as bool,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, _$SnRealmImpl obj) {
|
||||
writer
|
||||
..writeByte(13)
|
||||
..writeByte(0)
|
||||
..write(obj.id)
|
||||
..writeByte(1)
|
||||
..write(obj.createdAt)
|
||||
..writeByte(2)
|
||||
..write(obj.updatedAt)
|
||||
..writeByte(3)
|
||||
..write(obj.deletedAt)
|
||||
..writeByte(4)
|
||||
..write(obj.alias)
|
||||
..writeByte(5)
|
||||
..write(obj.name)
|
||||
..writeByte(6)
|
||||
..write(obj.description)
|
||||
..writeByte(7)
|
||||
..write(obj.avatar)
|
||||
..writeByte(8)
|
||||
..write(obj.banner)
|
||||
..writeByte(10)
|
||||
..write(obj.accountId)
|
||||
..writeByte(11)
|
||||
..write(obj.isPublic)
|
||||
..writeByte(12)
|
||||
..write(obj.isCommunity)
|
||||
..writeByte(9)
|
||||
..write(obj.accessPolicy);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is SnRealmImplAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
@ -128,6 +54,7 @@ _$SnRealmImpl _$$SnRealmImplFromJson(Map<String, dynamic> json) =>
|
||||
accountId: (json['account_id'] as num).toInt(),
|
||||
isPublic: json['is_public'] as bool,
|
||||
isCommunity: json['is_community'] as bool,
|
||||
popularity: (json['popularity'] as num?)?.toInt() ?? 0,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$SnRealmImplToJson(_$SnRealmImpl instance) =>
|
||||
@ -146,4 +73,5 @@ Map<String, dynamic> _$$SnRealmImplToJson(_$SnRealmImpl instance) =>
|
||||
'account_id': instance.accountId,
|
||||
'is_public': instance.isPublic,
|
||||
'is_community': instance.isCommunity,
|
||||
'popularity': instance.popularity,
|
||||
};
|
||||
|
@ -97,6 +97,13 @@ class AboutScreen extends StatelessWidget {
|
||||
launchUrlString('https://status.solsynth.dev');
|
||||
},
|
||||
),
|
||||
TextButton(
|
||||
style: denseButtonStyle,
|
||||
child: Text('projectDetail').tr(),
|
||||
onPressed: () {
|
||||
launchUrlString('https://solsynth.dev/products/solar-network');
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
).center(),
|
||||
@ -108,6 +115,12 @@ class AboutScreen extends StatelessWidget {
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
InkWell(
|
||||
child: Text('GitHub', style: TextStyle(fontSize: 12)),
|
||||
onTap: () {
|
||||
launchUrlString('https://github.com/Solsynth/HyperNet.Surface');
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -109,11 +109,13 @@ class ChatMessage extends StatelessWidget {
|
||||
onTap: () {
|
||||
if (user == null) return;
|
||||
showPopover(
|
||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||
backgroundColor:
|
||||
Theme.of(context).colorScheme.surface,
|
||||
context: context,
|
||||
transition: PopoverTransition.other,
|
||||
bodyBuilder: (context) => SizedBox(
|
||||
width: math.min(400, MediaQuery.of(context).size.width - 10),
|
||||
width: math.min(
|
||||
400, MediaQuery.of(context).size.width - 10),
|
||||
child: AccountPopoverCard(
|
||||
data: user,
|
||||
),
|
||||
@ -144,11 +146,14 @@ class ChatMessage extends StatelessWidget {
|
||||
radius: 12,
|
||||
).padding(right: 8),
|
||||
Text(
|
||||
(data.sender.nick?.isNotEmpty ?? false) ? data.sender.nick! : user?.nick ?? 'unknown',
|
||||
(data.sender.nick?.isNotEmpty ?? false)
|
||||
? data.sender.nick!
|
||||
: user?.nick ?? 'unknown',
|
||||
).bold(),
|
||||
const Gap(8),
|
||||
Text(
|
||||
dateFormatter.format(data.createdAt.toLocal()),
|
||||
dateFormatter
|
||||
.format(data.createdAt.toLocal()),
|
||||
).fontSize(13),
|
||||
],
|
||||
).height(21),
|
||||
@ -159,7 +164,8 @@ class ChatMessage extends StatelessWidget {
|
||||
maxWidth: 480,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||
borderRadius:
|
||||
const BorderRadius.all(Radius.circular(8)),
|
||||
border: Border.all(
|
||||
color: Theme.of(context).dividerColor,
|
||||
width: 1,
|
||||
@ -207,9 +213,12 @@ class ChatMessage extends StatelessWidget {
|
||||
maxHeight: 560,
|
||||
maxWidth: 480,
|
||||
minWidth: 480,
|
||||
padding: padding.copyWith(top: 8),
|
||||
padding: padding.copyWith(top: 8, left: 48 + padding.left),
|
||||
),
|
||||
if (!hasMerged && !isCompact) const Gap(12) else if (!isCompact) const Gap(8),
|
||||
if (!hasMerged && !isCompact)
|
||||
const Gap(12)
|
||||
else if (!isCompact)
|
||||
const Gap(8),
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -223,7 +232,8 @@ class _ChatMessageText extends StatelessWidget {
|
||||
final Function(SnChatMessage)? onEdit;
|
||||
final Function(SnChatMessage)? onDelete;
|
||||
|
||||
const _ChatMessageText({required this.data, this.onReply, this.onEdit, this.onDelete});
|
||||
const _ChatMessageText(
|
||||
{required this.data, this.onReply, this.onEdit, this.onDelete});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -237,7 +247,8 @@ class _ChatMessageText extends StatelessWidget {
|
||||
children: [
|
||||
SelectionArea(
|
||||
contextMenuBuilder: (context, editableTextState) {
|
||||
final List<ContextMenuButtonItem> items = editableTextState.contextMenuButtonItems;
|
||||
final List<ContextMenuButtonItem> items =
|
||||
editableTextState.contextMenuButtonItems;
|
||||
|
||||
if (onReply != null) {
|
||||
items.insert(
|
||||
@ -286,6 +297,8 @@ class _ChatMessageText extends StatelessWidget {
|
||||
child: MarkdownTextContent(
|
||||
content: data.body['text'],
|
||||
isAutoWarp: true,
|
||||
isEnlargeSticker:
|
||||
RegExp(r"^:([-\w]+):$").hasMatch(data.body['text'] ?? ''),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -44,7 +44,9 @@ class MarkdownTextContent extends StatelessWidget {
|
||||
Theme.of(context),
|
||||
).copyWith(
|
||||
textScaler: textScaler,
|
||||
p: textColor != null ? Theme.of(context).textTheme.bodyMedium!.copyWith(color: textColor) : null,
|
||||
p: textColor != null
|
||||
? Theme.of(context).textTheme.bodyMedium!.copyWith(color: textColor)
|
||||
: null,
|
||||
blockquote: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
@ -115,7 +117,7 @@ class MarkdownTextContent extends StatelessWidget {
|
||||
final alias = segments[1];
|
||||
final st = context.read<SnStickerProvider>();
|
||||
final sn = context.read<SnNetworkProvider>();
|
||||
final double size = isEnlargeSticker ? 128 : 32;
|
||||
final double size = isEnlargeSticker ? 96 : 32;
|
||||
return Container(
|
||||
width: size,
|
||||
height: size,
|
||||
@ -131,7 +133,8 @@ class MarkdownTextContent extends StatelessWidget {
|
||||
if (snapshot.hasData) {
|
||||
return GestureDetector(
|
||||
child: UniversalImage(
|
||||
sn.getAttachmentUrl(snapshot.data!.attachment.rid),
|
||||
sn.getAttachmentUrl(
|
||||
snapshot.data!.attachment.rid),
|
||||
fit: BoxFit.contain,
|
||||
width: size,
|
||||
height: size,
|
||||
@ -177,7 +180,9 @@ class MarkdownTextContent extends StatelessWidget {
|
||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||
child: AspectRatio(
|
||||
aspectRatio: attachment.metadata['ratio'] ??
|
||||
switch (attachment.mimetype.split('/').firstOrNull) {
|
||||
switch (attachment.mimetype
|
||||
.split('/')
|
||||
.firstOrNull) {
|
||||
'audio' => 16 / 9,
|
||||
'video' => 16 / 9,
|
||||
_ => 1,
|
||||
|
@ -31,6 +31,7 @@ class AppScaffold extends StatelessWidget {
|
||||
final AppBar? appBar;
|
||||
final DrawerCallback? onDrawerChanged;
|
||||
final DrawerCallback? onEndDrawerChanged;
|
||||
final bool noBackground;
|
||||
|
||||
const AppScaffold({
|
||||
super.key,
|
||||
@ -45,6 +46,7 @@ class AppScaffold extends StatelessWidget {
|
||||
this.endDrawer,
|
||||
this.onDrawerChanged,
|
||||
this.onEndDrawerChanged,
|
||||
this.noBackground = false,
|
||||
});
|
||||
|
||||
@override
|
||||
@ -52,20 +54,23 @@ class AppScaffold extends StatelessWidget {
|
||||
final appBarHeight = appBar?.preferredSize.height ?? 0;
|
||||
final safeTop = MediaQuery.of(context).padding.top;
|
||||
|
||||
final content = Column(
|
||||
children: [
|
||||
IgnorePointer(
|
||||
child: SizedBox(height: appBar != null ? appBarHeight + safeTop : 0),
|
||||
),
|
||||
if (body != null) Expanded(child: body!),
|
||||
],
|
||||
);
|
||||
|
||||
return Scaffold(
|
||||
extendBody: true,
|
||||
extendBodyBehindAppBar: true,
|
||||
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
body: SizedBox.expand(
|
||||
child: AppBackground(
|
||||
isRoot: true,
|
||||
child: Column(
|
||||
children: [
|
||||
IgnorePointer(child: SizedBox(height: appBar != null ? appBarHeight + safeTop : 0)),
|
||||
if (body != null) Expanded(child: body!),
|
||||
],
|
||||
),
|
||||
),
|
||||
child: noBackground
|
||||
? content
|
||||
: AppBackground(isRoot: true, child: content),
|
||||
),
|
||||
appBar: appBar,
|
||||
bottomNavigationBar: bottomNavigationBar,
|
||||
@ -107,11 +112,19 @@ class AppRootScaffold extends StatelessWidget {
|
||||
final isCollapseDrawer = cfg.drawerIsCollapsed;
|
||||
final isExpandedDrawer = cfg.drawerIsExpanded;
|
||||
|
||||
final routeName = GoRouter.of(context).routerDelegate.currentConfiguration.last.route.name;
|
||||
final isShowBottomNavigation = NavigationProvider.kShowBottomNavScreen.contains(routeName)
|
||||
? ResponsiveBreakpoints.of(context).smallerOrEqualTo(MOBILE)
|
||||
: false;
|
||||
final isPopable = !NavigationProvider.kAllDestination.map((ele) => ele.screen).contains(routeName);
|
||||
final routeName = GoRouter.of(context)
|
||||
.routerDelegate
|
||||
.currentConfiguration
|
||||
.last
|
||||
.route
|
||||
.name;
|
||||
final isShowBottomNavigation =
|
||||
NavigationProvider.kShowBottomNavScreen.contains(routeName)
|
||||
? ResponsiveBreakpoints.of(context).smallerOrEqualTo(MOBILE)
|
||||
: false;
|
||||
final isPopable = !NavigationProvider.kAllDestination
|
||||
.map((ele) => ele.screen)
|
||||
.contains(routeName);
|
||||
|
||||
final innerWidget = isCollapseDrawer
|
||||
? body
|
||||
@ -126,7 +139,9 @@ class AppRootScaffold extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
),
|
||||
child: isExpandedDrawer ? AppNavigationDrawer(elevation: 0) : AppRailNavigation(),
|
||||
child: isExpandedDrawer
|
||||
? AppNavigationDrawer(elevation: 0)
|
||||
: AppRailNavigation(),
|
||||
),
|
||||
Expanded(child: body),
|
||||
],
|
||||
@ -150,7 +165,8 @@ class AppRootScaffold extends StatelessWidget {
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
if (!kIsWeb && (Platform.isWindows || Platform.isLinux || Platform.isMacOS))
|
||||
if (!kIsWeb &&
|
||||
(Platform.isWindows || Platform.isLinux || Platform.isMacOS))
|
||||
WindowTitleBarBox(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
@ -164,12 +180,19 @@ class AppRootScaffold extends StatelessWidget {
|
||||
child: MoveWindow(
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: Platform.isMacOS ? MainAxisAlignment.center : MainAxisAlignment.start,
|
||||
mainAxisAlignment: Platform.isMacOS
|
||||
? MainAxisAlignment.center
|
||||
: MainAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
'Solar Network',
|
||||
style: GoogleFonts.spaceGrotesk(),
|
||||
textAlign: !kIsWeb
|
||||
? Platform.isMacOS
|
||||
? TextAlign.center
|
||||
: null
|
||||
: null,
|
||||
).padding(horizontal: 12, vertical: 5),
|
||||
),
|
||||
if (!Platform.isMacOS)
|
||||
@ -179,9 +202,12 @@ class AppRootScaffold extends StatelessWidget {
|
||||
Expanded(child: MoveWindow()),
|
||||
Row(
|
||||
children: [
|
||||
MinimizeWindowButton(colors: windowButtonColor),
|
||||
MaximizeWindowButton(colors: windowButtonColor),
|
||||
CloseWindowButton(colors: windowButtonColor),
|
||||
MinimizeWindowButton(
|
||||
colors: windowButtonColor),
|
||||
MaximizeWindowButton(
|
||||
colors: windowButtonColor),
|
||||
CloseWindowButton(
|
||||
colors: windowButtonColor),
|
||||
],
|
||||
),
|
||||
],
|
||||
@ -194,16 +220,28 @@ class AppRootScaffold extends StatelessWidget {
|
||||
Expanded(child: innerWidget),
|
||||
],
|
||||
),
|
||||
Positioned(top: safeTop > 0 ? safeTop : 16, right: 8, child: NotifyIndicator()),
|
||||
Positioned(
|
||||
top: safeTop > 0 ? safeTop : 16,
|
||||
right: 8,
|
||||
child: NotifyIndicator()),
|
||||
if (ResponsiveBreakpoints.of(context).smallerOrEqualTo(MOBILE))
|
||||
Positioned(bottom: safeBottom > 0 ? safeBottom : 16, left: 0, right: 0, child: ConnectionIndicator())
|
||||
Positioned(
|
||||
bottom: safeBottom > 0 ? safeBottom : 16,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: ConnectionIndicator())
|
||||
else
|
||||
Positioned(top: safeTop > 0 ? safeTop : 16, left: 0, right: 0, child: ConnectionIndicator()),
|
||||
Positioned(
|
||||
top: safeTop > 0 ? safeTop : 16,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: ConnectionIndicator()),
|
||||
],
|
||||
),
|
||||
drawer: !isExpandedDrawer ? AppNavigationDrawer() : null,
|
||||
drawerEdgeDragWidth: isPopable ? 0 : null,
|
||||
bottomNavigationBar: isShowBottomNavigation ? AppBottomNavigationBar() : null,
|
||||
bottomNavigationBar:
|
||||
isShowBottomNavigation ? AppBottomNavigationBar() : null,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -23,7 +23,8 @@ class NotifyIndicator extends StatefulWidget {
|
||||
State<NotifyIndicator> createState() => _NotifyIndicatorState();
|
||||
}
|
||||
|
||||
class _NotifyIndicatorState extends State<NotifyIndicator> with SingleTickerProviderStateMixin {
|
||||
class _NotifyIndicatorState extends State<NotifyIndicator>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late final AnimationController _animationController = AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(milliseconds: 300),
|
||||
@ -101,7 +102,8 @@ class _NotifyIndicatorState extends State<NotifyIndicator> with SingleTickerProv
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
width: double.infinity,
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: isMobile ? MediaQuery.of(context).size.width - 16 : 360,
|
||||
maxWidth:
|
||||
isMobile ? MediaQuery.of(context).size.width - 16 : 360,
|
||||
),
|
||||
child: Material(
|
||||
elevation: 2,
|
||||
@ -118,7 +120,8 @@ class _NotifyIndicatorState extends State<NotifyIndicator> with SingleTickerProv
|
||||
),
|
||||
)
|
||||
else
|
||||
Icon(kNotificationTopicIcons[current?.topic] ?? Symbols.notifications),
|
||||
Icon(kNotificationTopicIcons[current?.topic] ??
|
||||
Symbols.notifications),
|
||||
const Gap(16),
|
||||
Expanded(
|
||||
child: Column(
|
||||
@ -126,14 +129,20 @@ class _NotifyIndicatorState extends State<NotifyIndicator> with SingleTickerProv
|
||||
children: [
|
||||
Text(
|
||||
current?.title ?? 'Notification',
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium!
|
||||
.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
if (current?.subtitle?.isNotEmpty ?? false)
|
||||
Text(
|
||||
current!.subtitle!,
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium!
|
||||
.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
@ -148,18 +157,25 @@ class _NotifyIndicatorState extends State<NotifyIndicator> with SingleTickerProv
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text(DateFormat('HH:mm').format(current?.createdAt.toLocal() ?? DateTime.now()))
|
||||
.fontSize(12)
|
||||
.padding(right: 2),
|
||||
Text(
|
||||
DateFormat('HH:mm').format(
|
||||
(current?.createdAt ?? DateTime.now())
|
||||
.millisecondsSinceEpoch >
|
||||
0
|
||||
? (current?.createdAt ?? DateTime.now())
|
||||
: DateTime.now()),
|
||||
).fontSize(12).padding(right: 2),
|
||||
const Gap(6),
|
||||
if (current?.metadata['image'] != null)
|
||||
SizedBox(
|
||||
width: 40,
|
||||
height: 40,
|
||||
child: ClipRRect(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(8)),
|
||||
child: AutoResizeUniversalImage(
|
||||
sn.getAttachmentUrl(current?.metadata['image']),
|
||||
sn.getAttachmentUrl(
|
||||
current?.metadata['image']),
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
|
@ -92,9 +92,10 @@ class OpenablePostItem extends StatelessWidget {
|
||||
openColor: Colors.transparent,
|
||||
openElevation: 0,
|
||||
transitionType: ContainerTransitionType.fade,
|
||||
closedColor: Theme.of(context).colorScheme.surfaceContainerLow.withOpacity(
|
||||
cfg.prefs.getBool(kAppBackgroundStoreKey) == true ? 0.75 : 1,
|
||||
),
|
||||
closedColor:
|
||||
Theme.of(context).colorScheme.surfaceContainerLow.withOpacity(
|
||||
cfg.prefs.getBool(kAppBackgroundStoreKey) == true ? 0.75 : 1,
|
||||
),
|
||||
closedShape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(16)),
|
||||
),
|
||||
@ -135,9 +136,11 @@ class PostItem extends StatelessWidget {
|
||||
final box = context.findRenderObject() as RenderBox?;
|
||||
final url = 'https://solsynth.dev/posts/${data.id}';
|
||||
if (!kIsWeb && (Platform.isAndroid || Platform.isIOS)) {
|
||||
Share.shareUri(Uri.parse(url), sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size);
|
||||
Share.shareUri(Uri.parse(url),
|
||||
sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size);
|
||||
} else {
|
||||
Share.share(url, sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size);
|
||||
Share.share(url,
|
||||
sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size);
|
||||
}
|
||||
}
|
||||
|
||||
@ -155,7 +158,8 @@ class PostItem extends StatelessWidget {
|
||||
child: MultiProvider(
|
||||
providers: [
|
||||
Provider<SnNetworkProvider>(create: (_) => context.read()),
|
||||
ChangeNotifierProvider<ConfigProvider>(create: (_) => context.read()),
|
||||
ChangeNotifierProvider<ConfigProvider>(
|
||||
create: (_) => context.read()),
|
||||
],
|
||||
child: ResponsiveBreakpoints.builder(
|
||||
breakpoints: ResponsiveBreakpoints.of(context).breakpoints,
|
||||
@ -183,7 +187,8 @@ class PostItem extends StatelessWidget {
|
||||
sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size,
|
||||
);
|
||||
} else {
|
||||
await FileSaver.instance.saveFile(name: 'Solar Network Post #${data.id}.png', file: imageFile);
|
||||
await FileSaver.instance.saveFile(
|
||||
name: 'Solar Network Post #${data.id}.png', file: imageFile);
|
||||
}
|
||||
|
||||
await imageFile.delete();
|
||||
@ -197,7 +202,9 @@ class PostItem extends StatelessWidget {
|
||||
final isAuthor = ua.isAuthorized && data.publisher.accountId == ua.user?.id;
|
||||
|
||||
// Video full view
|
||||
if (showFullPost && data.type == 'video' && ResponsiveBreakpoints.of(context).largerThan(TABLET)) {
|
||||
if (showFullPost &&
|
||||
data.type == 'video' &&
|
||||
ResponsiveBreakpoints.of(context).largerThan(TABLET)) {
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
@ -217,7 +224,8 @@ class PostItem extends StatelessWidget {
|
||||
if (onDeleted != null) {}
|
||||
},
|
||||
).padding(bottom: 8),
|
||||
if (data.preload?.video != null) _PostVideoPlayer(data: data).padding(bottom: 8),
|
||||
if (data.preload?.video != null)
|
||||
_PostVideoPlayer(data: data).padding(bottom: 8),
|
||||
_PostHeadline(data: data).padding(horizontal: 4, bottom: 8),
|
||||
_PostFeaturedComment(data: data),
|
||||
_PostBottomAction(
|
||||
@ -265,7 +273,8 @@ class PostItem extends StatelessWidget {
|
||||
if (onDeleted != null) {}
|
||||
},
|
||||
).padding(horizontal: 12, top: 8, bottom: 8),
|
||||
if (data.preload?.video != null) _PostVideoPlayer(data: data).padding(horizontal: 12, bottom: 8),
|
||||
if (data.preload?.video != null)
|
||||
_PostVideoPlayer(data: data).padding(horizontal: 12, bottom: 8),
|
||||
Container(
|
||||
width: double.infinity,
|
||||
margin: const EdgeInsets.only(bottom: 4, left: 12, right: 12),
|
||||
@ -308,8 +317,13 @@ class PostItem extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
),
|
||||
Text('postArticle').tr().fontSize(13).opacity(0.75).padding(horizontal: 24, bottom: 8),
|
||||
_PostFeaturedComment(data: data, maxWidth: maxWidth).padding(horizontal: 12),
|
||||
Text('postArticle')
|
||||
.tr()
|
||||
.fontSize(13)
|
||||
.opacity(0.75)
|
||||
.padding(horizontal: 24, bottom: 8),
|
||||
_PostFeaturedComment(data: data, maxWidth: maxWidth)
|
||||
.padding(horizontal: 12),
|
||||
_PostBottomAction(
|
||||
data: data,
|
||||
showComments: showComments,
|
||||
@ -324,7 +338,8 @@ class PostItem extends StatelessWidget {
|
||||
}
|
||||
|
||||
final displayableAttachments = data.preload?.attachments
|
||||
?.where((ele) => ele?.mediaType != SnMediaType.image || data.type != 'article')
|
||||
?.where((ele) =>
|
||||
ele?.mediaType != SnMediaType.image || data.type != 'article')
|
||||
.toList();
|
||||
|
||||
final cfg = context.read<ConfigProvider>();
|
||||
@ -349,9 +364,13 @@ class PostItem extends StatelessWidget {
|
||||
if (onDeleted != null) onDeleted!();
|
||||
},
|
||||
).padding(horizontal: 12, vertical: 8),
|
||||
if (data.preload?.video != null) _PostVideoPlayer(data: data).padding(horizontal: 12, bottom: 8),
|
||||
if (data.type == 'question') _PostQuestionHint(data: data).padding(horizontal: 16, bottom: 8),
|
||||
if (data.body['title'] != null || data.body['description'] != null)
|
||||
if (data.preload?.video != null)
|
||||
_PostVideoPlayer(data: data).padding(horizontal: 12, bottom: 8),
|
||||
if (data.type == 'question')
|
||||
_PostQuestionHint(data: data)
|
||||
.padding(horizontal: 16, bottom: 8),
|
||||
if (data.body['title'] != null ||
|
||||
data.body['description'] != null)
|
||||
_PostHeadline(
|
||||
data: data,
|
||||
isEnlarge: data.type == 'article' && showFullPost,
|
||||
@ -365,7 +384,8 @@ class PostItem extends StatelessWidget {
|
||||
if (data.repostTo != null)
|
||||
_PostQuoteContent(child: data.repostTo!).padding(
|
||||
horizontal: 12,
|
||||
bottom: data.preload?.attachments?.isNotEmpty ?? false ? 12 : 0,
|
||||
bottom:
|
||||
data.preload?.attachments?.isNotEmpty ?? false ? 12 : 0,
|
||||
),
|
||||
if (data.visibility > 0)
|
||||
_PostVisibilityHint(data: data).padding(
|
||||
@ -377,7 +397,9 @@ class PostItem extends StatelessWidget {
|
||||
horizontal: 16,
|
||||
vertical: 4,
|
||||
),
|
||||
if (data.tags.isNotEmpty) _PostTagsList(data: data).padding(horizontal: 16, top: 4, bottom: 6),
|
||||
if (data.tags.isNotEmpty)
|
||||
_PostTagsList(data: data)
|
||||
.padding(horizontal: 16, top: 4, bottom: 6),
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -390,12 +412,16 @@ class PostItem extends StatelessWidget {
|
||||
fit: showFullPost ? BoxFit.cover : BoxFit.contain,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
),
|
||||
if (data.preload?.poll != null) PostPoll(poll: data.preload!.poll!).padding(horizontal: 12, vertical: 4),
|
||||
if (data.body['content'] != null && (cfg.prefs.getBool(kAppExpandPostLink) ?? true))
|
||||
if (data.preload?.poll != null)
|
||||
PostPoll(poll: data.preload!.poll!)
|
||||
.padding(horizontal: 12, vertical: 4),
|
||||
if (data.body['content'] != null &&
|
||||
(cfg.prefs.getBool(kAppExpandPostLink) ?? true))
|
||||
LinkPreviewWidget(
|
||||
text: data.body['content'],
|
||||
).padding(horizontal: 4),
|
||||
_PostFeaturedComment(data: data, maxWidth: maxWidth).padding(horizontal: 12),
|
||||
_PostFeaturedComment(data: data, maxWidth: maxWidth)
|
||||
.padding(horizontal: 12),
|
||||
Container(
|
||||
constraints: BoxConstraints(maxWidth: maxWidth ?? double.infinity),
|
||||
child: Column(
|
||||
@ -457,7 +483,8 @@ class PostShareImageWidget extends StatelessWidget {
|
||||
showMenu: false,
|
||||
isRelativeDate: false,
|
||||
).padding(horizontal: 16, bottom: 8),
|
||||
if (data.type == 'question') _PostQuestionHint(data: data).padding(horizontal: 16, bottom: 8),
|
||||
if (data.type == 'question')
|
||||
_PostQuestionHint(data: data).padding(horizontal: 16, bottom: 8),
|
||||
_PostHeadline(
|
||||
data: data,
|
||||
isEnlarge: data.type == 'article',
|
||||
@ -472,7 +499,8 @@ class PostShareImageWidget extends StatelessWidget {
|
||||
child: data.repostTo!,
|
||||
isRelativeDate: false,
|
||||
).padding(horizontal: 16, bottom: 8),
|
||||
if (data.type != 'article' && (data.preload?.attachments?.isNotEmpty ?? false))
|
||||
if (data.type != 'article' &&
|
||||
(data.preload?.attachments?.isNotEmpty ?? false))
|
||||
StyledWidget(AttachmentList(
|
||||
data: data.preload!.attachments!,
|
||||
columned: true,
|
||||
@ -481,7 +509,8 @@ class PostShareImageWidget extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (data.visibility > 0) _PostVisibilityHint(data: data),
|
||||
if (data.body['content_truncated'] == true) _PostTruncatedHint(data: data),
|
||||
if (data.body['content_truncated'] == true)
|
||||
_PostTruncatedHint(data: data),
|
||||
],
|
||||
).padding(horizontal: 16),
|
||||
_PostBottomAction(
|
||||
@ -541,7 +570,8 @@ class PostShareImageWidget extends StatelessWidget {
|
||||
version: QrVersions.auto,
|
||||
size: 100,
|
||||
gapless: true,
|
||||
embeddedImage: AssetImage('assets/icon/icon-light-radius.png'),
|
||||
embeddedImage:
|
||||
AssetImage('assets/icon/icon-light-radius.png'),
|
||||
embeddedImageStyle: QrEmbeddedImageStyle(
|
||||
size: Size(28, 28),
|
||||
),
|
||||
@ -572,9 +602,11 @@ class _PostQuestionHint extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
children: [
|
||||
Icon(data.body['answer'] == null ? Symbols.help : Symbols.check_circle, size: 20),
|
||||
Icon(data.body['answer'] == null ? Symbols.help : Symbols.check_circle,
|
||||
size: 20),
|
||||
const Gap(4),
|
||||
if (data.body['answer'] == null && data.body['reward']?.toDouble() != null)
|
||||
if (data.body['answer'] == null &&
|
||||
data.body['reward']?.toDouble() != null)
|
||||
Text('postQuestionUnansweredWithReward'.tr(args: [
|
||||
'${data.body['reward']}',
|
||||
])).opacity(0.75)
|
||||
@ -610,7 +642,9 @@ class _PostBottomAction extends StatelessWidget {
|
||||
);
|
||||
|
||||
final String? mostTypicalReaction = data.metric.reactionList.isNotEmpty
|
||||
? data.metric.reactionList.entries.reduce((a, b) => a.value > b.value ? a : b).key
|
||||
? data.metric.reactionList.entries
|
||||
.reduce((a, b) => a.value > b.value ? a : b)
|
||||
.key
|
||||
: null;
|
||||
|
||||
return Row(
|
||||
@ -624,7 +658,8 @@ class _PostBottomAction extends StatelessWidget {
|
||||
InkWell(
|
||||
child: Row(
|
||||
children: [
|
||||
if (mostTypicalReaction == null || kTemplateReactions[mostTypicalReaction] == null)
|
||||
if (mostTypicalReaction == null ||
|
||||
kTemplateReactions[mostTypicalReaction] == null)
|
||||
Icon(Symbols.add_reaction, size: 20, color: iconColor)
|
||||
else
|
||||
Text(
|
||||
@ -636,7 +671,8 @@ class _PostBottomAction extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
const Gap(8),
|
||||
if (data.totalUpvote > 0 && data.totalUpvote >= data.totalDownvote)
|
||||
if (data.totalUpvote > 0 &&
|
||||
data.totalUpvote >= data.totalDownvote)
|
||||
Text('postReactionUpvote').plural(
|
||||
data.totalUpvote,
|
||||
)
|
||||
@ -655,8 +691,12 @@ class _PostBottomAction extends StatelessWidget {
|
||||
data: data,
|
||||
onChanged: (value, attr, delta) {
|
||||
onChanged(data.copyWith(
|
||||
totalUpvote: attr == 1 ? data.totalUpvote + delta : data.totalUpvote,
|
||||
totalDownvote: attr == 2 ? data.totalDownvote + delta : data.totalDownvote,
|
||||
totalUpvote: attr == 1
|
||||
? data.totalUpvote + delta
|
||||
: data.totalUpvote,
|
||||
totalDownvote: attr == 2
|
||||
? data.totalDownvote + delta
|
||||
: data.totalDownvote,
|
||||
metric: data.metric.copyWith(reactionList: value),
|
||||
));
|
||||
},
|
||||
@ -904,8 +944,10 @@ class _PostContentHeader extends StatelessWidget {
|
||||
const Gap(4),
|
||||
Text(
|
||||
isRelativeDate
|
||||
? RelativeTime(context).format(data.publishedAt ?? data.createdAt)
|
||||
: DateFormat('y/M/d HH:mm').format(data.publishedAt ?? data.createdAt),
|
||||
? RelativeTime(context)
|
||||
.format(data.publishedAt ?? data.createdAt)
|
||||
: DateFormat('y/M/d HH:mm')
|
||||
.format(data.publishedAt ?? data.createdAt),
|
||||
).fontSize(13),
|
||||
],
|
||||
).opacity(0.8),
|
||||
@ -923,8 +965,10 @@ class _PostContentHeader extends StatelessWidget {
|
||||
const Gap(4),
|
||||
Text(
|
||||
isRelativeDate
|
||||
? RelativeTime(context).format(data.publishedAt ?? data.createdAt)
|
||||
: DateFormat('y/M/d HH:mm').format(data.publishedAt ?? data.createdAt),
|
||||
? RelativeTime(context)
|
||||
.format(data.publishedAt ?? data.createdAt)
|
||||
: DateFormat('y/M/d HH:mm')
|
||||
.format(data.publishedAt ?? data.createdAt),
|
||||
).fontSize(13),
|
||||
],
|
||||
).opacity(0.8),
|
||||
@ -1107,7 +1151,8 @@ class _PostContentBody extends StatelessWidget {
|
||||
if (data.body['content'] == null) return const SizedBox.shrink();
|
||||
final content = MarkdownTextContent(
|
||||
isAutoWarp: data.type == 'story',
|
||||
isEnlargeSticker: true,
|
||||
isEnlargeSticker:
|
||||
RegExp(r"^:([-\w]+):$").hasMatch(data.body['content'] ?? ''),
|
||||
textScaler: isEnlarge ? TextScaler.linear(1.1) : null,
|
||||
content: data.body['content'],
|
||||
attachments: data.preload?.attachments,
|
||||
@ -1156,10 +1201,12 @@ class _PostQuoteContent extends StatelessWidget {
|
||||
onDeleted: () {},
|
||||
).padding(bottom: 4),
|
||||
_PostContentBody(data: child),
|
||||
if (child.visibility > 0) _PostVisibilityHint(data: child).padding(top: 4),
|
||||
if (child.visibility > 0)
|
||||
_PostVisibilityHint(data: child).padding(top: 4),
|
||||
],
|
||||
).padding(horizontal: 16),
|
||||
if (child.type != 'article' && (child.preload?.attachments?.isNotEmpty ?? false))
|
||||
if (child.type != 'article' &&
|
||||
(child.preload?.attachments?.isNotEmpty ?? false))
|
||||
ClipRRect(
|
||||
borderRadius: const BorderRadius.only(
|
||||
bottomLeft: Radius.circular(8),
|
||||
@ -1310,7 +1357,9 @@ class _PostTruncatedHint extends StatelessWidget {
|
||||
const Gap(4),
|
||||
Text('postReadEstimate').tr(args: [
|
||||
'${Duration(
|
||||
seconds: (data.body['content_length'] as num).toDouble() * 60 ~/ kHumanReadSpeed,
|
||||
seconds: (data.body['content_length'] as num).toDouble() *
|
||||
60 ~/
|
||||
kHumanReadSpeed,
|
||||
).inSeconds}s',
|
||||
]),
|
||||
],
|
||||
@ -1349,7 +1398,8 @@ class _PostFeaturedCommentState extends State<_PostFeaturedComment> {
|
||||
// If this is a answered question, fetch the answer instead
|
||||
if (widget.data.type == 'question' && widget.data.body['answer'] != null) {
|
||||
final sn = context.read<SnNetworkProvider>();
|
||||
final resp = await sn.client.get('/cgi/co/posts/${widget.data.body['answer']}');
|
||||
final resp =
|
||||
await sn.client.get('/cgi/co/posts/${widget.data.body['answer']}');
|
||||
_isAnswer = true;
|
||||
setState(() => _featuredComment = SnPost.fromJson(resp.data));
|
||||
return;
|
||||
@ -1357,9 +1407,11 @@ class _PostFeaturedCommentState extends State<_PostFeaturedComment> {
|
||||
|
||||
try {
|
||||
final sn = context.read<SnNetworkProvider>();
|
||||
final resp = await sn.client.get('/cgi/co/posts/${widget.data.id}/replies/featured', queryParameters: {
|
||||
'take': 1,
|
||||
});
|
||||
final resp = await sn.client.get(
|
||||
'/cgi/co/posts/${widget.data.id}/replies/featured',
|
||||
queryParameters: {
|
||||
'take': 1,
|
||||
});
|
||||
setState(() => _featuredComment = SnPost.fromJson(resp.data[0]));
|
||||
} catch (err) {
|
||||
if (!mounted) return;
|
||||
@ -1388,7 +1440,9 @@ class _PostFeaturedCommentState extends State<_PostFeaturedComment> {
|
||||
width: double.infinity,
|
||||
child: Material(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||
color: _isAnswer ? Colors.green.withOpacity(0.5) : Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||
color: _isAnswer
|
||||
? Colors.green.withOpacity(0.5)
|
||||
: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||
child: InkWell(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||
onTap: () {
|
||||
@ -1408,11 +1462,17 @@ class _PostFeaturedCommentState extends State<_PostFeaturedComment> {
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
const Gap(2),
|
||||
Icon(_isAnswer ? Symbols.task_alt : Symbols.prompt_suggestion, size: 20),
|
||||
Icon(_isAnswer ? Symbols.task_alt : Symbols.prompt_suggestion,
|
||||
size: 20),
|
||||
const Gap(10),
|
||||
Text(
|
||||
_isAnswer ? 'postQuestionAnswerTitle' : 'postFeaturedComment',
|
||||
style: Theme.of(context).textTheme.titleMedium!.copyWith(fontSize: 15),
|
||||
_isAnswer
|
||||
? 'postQuestionAnswerTitle'
|
||||
: 'postFeaturedComment',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleMedium!
|
||||
.copyWith(fontSize: 15),
|
||||
).tr(),
|
||||
],
|
||||
),
|
||||
@ -1550,7 +1610,8 @@ class _PostGetInsightPopupState extends State<_PostGetInsightPopup> {
|
||||
}
|
||||
|
||||
RegExp cleanThinkingRegExp = RegExp(r'<think>[\s\S]*?</think>');
|
||||
setState(() => _response = out.replaceAll(cleanThinkingRegExp, '').trim());
|
||||
setState(
|
||||
() => _response = out.replaceAll(cleanThinkingRegExp, '').trim());
|
||||
} catch (err) {
|
||||
if (!mounted) return;
|
||||
context.showErrorDialog(err);
|
||||
@ -1573,11 +1634,16 @@ class _PostGetInsightPopupState extends State<_PostGetInsightPopup> {
|
||||
children: [
|
||||
const Icon(Symbols.book_4_spark, size: 24),
|
||||
const Gap(16),
|
||||
Text('postGetInsightTitle', style: Theme.of(context).textTheme.titleLarge).tr(),
|
||||
Text('postGetInsightTitle',
|
||||
style: Theme.of(context).textTheme.titleLarge)
|
||||
.tr(),
|
||||
],
|
||||
).padding(horizontal: 20, top: 16, bottom: 12),
|
||||
const Gap(4),
|
||||
Text('postGetInsightDescription', style: Theme.of(context).textTheme.bodySmall).tr().padding(horizontal: 20),
|
||||
Text('postGetInsightDescription',
|
||||
style: Theme.of(context).textTheme.bodySmall)
|
||||
.tr()
|
||||
.padding(horizontal: 20),
|
||||
const Gap(4),
|
||||
if (_response == null)
|
||||
Expanded(
|
||||
@ -1595,12 +1661,16 @@ class _PostGetInsightPopupState extends State<_PostGetInsightPopup> {
|
||||
leading: const Icon(Symbols.info),
|
||||
title: Text('aiThinkingProcess'.tr()),
|
||||
tilePadding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
collapsedBackgroundColor: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||
collapsedBackgroundColor:
|
||||
Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||
minTileHeight: 32,
|
||||
children: [
|
||||
SelectableText(
|
||||
_thinkingProcess!,
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(fontStyle: FontStyle.italic),
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium!
|
||||
.copyWith(fontStyle: FontStyle.italic),
|
||||
).padding(horizontal: 20, vertical: 8),
|
||||
],
|
||||
).padding(vertical: 8),
|
||||
@ -1637,7 +1707,8 @@ class _PostVideoPlayer extends StatelessWidget {
|
||||
aspectRatio: 16 / 9,
|
||||
child: ClipRRect(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||
child: AttachmentItem(data: data.preload!.video!, heroTag: 'post-video-${data.id}'),
|
||||
child: AttachmentItem(
|
||||
data: data.preload!.video!, heroTag: 'post-video-${data.id}'),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
@ -30,25 +30,21 @@ import 'package:surface/widgets/universal_image.dart';
|
||||
import '../attachment/pending_attachment_compress.dart';
|
||||
|
||||
class PostMediaPendingList extends StatelessWidget {
|
||||
final PostWriteMedia? thumbnail;
|
||||
final List<PostWriteMedia> attachments;
|
||||
final bool isBusy;
|
||||
final Future<void> Function(int idx, PostWriteMedia updatedMedia)? onUpdate;
|
||||
final Future<void> Function(int idx)? onRemove;
|
||||
final Future<void> Function(int idx)? onUpload;
|
||||
final void Function(int? idx)? onPostSetThumbnail;
|
||||
final void Function(int idx)? onInsertLink;
|
||||
final void Function(bool state)? onUpdateBusy;
|
||||
|
||||
const PostMediaPendingList({
|
||||
super.key,
|
||||
this.thumbnail,
|
||||
required this.attachments,
|
||||
required this.isBusy,
|
||||
this.onUpdate,
|
||||
this.onRemove,
|
||||
this.onUpload,
|
||||
this.onPostSetThumbnail,
|
||||
this.onInsertLink,
|
||||
this.onUpdateBusy,
|
||||
});
|
||||
@ -116,7 +112,7 @@ class PostMediaPendingList extends StatelessWidget {
|
||||
}
|
||||
|
||||
Future<void> _deleteAttachment(BuildContext context, int idx) async {
|
||||
final media = idx == -1 ? thumbnail! : attachments[idx];
|
||||
final media = attachments[idx];
|
||||
if (media.attachment == null) return;
|
||||
|
||||
try {
|
||||
@ -212,22 +208,6 @@ class PostMediaPendingList extends StatelessWidget {
|
||||
onSelected: () {
|
||||
onUpload!(idx);
|
||||
}),
|
||||
if (media.attachment != null && media.type == SnMediaType.image && onPostSetThumbnail != null && idx != -1)
|
||||
MenuItem(
|
||||
label: 'attachmentSetAsPostThumbnail'.tr(),
|
||||
icon: Symbols.gallery_thumbnail,
|
||||
onSelected: () {
|
||||
onPostSetThumbnail!(idx);
|
||||
},
|
||||
)
|
||||
else if (media.attachment != null && media.type == SnMediaType.image && onPostSetThumbnail != null)
|
||||
MenuItem(
|
||||
label: 'attachmentUnsetAsPostThumbnail'.tr(),
|
||||
icon: Symbols.cancel,
|
||||
onSelected: () {
|
||||
onPostSetThumbnail!(null);
|
||||
},
|
||||
),
|
||||
if (media.attachment != null && onInsertLink != null)
|
||||
MenuItem(
|
||||
label: 'attachmentInsertLink'.tr(),
|
||||
@ -291,35 +271,18 @@ class PostMediaPendingList extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
constraints: const BoxConstraints(maxHeight: 120),
|
||||
child: Row(
|
||||
children: [
|
||||
const Gap(16),
|
||||
if (thumbnail != null)
|
||||
ContextMenuArea(
|
||||
contextMenu: _createContextMenu(context, -1, thumbnail!),
|
||||
child: _PostMediaPendingItem(media: thumbnail!),
|
||||
),
|
||||
if (thumbnail != null)
|
||||
const VerticalDivider(width: 1, thickness: 1).padding(
|
||||
horizontal: 12,
|
||||
vertical: 16,
|
||||
),
|
||||
Expanded(
|
||||
child: ListView.separated(
|
||||
scrollDirection: Axis.horizontal,
|
||||
padding: const EdgeInsets.only(right: 8),
|
||||
separatorBuilder: (context, index) => const Gap(8),
|
||||
itemCount: attachments.length,
|
||||
itemBuilder: (context, idx) {
|
||||
final media = attachments[idx];
|
||||
return ContextMenuArea(
|
||||
contextMenu: _createContextMenu(context, idx, media),
|
||||
child: _PostMediaPendingItem(media: media),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
child: ListView.separated(
|
||||
scrollDirection: Axis.horizontal,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
separatorBuilder: (context, index) => const Gap(8),
|
||||
itemCount: attachments.length,
|
||||
itemBuilder: (context, idx) {
|
||||
final media = attachments[idx];
|
||||
return ContextMenuArea(
|
||||
contextMenu: _createContextMenu(context, idx, media),
|
||||
child: _PostMediaPendingItem(media: media),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
149
lib/widgets/realm/realm_item.dart
Normal file
149
lib/widgets/realm/realm_item.dart
Normal file
@ -0,0 +1,149 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
import 'package:surface/providers/sn_network.dart';
|
||||
import 'package:surface/types/realm.dart';
|
||||
import 'package:surface/widgets/account/account_image.dart';
|
||||
import 'package:surface/widgets/universal_image.dart';
|
||||
|
||||
class RealmItemWidget extends StatelessWidget {
|
||||
final SnRealm item;
|
||||
final bool isListView;
|
||||
final List<PopupMenuItem>? actionListView;
|
||||
final Function? onUpdate;
|
||||
final Function? onTap;
|
||||
final bool showPopularity;
|
||||
|
||||
const RealmItemWidget({
|
||||
super.key,
|
||||
required this.item,
|
||||
required this.isListView,
|
||||
this.actionListView,
|
||||
this.onUpdate,
|
||||
this.onTap,
|
||||
this.showPopularity = true,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (isListView) {
|
||||
return ListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
leading: AccountImage(
|
||||
content: item.avatar,
|
||||
fallbackWidget: const Icon(Symbols.group, size: 20),
|
||||
),
|
||||
title: Text(item.name),
|
||||
subtitle: Row(
|
||||
children: [
|
||||
if (showPopularity) const Icon(Symbols.local_fire_department, size: 18).padding(right: 1),
|
||||
if (showPopularity) Text(item.popularity.toString()),
|
||||
if (showPopularity) const Gap(6),
|
||||
Expanded(
|
||||
child: Text(
|
||||
item.description,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
trailing:
|
||||
actionListView != null ? PopupMenuButton(itemBuilder: (BuildContext context) => actionListView!) : null,
|
||||
onTap: () {
|
||||
if (onTap != null) {
|
||||
onTap!();
|
||||
return;
|
||||
}
|
||||
GoRouter.of(context).pushNamed(
|
||||
'realmDetail',
|
||||
pathParameters: {'alias': item.alias},
|
||||
).then((value) {
|
||||
if (value == true) {
|
||||
onUpdate?.call();
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
final sn = context.read<SnNetworkProvider>();
|
||||
|
||||
return Container(
|
||||
constraints: BoxConstraints(maxWidth: 640),
|
||||
child: Card(
|
||||
margin: const EdgeInsets.all(12),
|
||||
child: InkWell(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
AspectRatio(
|
||||
aspectRatio: 16 / 7,
|
||||
child: Stack(
|
||||
clipBehavior: Clip.none,
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||
child: Container(
|
||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||
child: (item.banner?.isEmpty ?? true)
|
||||
? const SizedBox.shrink()
|
||||
: AutoResizeUniversalImage(
|
||||
sn.getAttachmentUrl(item.banner!),
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
bottom: -30,
|
||||
left: 18,
|
||||
child: AccountImage(
|
||||
content: item.avatar,
|
||||
radius: 24,
|
||||
fallbackWidget: const Icon(Symbols.group, size: 24),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const Gap(20 + 12),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(item.name).textStyle(Theme.of(context).textTheme.titleMedium!),
|
||||
if (showPopularity)
|
||||
Row(
|
||||
children: [
|
||||
Text(item.popularity.toString()),
|
||||
const Icon(Symbols.local_fire_department, size: 16).padding(bottom: 2),
|
||||
],
|
||||
).padding(top: 6),
|
||||
Text(item.description).textStyle(Theme.of(context).textTheme.bodySmall!),
|
||||
],
|
||||
).padding(horizontal: 24, bottom: 14),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
if (onTap != null) {
|
||||
onTap!();
|
||||
return;
|
||||
}
|
||||
GoRouter.of(context).pushNamed(
|
||||
'realmDetail',
|
||||
pathParameters: {'alias': item.alias},
|
||||
).then((value) {
|
||||
if (value == true) {
|
||||
onUpdate?.call();
|
||||
}
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
).center();
|
||||
}
|
||||
}
|
@ -12,9 +12,11 @@
|
||||
#include <flutter_udid/flutter_udid_plugin.h>
|
||||
#include <flutter_webrtc/flutter_web_r_t_c_plugin.h>
|
||||
#include <hotkey_manager_linux/hotkey_manager_linux_plugin.h>
|
||||
#include <local_notifier/local_notifier_plugin.h>
|
||||
#include <media_kit_libs_linux/media_kit_libs_linux_plugin.h>
|
||||
#include <media_kit_video/media_kit_video_plugin.h>
|
||||
#include <pasteboard/pasteboard_plugin.h>
|
||||
#include <sqlite3_flutter_libs/sqlite3_flutter_libs_plugin.h>
|
||||
#include <tray_manager/tray_manager_plugin.h>
|
||||
#include <url_launcher_linux/url_launcher_plugin.h>
|
||||
|
||||
@ -37,6 +39,9 @@ void fl_register_plugins(FlPluginRegistry* registry) {
|
||||
g_autoptr(FlPluginRegistrar) hotkey_manager_linux_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "HotkeyManagerLinuxPlugin");
|
||||
hotkey_manager_linux_plugin_register_with_registrar(hotkey_manager_linux_registrar);
|
||||
g_autoptr(FlPluginRegistrar) local_notifier_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "LocalNotifierPlugin");
|
||||
local_notifier_plugin_register_with_registrar(local_notifier_registrar);
|
||||
g_autoptr(FlPluginRegistrar) media_kit_libs_linux_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "MediaKitLibsLinuxPlugin");
|
||||
media_kit_libs_linux_plugin_register_with_registrar(media_kit_libs_linux_registrar);
|
||||
@ -46,6 +51,9 @@ void fl_register_plugins(FlPluginRegistry* registry) {
|
||||
g_autoptr(FlPluginRegistrar) pasteboard_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "PasteboardPlugin");
|
||||
pasteboard_plugin_register_with_registrar(pasteboard_registrar);
|
||||
g_autoptr(FlPluginRegistrar) sqlite3_flutter_libs_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "Sqlite3FlutterLibsPlugin");
|
||||
sqlite3_flutter_libs_plugin_register_with_registrar(sqlite3_flutter_libs_registrar);
|
||||
g_autoptr(FlPluginRegistrar) tray_manager_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "TrayManagerPlugin");
|
||||
tray_manager_plugin_register_with_registrar(tray_manager_registrar);
|
||||
|
@ -9,9 +9,11 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
||||
flutter_udid
|
||||
flutter_webrtc
|
||||
hotkey_manager_linux
|
||||
local_notifier
|
||||
media_kit_libs_linux
|
||||
media_kit_video
|
||||
pasteboard
|
||||
sqlite3_flutter_libs
|
||||
tray_manager
|
||||
url_launcher_linux
|
||||
)
|
||||
|
@ -21,6 +21,7 @@ import gal
|
||||
import hotkey_manager_macos
|
||||
import in_app_review
|
||||
import livekit_client
|
||||
import local_notifier
|
||||
import media_kit_libs_macos_video
|
||||
import media_kit_video
|
||||
import package_info_plus
|
||||
@ -30,6 +31,7 @@ import screen_brightness_macos
|
||||
import share_plus
|
||||
import shared_preferences_foundation
|
||||
import sqflite_darwin
|
||||
import sqlite3_flutter_libs
|
||||
import tray_manager
|
||||
import url_launcher_macos
|
||||
import video_compress
|
||||
@ -52,6 +54,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
HotkeyManagerMacosPlugin.register(with: registry.registrar(forPlugin: "HotkeyManagerMacosPlugin"))
|
||||
InAppReviewPlugin.register(with: registry.registrar(forPlugin: "InAppReviewPlugin"))
|
||||
LiveKitPlugin.register(with: registry.registrar(forPlugin: "LiveKitPlugin"))
|
||||
LocalNotifierPlugin.register(with: registry.registrar(forPlugin: "LocalNotifierPlugin"))
|
||||
MediaKitLibsMacosVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitLibsMacosVideoPlugin"))
|
||||
MediaKitVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitVideoPlugin"))
|
||||
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
|
||||
@ -61,6 +64,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
|
||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
|
||||
Sqlite3FlutterLibsPlugin.register(with: registry.registrar(forPlugin: "Sqlite3FlutterLibsPlugin"))
|
||||
TrayManagerPlugin.register(with: registry.registrar(forPlugin: "TrayManagerPlugin"))
|
||||
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
||||
VideoCompressPlugin.register(with: registry.registrar(forPlugin: "VideoCompressPlugin"))
|
||||
|
@ -13,59 +13,59 @@ PODS:
|
||||
- FlutterMacOS
|
||||
- file_selector_macos (0.0.1):
|
||||
- FlutterMacOS
|
||||
- Firebase/Analytics (11.7.0):
|
||||
- Firebase/Analytics (11.8.0):
|
||||
- Firebase/Core
|
||||
- Firebase/Core (11.7.0):
|
||||
- Firebase/Core (11.8.0):
|
||||
- Firebase/CoreOnly
|
||||
- FirebaseAnalytics (~> 11.7.0)
|
||||
- Firebase/CoreOnly (11.7.0):
|
||||
- FirebaseCore (~> 11.7.0)
|
||||
- Firebase/Messaging (11.7.0):
|
||||
- FirebaseAnalytics (~> 11.8.0)
|
||||
- Firebase/CoreOnly (11.8.0):
|
||||
- FirebaseCore (~> 11.8.0)
|
||||
- Firebase/Messaging (11.8.0):
|
||||
- Firebase/CoreOnly
|
||||
- FirebaseMessaging (~> 11.7.0)
|
||||
- firebase_analytics (11.4.2):
|
||||
- Firebase/Analytics (= 11.7.0)
|
||||
- FirebaseMessaging (~> 11.8.0)
|
||||
- firebase_analytics (11.4.3):
|
||||
- Firebase/Analytics (= 11.8.0)
|
||||
- firebase_core
|
||||
- FlutterMacOS
|
||||
- firebase_core (3.11.0):
|
||||
- Firebase/CoreOnly (~> 11.7.0)
|
||||
- firebase_core (3.12.0):
|
||||
- Firebase/CoreOnly (~> 11.8.0)
|
||||
- FlutterMacOS
|
||||
- firebase_messaging (15.2.2):
|
||||
- Firebase/CoreOnly (~> 11.7.0)
|
||||
- Firebase/Messaging (~> 11.7.0)
|
||||
- firebase_messaging (15.2.3):
|
||||
- Firebase/CoreOnly (~> 11.8.0)
|
||||
- Firebase/Messaging (~> 11.8.0)
|
||||
- firebase_core
|
||||
- FlutterMacOS
|
||||
- FirebaseAnalytics (11.7.0):
|
||||
- FirebaseAnalytics/AdIdSupport (= 11.7.0)
|
||||
- FirebaseCore (~> 11.7.0)
|
||||
- FirebaseAnalytics (11.8.0):
|
||||
- FirebaseAnalytics/AdIdSupport (= 11.8.0)
|
||||
- FirebaseCore (~> 11.8.0)
|
||||
- FirebaseInstallations (~> 11.0)
|
||||
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
|
||||
- GoogleUtilities/MethodSwizzler (~> 8.0)
|
||||
- GoogleUtilities/Network (~> 8.0)
|
||||
- "GoogleUtilities/NSData+zlib (~> 8.0)"
|
||||
- nanopb (~> 3.30910.0)
|
||||
- FirebaseAnalytics/AdIdSupport (11.7.0):
|
||||
- FirebaseCore (~> 11.7.0)
|
||||
- FirebaseAnalytics/AdIdSupport (11.8.0):
|
||||
- FirebaseCore (~> 11.8.0)
|
||||
- FirebaseInstallations (~> 11.0)
|
||||
- GoogleAppMeasurement (= 11.7.0)
|
||||
- GoogleAppMeasurement (= 11.8.0)
|
||||
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
|
||||
- GoogleUtilities/MethodSwizzler (~> 8.0)
|
||||
- GoogleUtilities/Network (~> 8.0)
|
||||
- "GoogleUtilities/NSData+zlib (~> 8.0)"
|
||||
- nanopb (~> 3.30910.0)
|
||||
- FirebaseCore (11.7.0):
|
||||
- FirebaseCoreInternal (~> 11.7.0)
|
||||
- FirebaseCore (11.8.1):
|
||||
- FirebaseCoreInternal (~> 11.8.0)
|
||||
- GoogleUtilities/Environment (~> 8.0)
|
||||
- GoogleUtilities/Logger (~> 8.0)
|
||||
- FirebaseCoreInternal (11.7.0):
|
||||
- FirebaseCoreInternal (11.8.0):
|
||||
- "GoogleUtilities/NSData+zlib (~> 8.0)"
|
||||
- FirebaseInstallations (11.7.0):
|
||||
- FirebaseCore (~> 11.7.0)
|
||||
- FirebaseInstallations (11.8.0):
|
||||
- FirebaseCore (~> 11.8.0)
|
||||
- GoogleUtilities/Environment (~> 8.0)
|
||||
- GoogleUtilities/UserDefaults (~> 8.0)
|
||||
- PromisesObjC (~> 2.4)
|
||||
- FirebaseMessaging (11.7.0):
|
||||
- FirebaseCore (~> 11.7.0)
|
||||
- FirebaseMessaging (11.8.0):
|
||||
- FirebaseCore (~> 11.8.0)
|
||||
- FirebaseInstallations (~> 11.0)
|
||||
- GoogleDataTransport (~> 10.0)
|
||||
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
|
||||
@ -86,21 +86,21 @@ PODS:
|
||||
- gal (1.0.0):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- GoogleAppMeasurement (11.7.0):
|
||||
- GoogleAppMeasurement/AdIdSupport (= 11.7.0)
|
||||
- GoogleAppMeasurement (11.8.0):
|
||||
- GoogleAppMeasurement/AdIdSupport (= 11.8.0)
|
||||
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
|
||||
- GoogleUtilities/MethodSwizzler (~> 8.0)
|
||||
- GoogleUtilities/Network (~> 8.0)
|
||||
- "GoogleUtilities/NSData+zlib (~> 8.0)"
|
||||
- nanopb (~> 3.30910.0)
|
||||
- GoogleAppMeasurement/AdIdSupport (11.7.0):
|
||||
- GoogleAppMeasurement/WithoutAdIdSupport (= 11.7.0)
|
||||
- GoogleAppMeasurement/AdIdSupport (11.8.0):
|
||||
- GoogleAppMeasurement/WithoutAdIdSupport (= 11.8.0)
|
||||
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
|
||||
- GoogleUtilities/MethodSwizzler (~> 8.0)
|
||||
- GoogleUtilities/Network (~> 8.0)
|
||||
- "GoogleUtilities/NSData+zlib (~> 8.0)"
|
||||
- nanopb (~> 3.30910.0)
|
||||
- GoogleAppMeasurement/WithoutAdIdSupport (11.7.0):
|
||||
- GoogleAppMeasurement/WithoutAdIdSupport (11.8.0):
|
||||
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
|
||||
- GoogleUtilities/MethodSwizzler (~> 8.0)
|
||||
- GoogleUtilities/Network (~> 8.0)
|
||||
@ -177,6 +177,25 @@ PODS:
|
||||
- sqflite_darwin (0.0.4):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- sqlite3 (3.49.1):
|
||||
- sqlite3/common (= 3.49.1)
|
||||
- sqlite3/common (3.49.1)
|
||||
- sqlite3/dbstatvtab (3.49.1):
|
||||
- sqlite3/common
|
||||
- sqlite3/fts5 (3.49.1):
|
||||
- sqlite3/common
|
||||
- sqlite3/perf-threadsafe (3.49.1):
|
||||
- sqlite3/common
|
||||
- sqlite3/rtree (3.49.1):
|
||||
- sqlite3/common
|
||||
- sqlite3_flutter_libs (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- sqlite3 (~> 3.49.0)
|
||||
- sqlite3/dbstatvtab
|
||||
- sqlite3/fts5
|
||||
- sqlite3/perf-threadsafe
|
||||
- sqlite3/rtree
|
||||
- tray_manager (0.0.1):
|
||||
- FlutterMacOS
|
||||
- url_launcher_macos (0.0.1):
|
||||
@ -216,6 +235,7 @@ DEPENDENCIES:
|
||||
- share_plus (from `Flutter/ephemeral/.symlinks/plugins/share_plus/macos`)
|
||||
- shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||
- sqflite_darwin (from `Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin`)
|
||||
- sqlite3_flutter_libs (from `Flutter/ephemeral/.symlinks/plugins/sqlite3_flutter_libs/darwin`)
|
||||
- tray_manager (from `Flutter/ephemeral/.symlinks/plugins/tray_manager/macos`)
|
||||
- url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
|
||||
- video_compress (from `Flutter/ephemeral/.symlinks/plugins/video_compress/macos`)
|
||||
@ -237,6 +257,7 @@ SPEC REPOS:
|
||||
- OrderedSet
|
||||
- PromisesObjC
|
||||
- SAMKeychain
|
||||
- sqlite3
|
||||
- WebRTC-SDK
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
@ -296,6 +317,8 @@ EXTERNAL SOURCES:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin
|
||||
sqflite_darwin:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin
|
||||
sqlite3_flutter_libs:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/sqlite3_flutter_libs/darwin
|
||||
tray_manager:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/tray_manager/macos
|
||||
url_launcher_macos:
|
||||
@ -313,21 +336,21 @@ SPEC CHECKSUMS:
|
||||
file_picker: e716a70a9fe5fd9e09ebc922d7541464289443af
|
||||
file_saver: 44e6fbf666677faf097302460e214e977fdd977b
|
||||
file_selector_macos: cc3858c981fe6889f364731200d6232dac1d812d
|
||||
Firebase: a64bf6a8546e6eab54f1c715cd6151f39d2329f4
|
||||
firebase_analytics: 41d88c024a7756462a803e36236ba74f24cdc2c5
|
||||
firebase_core: 751d3d919b95d4ae46ab049d0d64d42d4eec086b
|
||||
firebase_messaging: cc174f19945e9541e140e3cb0118448e59b38c6c
|
||||
FirebaseAnalytics: bc9e565af9044ba8d6c6e4157e4edca8e5fdf7ec
|
||||
FirebaseCore: 3227e35f4197a924206fbcdc0349325baf4f5de4
|
||||
FirebaseCoreInternal: d6c17dafc8dc33614733a8b52df78fcb4394c881
|
||||
FirebaseInstallations: 9347e719c3d52d8d7b9074b2c32407dd027305e9
|
||||
FirebaseMessaging: 00ece041b71ddb52a2862ffdee73fb6e9824bd0c
|
||||
Firebase: d80354ed7f6df5f9aca55e9eb47cc4b634735eaf
|
||||
firebase_analytics: 1a71372a9735d7046d2c69db848a8d178f9fb587
|
||||
firebase_core: 68e1d27035b096239f147a041643e14e156f1481
|
||||
firebase_messaging: 89b5e0e28413dd878a58d2f286cdc03887b5d467
|
||||
FirebaseAnalytics: 4fd42def128146e24e480e89f310e3d8534ea42b
|
||||
FirebaseCore: 99fe0c4b44a39f37d99e6404e02009d2db5d718d
|
||||
FirebaseCoreInternal: df24ce5af28864660ecbd13596fc8dd3a8c34629
|
||||
FirebaseInstallations: 6c963bd2a86aca0481eef4f48f5a4df783ae5917
|
||||
FirebaseMessaging: 487b634ccdf6f7b7ff180fdcb2a9935490f764e8
|
||||
flutter_inappwebview_macos: bdf207b8f4ebd58e86ae06cd96b147de99a67c9b
|
||||
flutter_udid: 2e7b3da4b5fdfba86a396b97898f5fe8f4ec1a52
|
||||
flutter_webrtc: d55fd3f5c75b42940b6b4b2cf376a5797398d1f8
|
||||
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
|
||||
gal: 6a522c75909f1244732d4596d11d6a2f86ff37a5
|
||||
GoogleAppMeasurement: 0471a5b5bff51f3a91b1e76df22c952d04c63967
|
||||
GoogleAppMeasurement: fc0817122bd4d4189164f85374e06773b9561896
|
||||
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
|
||||
GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d
|
||||
HotKey: 400beb7caa29054ea8d864c96f5ba7e5b4852277
|
||||
@ -348,6 +371,8 @@ SPEC CHECKSUMS:
|
||||
share_plus: 1fa619de8392a4398bfaf176d441853922614e89
|
||||
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
|
||||
sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d
|
||||
sqlite3: fc1400008a9b3525f5914ed715a5d1af0b8f4983
|
||||
sqlite3_flutter_libs: 069c435986dd4b63461aecd68f4b30be4a9e9daa
|
||||
tray_manager: 9064e219c56d75c476e46b9a21182087930baf90
|
||||
url_launcher_macos: c82c93949963e55b228a30115bd219499a6fe404
|
||||
video_compress: c896234f100791b5fef7f049afa38f6d2ef7b42f
|
||||
|
@ -59,6 +59,7 @@
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
enableGPUValidationMode = "1"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
|
347
pubspec.lock
347
pubspec.lock
@ -5,31 +5,26 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: _fe_analyzer_shared
|
||||
sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab"
|
||||
sha256: dc27559385e905ad30838356c5f5d574014ba39872d732111cd07ac0beff4c57
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "76.0.0"
|
||||
version: "80.0.0"
|
||||
_flutterfire_internals:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: _flutterfire_internals
|
||||
sha256: e051259913915ea5bc8fe18664596bea08592fd123930605d562969cd7315fcd
|
||||
sha256: "401dd18096f5eaa140404ccbbbf346f83c850e6f27049698a7ee75a3488ddb32"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.51"
|
||||
_macros:
|
||||
dependency: transitive
|
||||
description: dart
|
||||
source: sdk
|
||||
version: "0.3.3"
|
||||
version: "1.3.52"
|
||||
analyzer:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: analyzer
|
||||
sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e"
|
||||
sha256: "192d1c5b944e7e53b24b5586db760db934b177d4147c42fbca8c8c5f1eb8d11e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.11.0"
|
||||
version: "7.3.0"
|
||||
animations:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -50,10 +45,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: archive
|
||||
sha256: "6199c74e3db4fbfbd04f66d739e72fe11c8a8957d5f219f1f4482dbde6420b5a"
|
||||
sha256: "528579c7e4579719f04b21eeeeddfd73a18b31dabc22766893b7d1be7f49b967"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.2"
|
||||
version: "4.0.3"
|
||||
args:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -66,10 +61,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: async
|
||||
sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
|
||||
sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.11.0"
|
||||
version: "2.12.0"
|
||||
bitsdojo_window:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -114,10 +109,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: boolean_selector
|
||||
sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66"
|
||||
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
version: "2.1.2"
|
||||
build:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -191,7 +186,7 @@ packages:
|
||||
source: hosted
|
||||
version: "3.4.1"
|
||||
cached_network_image_platform_interface:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: cached_network_image_platform_interface
|
||||
sha256: "35814b016e37fbdc91f7ae18c8caf49ba5c88501813f73ce8a07027a395e2829"
|
||||
@ -226,10 +221,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: characters
|
||||
sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
|
||||
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
version: "1.4.0"
|
||||
charcode:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: charcode
|
||||
sha256: fb0f1107cac15a5ea6ef0a6ef71a807b9e4267c713bb93e00e92d737cc8dbd8a
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
checked_yaml:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -250,10 +253,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: clock
|
||||
sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
|
||||
sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
version: "1.1.2"
|
||||
code_builder:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -266,10 +269,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: collection
|
||||
sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf
|
||||
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.19.0"
|
||||
version: "1.19.1"
|
||||
connectivity_plus:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -346,18 +349,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: dart_style
|
||||
sha256: "7306ab8a2359a48d22310ad823521d723acfed60ee1f7e37388e8986853b6820"
|
||||
sha256: "27eb0ae77836989a3bc541ce55595e8ceee0992807f14511552a898ddd0d88ac"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.8"
|
||||
version: "3.0.1"
|
||||
dart_webrtc:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: dart_webrtc
|
||||
sha256: "03df5b41b23bc185ebcf4b0ffc92d002e295bf56287fb5f9d2c321ddaf7760cc"
|
||||
sha256: b34e90bc82f33c1023cf98661369c37bccd648c8a4cf882a875d9f5d8bbef694
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.5.1"
|
||||
version: "1.5.2+hotfix.1"
|
||||
dbus:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -414,6 +417,30 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.2"
|
||||
drift:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: drift
|
||||
sha256: "97d5832657d49f26e7a8e07de397ddc63790b039372878d5117af816d0fdb5cb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.25.1"
|
||||
drift_dev:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: drift_dev
|
||||
sha256: f1db88482dbb016b9bbddddf746d5d0a6938b156ff20e07320052981f97388cc
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.25.2"
|
||||
drift_flutter:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: drift_flutter
|
||||
sha256: "0cadbf3b8733409a6cf61d18ba2e94e149df81df7de26f48ae0695b48fd71922"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.4"
|
||||
dropdown_button2:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -474,18 +501,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: fake_async
|
||||
sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
|
||||
sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.1"
|
||||
version: "1.3.2"
|
||||
ffi:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: ffi
|
||||
sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6"
|
||||
sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.3"
|
||||
version: "2.1.4"
|
||||
file:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -498,10 +525,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: file_picker
|
||||
sha256: ab13ae8ef5580a411c458d6207b6774a6c237d77ac37011b13994879f68a8810
|
||||
sha256: "6f6bfa8797f296965bdc3e1f702574ab49a540c19b9237b401e7c2b25dfe594c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.3.7"
|
||||
version: "9.0.0"
|
||||
file_saver:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -546,34 +573,34 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: firebase_analytics
|
||||
sha256: "47428047a0778f72af53a3c7cb5d556e1cb25e2327cc8aa40d544971dc6245b2"
|
||||
sha256: "6abce50b79729d8a13c3d4ae05ac612d5ef2f57394330bc5e581ca0e762325f4"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "11.4.2"
|
||||
version: "11.4.3"
|
||||
firebase_analytics_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: firebase_analytics_platform_interface
|
||||
sha256: "1076f4b041f76143e14878c70f0758f17fe5910c0cd992db9e93bd3c3584512b"
|
||||
sha256: cd9ae65870bf23ab7e63a04fe9c1b38522fd3556a8c32288afd3f5cb10d4b8f4
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.3.2"
|
||||
version: "4.3.3"
|
||||
firebase_analytics_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: firebase_analytics_web
|
||||
sha256: "8f6dd64ea6d28b7f5b9e739d183a9e1c7f17027794a3e9aba1879621d42426ef"
|
||||
sha256: "5654ed7e39d7a8099e60748924327159785512d78d913e965f9ca93c533af910"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.5.10+8"
|
||||
version: "0.5.10+9"
|
||||
firebase_core:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: firebase_core
|
||||
sha256: "93dc4dd12f9b02c5767f235307f609e61ed9211047132d07f9e02c668f0bfc33"
|
||||
sha256: "6a4ea0f1d533443c8afc3d809cd36a4e2b8f2e2e711f697974f55bb31d71d1b8"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.11.0"
|
||||
version: "3.12.0"
|
||||
firebase_core_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -586,34 +613,34 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: firebase_core_web
|
||||
sha256: "0e13c80f0de8acaa5d0519cbe23c8b4cc138a2d5d508b5755c861bdfc9762678"
|
||||
sha256: e47f5c2776de018fa19bc9f6f723df136bc75cdb164d64b65305babd715c8e41
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.20.0"
|
||||
version: "2.21.0"
|
||||
firebase_messaging:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: firebase_messaging
|
||||
sha256: "3dee3b0cbfe719e64773cb7d1cad57c58b2235a8c136f5715fe733a54058c783"
|
||||
sha256: "8755a083a20bac4485e8b46d223f6f2eab34e659a76a75f8cf3cded53bc98a15"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "15.2.2"
|
||||
version: "15.2.3"
|
||||
firebase_messaging_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: firebase_messaging_platform_interface
|
||||
sha256: e9ea726b9bb864fc6223bb66422bd9877b9973ae51967754a769b0d01e201c1e
|
||||
sha256: "8cc771079677460de53ad8fcca5bc3074d58c5fc4f9d89b19585e5bfd9c64292"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.6.2"
|
||||
version: "4.6.3"
|
||||
firebase_messaging_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: firebase_messaging_web
|
||||
sha256: "5f7b40e8bf861a37f8b8196e347d8a919750421a45f0b45d1bb74e98fa72726e"
|
||||
sha256: caa73059b0396c97f691683c4cfc3f897c8543801579b7dd4851c431d8e4e091
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.10.2"
|
||||
version: "3.10.3"
|
||||
fixnum:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -780,10 +807,10 @@ packages:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: flutter_native_splash
|
||||
sha256: "7062602e0dbd29141fb8eb19220b5871ca650be5197ab9c1f193a28b17537bc7"
|
||||
sha256: edb09c35ee9230c4b03f13dd45bb3a276d0801865f0a4650b7e2a3bba61a803a
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.4"
|
||||
version: "2.4.5"
|
||||
flutter_plugin_android_lifecycle:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -838,18 +865,18 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_webrtc
|
||||
sha256: "572df3de6c828e571db4b75b4a96a15c2f34fa3d420a84438f44a3158b22e81a"
|
||||
sha256: "6ea3a86d95b61cfe42d5715426d355b3cece6c88d0119de428d56f6c653811ce"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.12.9"
|
||||
version: "0.12.11"
|
||||
freezed:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: freezed
|
||||
sha256: "44c19278dd9d89292cf46e97dc0c1e52ce03275f40a97c5a348e802a924bf40e"
|
||||
sha256: "59a584c24b3acdc5250bb856d0d3e9c0b798ed14a4af1ddb7dc1c7b41df91c9c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.7"
|
||||
version: "2.5.8"
|
||||
freezed_annotation:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -914,30 +941,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.2"
|
||||
hive:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: hive
|
||||
sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.3"
|
||||
hive_flutter:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: hive_flutter
|
||||
sha256: dca1da446b1d808a51689fb5d0c6c9510c0a2ba01e22805d492c73b68e33eecc
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
hive_generator:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: hive_generator
|
||||
sha256: "06cb8f58ace74de61f63500564931f9505368f45f98958bd7a6c35ba24159db4"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
home_widget:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -1030,10 +1033,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image
|
||||
sha256: "8346ad4b5173924b5ddddab782fc7d8a6300178c8b1dc427775405a01701c4a6"
|
||||
sha256: "13d3349ace88f12f4a0d175eb5c12dcdd39d35c4c109a8a13dfeb6d0bd9e31c3"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.5.2"
|
||||
version: "4.5.3"
|
||||
image_picker:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -1046,10 +1049,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: image_picker_android
|
||||
sha256: b62d34a506e12bb965e824b6db4fbf709ee4589cf5d3e99b45ab2287b008ee0c
|
||||
sha256: "82652a75e3dd667a91187769a6a2cc81bd8c111bbead698d8e938d2b63e5e89a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.8.12+20"
|
||||
version: "0.8.12+21"
|
||||
image_picker_for_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1083,7 +1086,7 @@ packages:
|
||||
source: hosted
|
||||
version: "0.2.1+2"
|
||||
image_picker_platform_interface:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: image_picker_platform_interface
|
||||
sha256: "886d57f0be73c4b140004e78b9f28a8914a09e50c2d816bdd0520051a71236a0"
|
||||
@ -1150,26 +1153,26 @@ packages:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: json_serializable
|
||||
sha256: c2fcb3920cf2b6ae6845954186420fca40bc0a8abcc84903b7801f17d7050d7c
|
||||
sha256: "81f04dee10969f89f604e1249382d46b97a1ccad53872875369622b5bfc9e58a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.9.0"
|
||||
version: "6.9.4"
|
||||
leak_tracker:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker
|
||||
sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06"
|
||||
sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.0.7"
|
||||
version: "10.0.8"
|
||||
leak_tracker_flutter_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_flutter_testing
|
||||
sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379"
|
||||
sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.8"
|
||||
version: "3.0.9"
|
||||
leak_tracker_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1198,10 +1201,18 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: livekit_client
|
||||
sha256: "0cfb2f48eff7a93ea8927696dc6f218aebd2fcd1fcc1b1a7b2f53ff3597fdb52"
|
||||
sha256: "753bbf484c6b70f10f3dc1dc808dfe3755f472d80eb9682323cff07ad8e2609d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.6"
|
||||
version: "2.4.0"
|
||||
local_notifier:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: local_notifier
|
||||
sha256: f6cfc933c6fbc961f4e52b5c880f68e41b2d3cd29aad557cc654fd211093a025
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.6"
|
||||
logging:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1210,14 +1221,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
macros:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: macros
|
||||
sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.3-main.0"
|
||||
markdown:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -1238,10 +1241,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: matcher
|
||||
sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
|
||||
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.12.16+1"
|
||||
version: "0.12.17"
|
||||
material_color_utilities:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1342,10 +1345,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
|
||||
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.15.0"
|
||||
version: "1.16.0"
|
||||
mime:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -1414,10 +1417,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: path
|
||||
sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
|
||||
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.9.0"
|
||||
version: "1.9.1"
|
||||
path_parsing:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1486,26 +1489,26 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: permission_handler
|
||||
sha256: "18bf33f7fefbd812f37e72091a15575e72d5318854877e0e4035a24ac1113ecb"
|
||||
sha256: "59adad729136f01ea9e35a48f5d1395e25cba6cea552249ddbe9cf950f5d7849"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "11.3.1"
|
||||
version: "11.4.0"
|
||||
permission_handler_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_android
|
||||
sha256: "71bbecfee799e65aff7c744761a57e817e73b738fedf62ab7afd5593da21f9f1"
|
||||
sha256: d3971dcdd76182a0c198c096b5db2f0884b0d4196723d21a866fc4cdea057ebc
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "12.0.13"
|
||||
version: "12.1.0"
|
||||
permission_handler_apple:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_apple
|
||||
sha256: e6f6d73b12438ef13e648c4ae56bd106ec60d17e90a59c4545db6781229082a0
|
||||
sha256: f84a188e79a35c687c132a0a0556c254747a08561e99ab933f12f6ca71ef3c98
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "9.4.5"
|
||||
version: "9.4.6"
|
||||
permission_handler_html:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1518,10 +1521,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_platform_interface
|
||||
sha256: e9c8eadee926c4532d0305dff94b85bf961f16759c3af791486613152af4b4f9
|
||||
sha256: eb99b295153abce5d683cac8c02e22faab63e50679b937fa1bf67d58bb282878
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.2.3"
|
||||
version: "4.3.0"
|
||||
permission_handler_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1534,10 +1537,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: petitparser
|
||||
sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27
|
||||
sha256: "07c8f0b1913bcde1ff0d26e57ace2f3012ccbf2b204e070290dad3bb22797646"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.0.2"
|
||||
version: "6.1.0"
|
||||
photo_view:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -1554,14 +1557,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.6"
|
||||
platform_detect:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: platform_detect
|
||||
sha256: a62f99417fc4fa2d099ce0ccdbb1bd3977920f2a64292c326271f049d4bc3a4f
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
plugin_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1642,6 +1637,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.1.0"
|
||||
recase:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: recase
|
||||
sha256: e4eb4ec2dcdee52dcf99cb4ceabaffc631d7424ee55e56f280bc039737f89213
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.1.0"
|
||||
receive_sharing_intent:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -1774,10 +1777,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_android
|
||||
sha256: ea86be7b7114f9e94fddfbb52649e59a03d6627ccd2387ebddcd6624719e9f16
|
||||
sha256: a768fc8ede5f0c8e6150476e14f38e2417c0864ca36bb4582be8e21925a03c22
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.5"
|
||||
version: "2.4.6"
|
||||
shared_preferences_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1806,10 +1809,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_web
|
||||
sha256: d2ca4132d3946fec2184261726b355836a82c33d7d5b67af32692aff18a4684e
|
||||
sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.2"
|
||||
version: "2.4.3"
|
||||
shared_preferences_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1867,10 +1870,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_gen
|
||||
sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832"
|
||||
sha256: "35c8150ece9e8c8d263337a265153c3329667640850b9304861faea59fc98f6b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.5.0"
|
||||
version: "2.0.0"
|
||||
source_helper:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1883,10 +1886,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_span
|
||||
sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
|
||||
sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.10.0"
|
||||
version: "1.10.1"
|
||||
sprintf:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1899,34 +1902,34 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sqflite
|
||||
sha256: "2d7299468485dca85efeeadf5d38986909c5eb0cd71fd3db2c2f000e6c9454bb"
|
||||
sha256: e2297b1da52f127bc7a3da11439985d9b536f75070f3325e62ada69a5c585d03
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
version: "2.4.2"
|
||||
sqflite_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sqflite_android
|
||||
sha256: "78f489aab276260cdd26676d2169446c7ecd3484bbd5fead4ca14f3ed4dd9ee3"
|
||||
sha256: "2b3070c5fa881839f8b402ee4a39c1b4d561704d4ebbbcfb808a119bc2a1701b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.0"
|
||||
version: "2.4.1"
|
||||
sqflite_common:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sqflite_common
|
||||
sha256: "761b9740ecbd4d3e66b8916d784e581861fd3c3553eda85e167bc49fdb68f709"
|
||||
sha256: "84731e8bfd8303a3389903e01fb2141b6e59b5973cacbb0929021df08dddbe8b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.4+6"
|
||||
version: "2.5.5"
|
||||
sqflite_darwin:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sqflite_darwin
|
||||
sha256: "22adfd9a2c7d634041e96d6241e6e1c8138ca6817018afc5d443fef91dcefa9c"
|
||||
sha256: "279832e5cde3fe99e8571879498c9211f3ca6391b0d818df4e17d9fff5c6ccb3"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.1+1"
|
||||
version: "2.4.2"
|
||||
sqflite_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1935,22 +1938,46 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.0"
|
||||
sqlite3:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sqlite3
|
||||
sha256: "32b632dda27d664f85520093ed6f735ae5c49b5b75345afb8b19411bc59bb53d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.7.4"
|
||||
sqlite3_flutter_libs:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sqlite3_flutter_libs
|
||||
sha256: "57fafacd815c981735406215966ff7caaa8eab984b094f52e692accefcbd9233"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.5.30"
|
||||
sqlparser:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sqlparser
|
||||
sha256: "27dd0a9f0c02e22ac0eb42a23df9ea079ce69b52bb4a3b478d64e0ef34a263ee"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.41.0"
|
||||
stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stack_trace
|
||||
sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377"
|
||||
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.12.0"
|
||||
version: "1.12.1"
|
||||
stream_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stream_channel
|
||||
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
|
||||
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
version: "2.1.4"
|
||||
stream_transform:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1963,10 +1990,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: string_scanner
|
||||
sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3"
|
||||
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
version: "1.4.1"
|
||||
styled_widget:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -1987,26 +2014,26 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: synchronized
|
||||
sha256: "69fe30f3a8b04a0be0c15ae6490fc859a78ef4c43ae2dd5e8a623d45bfcf9225"
|
||||
sha256: "0669c70faae6270521ee4f05bffd2919892d42d1276e6c495be80174b6bc0ef6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.3.0+3"
|
||||
version: "3.3.1"
|
||||
term_glyph:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: term_glyph
|
||||
sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
|
||||
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.1"
|
||||
version: "1.2.2"
|
||||
test_api:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c"
|
||||
sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.3"
|
||||
version: "0.7.4"
|
||||
timing:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -2195,10 +2222,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vm_service
|
||||
sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b
|
||||
sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "14.3.0"
|
||||
version: "14.3.1"
|
||||
volume_controller:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -2259,18 +2286,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: webrtc_interface
|
||||
sha256: "10fc6dc0ac16f909f5e434c18902415211d759313c87261f1e4ec5b4f6a04c26"
|
||||
sha256: e05f00091c9c70a15bab4ccb1b6c46d9a16a6075002f02cfac3641eccb05e25d
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.1"
|
||||
version: "1.2.1+hotfix.1"
|
||||
win32:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: win32
|
||||
sha256: daf97c9d80197ed7b619040e86c8ab9a9dad285e7671ee7390f9180cc828a51e
|
||||
sha256: b89e6e24d1454e149ab20fbb225af58660f0c0bf4475544650700d8e2da54aef
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.10.1"
|
||||
version: "5.11.0"
|
||||
win32_registry:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -2312,5 +2339,5 @@ packages:
|
||||
source: hosted
|
||||
version: "3.1.3"
|
||||
sdks:
|
||||
dart: ">=3.6.0 <4.0.0"
|
||||
dart: ">=3.7.0 <4.0.0"
|
||||
flutter: ">=3.27.0"
|
||||
|
15
pubspec.yaml
15
pubspec.yaml
@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
# In Windows, build-name is used as the major, minor, and patch parts
|
||||
# of the product and file versions while build-number is used as the build suffix.
|
||||
version: 2.3.2+70
|
||||
version: 2.3.2+71
|
||||
|
||||
environment:
|
||||
sdk: ^3.5.4
|
||||
@ -59,7 +59,7 @@ dependencies:
|
||||
relative_time: ^5.0.0
|
||||
image_picker: ^1.1.2
|
||||
cross_file: ^0.3.4+2
|
||||
file_picker: ^8.1.6 # pinned due to compile failed on android, https://github.com/miguelpruivo/flutter_file_picker/issues/1643
|
||||
file_picker: ^9.0.0 # pinned due to compile failed on android, https://github.com/miguelpruivo/flutter_file_picker/issues/1643
|
||||
croppy: ^1.3.1
|
||||
flutter_expandable_fab: ^2.3.0
|
||||
dropdown_button2: ^2.3.9
|
||||
@ -72,8 +72,6 @@ dependencies:
|
||||
collection: ^1.18.0
|
||||
mime: ^2.0.0
|
||||
web_socket_channel: ^3.0.1
|
||||
hive: ^2.2.3
|
||||
hive_flutter: ^1.1.0
|
||||
swipe_to: ^1.0.6
|
||||
firebase_core: ^3.8.0
|
||||
firebase_messaging: ^15.1.5
|
||||
@ -121,6 +119,11 @@ dependencies:
|
||||
tray_manager: ^0.3.2
|
||||
hotkey_manager: ^0.2.3
|
||||
image_picker_android: ^0.8.12+20
|
||||
cached_network_image_platform_interface: ^4.1.1
|
||||
image_picker_platform_interface: ^2.10.1
|
||||
drift: ^2.25.1
|
||||
drift_flutter: ^0.2.4
|
||||
local_notifier: ^0.1.6
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
@ -132,13 +135,13 @@ dev_dependencies:
|
||||
# package. See that file for information about deactivating specific lint
|
||||
# rules and activating additional ones.
|
||||
flutter_lints: ^5.0.0
|
||||
build_runner: ^2.4.13
|
||||
build_runner: ^2.4.15
|
||||
freezed: ^2.5.7
|
||||
json_serializable: ^6.8.0
|
||||
icons_launcher: ^3.0.0
|
||||
flutter_native_splash: ^2.4.2
|
||||
hive_generator: ^2.0.1
|
||||
flutter_launcher_icons: ^0.14.1
|
||||
drift_dev: ^2.25.2
|
||||
|
||||
# For information on the generic Dart part of this file, see the
|
||||
# following page: https://dart.dev/tools/pub/pubspec
|
||||
|
13631
web/drift_worker.dart.js
Normal file
13631
web/drift_worker.dart.js
Normal file
File diff suppressed because one or more lines are too long
@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "surface",
|
||||
"short_name": "surface",
|
||||
"name": "Solar Network",
|
||||
"short_name": "Solian",
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"background_color": "#ffffff",
|
||||
"theme_color": "#ffffff",
|
||||
"description": "A new Flutter project.",
|
||||
"description": "The Solar Network is a social network app.",
|
||||
"orientation": "portrait-primary",
|
||||
"prefer_related_applications": false,
|
||||
"icons": [
|
||||
|
BIN
web/sqlite3.wasm
Normal file
BIN
web/sqlite3.wasm
Normal file
Binary file not shown.
@ -17,12 +17,14 @@
|
||||
#include <gal/gal_plugin_c_api.h>
|
||||
#include <hotkey_manager_windows/hotkey_manager_windows_plugin_c_api.h>
|
||||
#include <livekit_client/live_kit_plugin.h>
|
||||
#include <local_notifier/local_notifier_plugin.h>
|
||||
#include <media_kit_libs_windows_video/media_kit_libs_windows_video_plugin_c_api.h>
|
||||
#include <media_kit_video/media_kit_video_plugin_c_api.h>
|
||||
#include <pasteboard/pasteboard_plugin.h>
|
||||
#include <permission_handler_windows/permission_handler_windows_plugin.h>
|
||||
#include <screen_brightness_windows/screen_brightness_windows_plugin.h>
|
||||
#include <share_plus/share_plus_windows_plugin_c_api.h>
|
||||
#include <sqlite3_flutter_libs/sqlite3_flutter_libs_plugin.h>
|
||||
#include <tray_manager/tray_manager_plugin.h>
|
||||
#include <url_launcher_windows/url_launcher_windows.h>
|
||||
|
||||
@ -49,6 +51,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||
registry->GetRegistrarForPlugin("HotkeyManagerWindowsPluginCApi"));
|
||||
LiveKitPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("LiveKitPlugin"));
|
||||
LocalNotifierPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("LocalNotifierPlugin"));
|
||||
MediaKitLibsWindowsVideoPluginCApiRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("MediaKitLibsWindowsVideoPluginCApi"));
|
||||
MediaKitVideoPluginCApiRegisterWithRegistrar(
|
||||
@ -61,6 +65,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||
registry->GetRegistrarForPlugin("ScreenBrightnessWindowsPlugin"));
|
||||
SharePlusWindowsPluginCApiRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi"));
|
||||
Sqlite3FlutterLibsPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("Sqlite3FlutterLibsPlugin"));
|
||||
TrayManagerPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("TrayManagerPlugin"));
|
||||
UrlLauncherWindowsRegisterWithRegistrar(
|
||||
|
@ -14,12 +14,14 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
||||
gal
|
||||
hotkey_manager_windows
|
||||
livekit_client
|
||||
local_notifier
|
||||
media_kit_libs_windows_video
|
||||
media_kit_video
|
||||
pasteboard
|
||||
permission_handler_windows
|
||||
screen_brightness_windows
|
||||
share_plus
|
||||
sqlite3_flutter_libs
|
||||
tray_manager
|
||||
url_launcher_windows
|
||||
)
|
||||
|
Reference in New Issue
Block a user