Compare commits
17 Commits
2.3.2+70
...
3c0e4046a4
| Author | SHA1 | Date | |
|---|---|---|---|
| 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
|
||||||
@@ -12,9 +12,9 @@ post {
|
|||||||
|
|
||||||
body:json {
|
body:json {
|
||||||
{
|
{
|
||||||
"alias": "BaLoading",
|
"alias": "Deadge",
|
||||||
"name": "BaLoading",
|
"name": "Dead",
|
||||||
"attachment_id": "2JCI2uh21mKkfk9P",
|
"attachment_id": "pcbFd0u4zgdM39HM",
|
||||||
"pack_id": 3
|
"pack_id": 4
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ meta {
|
|||||||
}
|
}
|
||||||
|
|
||||||
post {
|
post {
|
||||||
url: {{endpoint}}/cgi/id/dev/notify/122
|
url: {{endpoint}}/cgi/id/dev/notify/328
|
||||||
body: json
|
body: json
|
||||||
auth: inherit
|
auth: inherit
|
||||||
}
|
}
|
||||||
@@ -15,9 +15,9 @@ body:json {
|
|||||||
"client_id": "{{third_client_id}}",
|
"client_id": "{{third_client_id}}",
|
||||||
"client_secret":"{{third_client_tk}}",
|
"client_secret":"{{third_client_tk}}",
|
||||||
"type": "general",
|
"type": "general",
|
||||||
"subject": "处理该帐号 @solian 的决定",
|
"subject": "处理该发布者 @vedal987 的决定",
|
||||||
"subtitle": "违反用户协议",
|
"subtitle": "一条来自 Solar Network 客户支持的信息",
|
||||||
"content": "您的帐号违反了我们用户协议中关于冒充我们官方的行为,至此做出停权的决定。还请见谅。该决定是最终决定,不接受上诉。",
|
"content": "您的发布者违反了我们用户协议中的「禁止冒充他人」的相关条例,经管理决定,将相关内容隐藏。冒充他人的判定无论作者是否有主观意志,只要造成了误解我们就有责任处理。希望您能理解,本次决定未作出任何帐号相关的连带处罚。",
|
||||||
"priority": 10
|
"priority": 10
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -548,6 +548,7 @@
|
|||||||
"termAcceptNextWithAgree": "By clicking the \"Next\", it means you agree to our terms and its updates.",
|
"termAcceptNextWithAgree": "By clicking the \"Next\", it means you agree to our terms and its updates.",
|
||||||
"unauthorized": "Unauthorized",
|
"unauthorized": "Unauthorized",
|
||||||
"unauthorizedDescription": "Login to explore the entire Solar Network.",
|
"unauthorizedDescription": "Login to explore the entire Solar Network.",
|
||||||
|
"projectDetail": "Project Details",
|
||||||
"serviceStatus": "Service Status",
|
"serviceStatus": "Service Status",
|
||||||
"termRelated": "Related Terms",
|
"termRelated": "Related Terms",
|
||||||
"appDetails": "App Details",
|
"appDetails": "App Details",
|
||||||
@@ -583,6 +584,7 @@
|
|||||||
"colorSchemeBlack": "Black",
|
"colorSchemeBlack": "Black",
|
||||||
"colorSchemeApplied": "Color scheme has been applied, may need restart the app to take effect.",
|
"colorSchemeApplied": "Color scheme has been applied, may need restart the app to take effect.",
|
||||||
"postFeaturedComment": "Featured Comment",
|
"postFeaturedComment": "Featured Comment",
|
||||||
|
"postCategory": "Category",
|
||||||
"postCategoryTechnology": "Technology",
|
"postCategoryTechnology": "Technology",
|
||||||
"postCategoryGaming": "Gaming",
|
"postCategoryGaming": "Gaming",
|
||||||
"postCategoryLife": "Life",
|
"postCategoryLife": "Life",
|
||||||
@@ -625,6 +627,7 @@
|
|||||||
"realmJoin": "Join Realm",
|
"realmJoin": "Join Realm",
|
||||||
"realmCommunityHint": "This realm is a community realm, you can freely join.",
|
"realmCommunityHint": "This realm is a community realm, you can freely join.",
|
||||||
"realmCommunityPublicChannelsHint": "The public channels in this realm",
|
"realmCommunityPublicChannelsHint": "The public channels in this realm",
|
||||||
|
"realmCommunityPublishersHint": "The publishers in this realm",
|
||||||
"realmJoined": "Joined realm {}.",
|
"realmJoined": "Joined realm {}.",
|
||||||
"join": "Join",
|
"join": "Join",
|
||||||
"pollEditorNew": "New Poll",
|
"pollEditorNew": "New Poll",
|
||||||
@@ -665,5 +668,24 @@
|
|||||||
"zero": "No views",
|
"zero": "No views",
|
||||||
"one": "{} view",
|
"one": "{} view",
|
||||||
"other": "{} views"
|
"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."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -546,6 +546,7 @@
|
|||||||
"termAcceptNextWithAgree": "点击 “下一步”,即表示你同意我们的各项条款,包括其之后的更新。",
|
"termAcceptNextWithAgree": "点击 “下一步”,即表示你同意我们的各项条款,包括其之后的更新。",
|
||||||
"unauthorized": "未登陆",
|
"unauthorized": "未登陆",
|
||||||
"unauthorizedDescription": "登陆以探索整个 Solar Network。",
|
"unauthorizedDescription": "登陆以探索整个 Solar Network。",
|
||||||
|
"projectDetail": "项目详情",
|
||||||
"serviceStatus": "服务状态",
|
"serviceStatus": "服务状态",
|
||||||
"termRelated": "相关条款",
|
"termRelated": "相关条款",
|
||||||
"appDetails": "应用程序详情",
|
"appDetails": "应用程序详情",
|
||||||
@@ -581,6 +582,7 @@
|
|||||||
"colorSchemeBlack": "黑色",
|
"colorSchemeBlack": "黑色",
|
||||||
"colorSchemeApplied": "主题色已应用,可能需要重启来生效。",
|
"colorSchemeApplied": "主题色已应用,可能需要重启来生效。",
|
||||||
"postFeaturedComment": "精选评论",
|
"postFeaturedComment": "精选评论",
|
||||||
|
"postCategory": "分类",
|
||||||
"postCategoryTechnology": "技术",
|
"postCategoryTechnology": "技术",
|
||||||
"postCategoryGaming": "游戏",
|
"postCategoryGaming": "游戏",
|
||||||
"postCategoryLife": "生活",
|
"postCategoryLife": "生活",
|
||||||
@@ -624,6 +626,7 @@
|
|||||||
"realmJoin": "加入领域",
|
"realmJoin": "加入领域",
|
||||||
"realmCommunityHint": "该领域是一个社区领域,你可以自由加入。",
|
"realmCommunityHint": "该领域是一个社区领域,你可以自由加入。",
|
||||||
"realmCommunityPublicChannelsHint": "该领域包含的公共频道",
|
"realmCommunityPublicChannelsHint": "该领域包含的公共频道",
|
||||||
|
"realmCommunityPublishersHint": "该领域的发布者",
|
||||||
"realmJoined": "已加入领域 {}。",
|
"realmJoined": "已加入领域 {}。",
|
||||||
"join": "加入",
|
"join": "加入",
|
||||||
"pollEditorNew": "新投票",
|
"pollEditorNew": "新投票",
|
||||||
@@ -664,5 +667,23 @@
|
|||||||
"zero": "{} 次浏览",
|
"zero": "{} 次浏览",
|
||||||
"one": "{} 次浏览",
|
"one": "{} 次浏览",
|
||||||
"other": "{} 次浏览"
|
"other": "{} 次浏览"
|
||||||
}
|
},
|
||||||
|
"attachmentBillingUploaded": "已占用的字节数",
|
||||||
|
"attachmentBillingDiscount": "免费的字节数",
|
||||||
|
"attachmentBillingHint": "滑动窗口计价®\n在24小时内上传的文件大小超出免费空间才会适用扣费。",
|
||||||
|
"postThumbnail": "帖子缩略图",
|
||||||
|
"accountRealms": "领域",
|
||||||
|
"postInGlobal": "全站",
|
||||||
|
"postInGlobalDescription": "不关联此帖子与任何领域。",
|
||||||
|
"postChannelGlobal": "全站",
|
||||||
|
"postChannelFriends": "好友",
|
||||||
|
"postChannelFollowing": "关注",
|
||||||
|
"postChannelRealm": "领域",
|
||||||
|
"postFilterReset": "重置过滤器",
|
||||||
|
"postFilterResetDescription": "清除过滤器并显示所有帖子。",
|
||||||
|
"postFilterWithCategory": "查看{}区中的帖子",
|
||||||
|
"databaseSize": "数据库大小",
|
||||||
|
"databaseDelete": "删除数据库",
|
||||||
|
"databaseDeleteDescription": "删除本地数据库,内容将从服务器重新获取。",
|
||||||
|
"databaseDeleted": "本地数据库已被删除。"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -546,6 +546,7 @@
|
|||||||
"termAcceptNextWithAgree": "點擊 “下一步”,即表示你同意我們的各項條款,包括其之後的更新。",
|
"termAcceptNextWithAgree": "點擊 “下一步”,即表示你同意我們的各項條款,包括其之後的更新。",
|
||||||
"unauthorized": "未登陸",
|
"unauthorized": "未登陸",
|
||||||
"unauthorizedDescription": "登陸以探索整個 Solar Network。",
|
"unauthorizedDescription": "登陸以探索整個 Solar Network。",
|
||||||
|
"projectDetail": "項目詳情",
|
||||||
"serviceStatus": "服務狀態",
|
"serviceStatus": "服務狀態",
|
||||||
"termRelated": "相關條款",
|
"termRelated": "相關條款",
|
||||||
"appDetails": "應用程序詳情",
|
"appDetails": "應用程序詳情",
|
||||||
@@ -581,6 +582,7 @@
|
|||||||
"colorSchemeBlack": "黑色",
|
"colorSchemeBlack": "黑色",
|
||||||
"colorSchemeApplied": "主題色已應用,可能需要重啓來生效。",
|
"colorSchemeApplied": "主題色已應用,可能需要重啓來生效。",
|
||||||
"postFeaturedComment": "精選評論",
|
"postFeaturedComment": "精選評論",
|
||||||
|
"postCategory": "分類",
|
||||||
"postCategoryTechnology": "技術",
|
"postCategoryTechnology": "技術",
|
||||||
"postCategoryGaming": "遊戲",
|
"postCategoryGaming": "遊戲",
|
||||||
"postCategoryLife": "生活",
|
"postCategoryLife": "生活",
|
||||||
@@ -624,6 +626,7 @@
|
|||||||
"realmJoin": "加入領域",
|
"realmJoin": "加入領域",
|
||||||
"realmCommunityHint": "該領域是一個社區領域,你可以自由加入。",
|
"realmCommunityHint": "該領域是一個社區領域,你可以自由加入。",
|
||||||
"realmCommunityPublicChannelsHint": "該領域包含的公共頻道",
|
"realmCommunityPublicChannelsHint": "該領域包含的公共頻道",
|
||||||
|
"realmCommunityPublishersHint": "該領域的發佈者",
|
||||||
"realmJoined": "已加入領域 {}。",
|
"realmJoined": "已加入領域 {}。",
|
||||||
"join": "加入",
|
"join": "加入",
|
||||||
"pollEditorNew": "新投票",
|
"pollEditorNew": "新投票",
|
||||||
@@ -664,5 +667,23 @@
|
|||||||
"zero": "{} 次瀏覽",
|
"zero": "{} 次瀏覽",
|
||||||
"one": "{} 次瀏覽",
|
"one": "{} 次瀏覽",
|
||||||
"other": "{} 次瀏覽"
|
"other": "{} 次瀏覽"
|
||||||
}
|
},
|
||||||
|
"attachmentBillingUploaded": "已佔用的字節數",
|
||||||
|
"attachmentBillingDiscount": "免費的字節數",
|
||||||
|
"attachmentBillingHint": "滑動窗口計價®\n在24小時內上傳的文件大小超出免費空間才會適用扣費。",
|
||||||
|
"postThumbnail": "帖子縮略圖",
|
||||||
|
"accountRealms": "領域",
|
||||||
|
"postInGlobal": "全站",
|
||||||
|
"postInGlobalDescription": "不關聯此帖子與任何領域。",
|
||||||
|
"postChannelGlobal": "全站",
|
||||||
|
"postChannelFriends": "好友",
|
||||||
|
"postChannelFollowing": "關注",
|
||||||
|
"postChannelRealm": "領域",
|
||||||
|
"postFilterReset": "重置過濾器",
|
||||||
|
"postFilterResetDescription": "清除過濾器並顯示所有帖子。",
|
||||||
|
"postFilterWithCategory": "查看{}區中的帖子",
|
||||||
|
"databaseSize": "數據庫大小",
|
||||||
|
"databaseDelete": "刪除數據庫",
|
||||||
|
"databaseDeleteDescription": "刪除本地數據庫,內容將從服務器重新獲取。",
|
||||||
|
"databaseDeleted": "本地數據庫已被刪除。"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -546,6 +546,7 @@
|
|||||||
"termAcceptNextWithAgree": "點擊 “下一步”,即表示你同意我們的各項條款,包括其之後的更新。",
|
"termAcceptNextWithAgree": "點擊 “下一步”,即表示你同意我們的各項條款,包括其之後的更新。",
|
||||||
"unauthorized": "未登陸",
|
"unauthorized": "未登陸",
|
||||||
"unauthorizedDescription": "登陸以探索整個 Solar Network。",
|
"unauthorizedDescription": "登陸以探索整個 Solar Network。",
|
||||||
|
"projectDetail": "項目詳情",
|
||||||
"serviceStatus": "服務狀態",
|
"serviceStatus": "服務狀態",
|
||||||
"termRelated": "相關條款",
|
"termRelated": "相關條款",
|
||||||
"appDetails": "應用程序詳情",
|
"appDetails": "應用程序詳情",
|
||||||
@@ -581,6 +582,7 @@
|
|||||||
"colorSchemeBlack": "黑色",
|
"colorSchemeBlack": "黑色",
|
||||||
"colorSchemeApplied": "主題色已應用,可能需要重啟來生效。",
|
"colorSchemeApplied": "主題色已應用,可能需要重啟來生效。",
|
||||||
"postFeaturedComment": "精選評論",
|
"postFeaturedComment": "精選評論",
|
||||||
|
"postCategory": "分類",
|
||||||
"postCategoryTechnology": "技術",
|
"postCategoryTechnology": "技術",
|
||||||
"postCategoryGaming": "遊戲",
|
"postCategoryGaming": "遊戲",
|
||||||
"postCategoryLife": "生活",
|
"postCategoryLife": "生活",
|
||||||
@@ -624,6 +626,7 @@
|
|||||||
"realmJoin": "加入領域",
|
"realmJoin": "加入領域",
|
||||||
"realmCommunityHint": "該領域是一個社區領域,你可以自由加入。",
|
"realmCommunityHint": "該領域是一個社區領域,你可以自由加入。",
|
||||||
"realmCommunityPublicChannelsHint": "該領域包含的公共頻道",
|
"realmCommunityPublicChannelsHint": "該領域包含的公共頻道",
|
||||||
|
"realmCommunityPublishersHint": "該領域的發佈者",
|
||||||
"realmJoined": "已加入領域 {}。",
|
"realmJoined": "已加入領域 {}。",
|
||||||
"join": "加入",
|
"join": "加入",
|
||||||
"pollEditorNew": "新投票",
|
"pollEditorNew": "新投票",
|
||||||
@@ -664,5 +667,23 @@
|
|||||||
"zero": "{} 次瀏覽",
|
"zero": "{} 次瀏覽",
|
||||||
"one": "{} 次瀏覽",
|
"one": "{} 次瀏覽",
|
||||||
"other": "{} 次瀏覽"
|
"other": "{} 次瀏覽"
|
||||||
}
|
},
|
||||||
|
"attachmentBillingUploaded": "已佔用的字節數",
|
||||||
|
"attachmentBillingDiscount": "免費的字節數",
|
||||||
|
"attachmentBillingHint": "滑動窗口計價®\n在24小時內上傳的文件大小超出免費空間才會適用扣費。",
|
||||||
|
"postThumbnail": "帖子縮略圖",
|
||||||
|
"accountRealms": "領域",
|
||||||
|
"postInGlobal": "全站",
|
||||||
|
"postInGlobalDescription": "不關聯此帖子與任何領域。",
|
||||||
|
"postChannelGlobal": "全站",
|
||||||
|
"postChannelFriends": "好友",
|
||||||
|
"postChannelFollowing": "關注",
|
||||||
|
"postChannelRealm": "領域",
|
||||||
|
"postFilterReset": "重置過濾器",
|
||||||
|
"postFilterResetDescription": "清除過濾器並顯示所有帖子。",
|
||||||
|
"postFilterWithCategory": "查看{}區中的帖子",
|
||||||
|
"databaseSize": "數據庫大小",
|
||||||
|
"databaseDelete": "刪除數據庫",
|
||||||
|
"databaseDeleteDescription": "刪除本地數據庫,內容將從服務器重新獲取。",
|
||||||
|
"databaseDeleted": "本地數據庫已被刪除。"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -221,6 +221,25 @@ PODS:
|
|||||||
- sqflite_darwin (0.0.4):
|
- sqflite_darwin (0.0.4):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
|
- sqlite3 (3.49.0):
|
||||||
|
- sqlite3/common (= 3.49.0)
|
||||||
|
- sqlite3/common (3.49.0)
|
||||||
|
- sqlite3/dbstatvtab (3.49.0):
|
||||||
|
- sqlite3/common
|
||||||
|
- sqlite3/fts5 (3.49.0):
|
||||||
|
- sqlite3/common
|
||||||
|
- sqlite3/perf-threadsafe (3.49.0):
|
||||||
|
- sqlite3/common
|
||||||
|
- sqlite3/rtree (3.49.0):
|
||||||
|
- sqlite3/common
|
||||||
|
- sqlite3_flutter_libs (0.0.1):
|
||||||
|
- Flutter
|
||||||
|
- FlutterMacOS
|
||||||
|
- sqlite3 (~> 3.49.0)
|
||||||
|
- sqlite3/dbstatvtab
|
||||||
|
- sqlite3/fts5
|
||||||
|
- sqlite3/perf-threadsafe
|
||||||
|
- sqlite3/rtree
|
||||||
- SwiftyGif (5.4.5)
|
- SwiftyGif (5.4.5)
|
||||||
- url_launcher_ios (0.0.1):
|
- url_launcher_ios (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
@@ -268,6 +287,7 @@ DEPENDENCIES:
|
|||||||
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
||||||
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||||
- sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`)
|
- sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`)
|
||||||
|
- sqlite3_flutter_libs (from `.symlinks/plugins/sqlite3_flutter_libs/darwin`)
|
||||||
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||||
- video_compress (from `.symlinks/plugins/video_compress/ios`)
|
- video_compress (from `.symlinks/plugins/video_compress/ios`)
|
||||||
- volume_controller (from `.symlinks/plugins/volume_controller/ios`)
|
- volume_controller (from `.symlinks/plugins/volume_controller/ios`)
|
||||||
@@ -294,6 +314,7 @@ SPEC REPOS:
|
|||||||
- PromisesObjC
|
- PromisesObjC
|
||||||
- SAMKeychain
|
- SAMKeychain
|
||||||
- SDWebImage
|
- SDWebImage
|
||||||
|
- sqlite3
|
||||||
- SwiftyGif
|
- SwiftyGif
|
||||||
- WebRTC-SDK
|
- WebRTC-SDK
|
||||||
|
|
||||||
@@ -360,6 +381,8 @@ EXTERNAL SOURCES:
|
|||||||
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
|
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
|
||||||
sqflite_darwin:
|
sqflite_darwin:
|
||||||
:path: ".symlinks/plugins/sqflite_darwin/darwin"
|
:path: ".symlinks/plugins/sqflite_darwin/darwin"
|
||||||
|
sqlite3_flutter_libs:
|
||||||
|
:path: ".symlinks/plugins/sqlite3_flutter_libs/darwin"
|
||||||
url_launcher_ios:
|
url_launcher_ios:
|
||||||
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
||||||
video_compress:
|
video_compress:
|
||||||
@@ -421,6 +444,8 @@ SPEC CHECKSUMS:
|
|||||||
share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f
|
share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f
|
||||||
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
|
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
|
||||||
sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d
|
sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d
|
||||||
|
sqlite3: 4922312598b67e1825c6a6c821296dcbf6783046
|
||||||
|
sqlite3_flutter_libs: 069c435986dd4b63461aecd68f4b30be4a9e9daa
|
||||||
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
|
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
|
||||||
url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe
|
url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe
|
||||||
video_compress: fce97e4fb1dfd88175aa07d2ffc8a2f297f87fbe
|
video_compress: fce97e4fb1dfd88175aa07d2ffc8a2f297f87fbe
|
||||||
|
|||||||
@@ -2,11 +2,12 @@ import 'dart:async';
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:math' as math;
|
import 'dart:math' as math;
|
||||||
|
|
||||||
import 'package:collection/collection.dart';
|
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:drift/drift.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hive/hive.dart';
|
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:surface/database/database.dart';
|
||||||
|
import 'package:surface/providers/database.dart';
|
||||||
import 'package:surface/providers/sn_attachment.dart';
|
import 'package:surface/providers/sn_attachment.dart';
|
||||||
import 'package:surface/providers/sn_network.dart';
|
import 'package:surface/providers/sn_network.dart';
|
||||||
import 'package:surface/providers/user_directory.dart';
|
import 'package:surface/providers/user_directory.dart';
|
||||||
@@ -16,13 +17,13 @@ import 'package:surface/types/websocket.dart';
|
|||||||
import 'package:uuid/uuid.dart';
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
class ChatMessageController extends ChangeNotifier {
|
class ChatMessageController extends ChangeNotifier {
|
||||||
static const kChatMessageBoxPrefix = 'nex_chat_messages_';
|
|
||||||
static const kSingleBatchLoadLimit = 100;
|
static const kSingleBatchLoadLimit = 100;
|
||||||
|
|
||||||
late final SnNetworkProvider _sn;
|
late final SnNetworkProvider _sn;
|
||||||
late final UserDirectoryProvider _ud;
|
late final UserDirectoryProvider _ud;
|
||||||
late final WebSocketProvider _ws;
|
late final WebSocketProvider _ws;
|
||||||
late final SnAttachmentProvider _attach;
|
late final SnAttachmentProvider _attach;
|
||||||
|
late final DatabaseProvider _dt;
|
||||||
|
|
||||||
StreamSubscription? _wsSubscription;
|
StreamSubscription? _wsSubscription;
|
||||||
|
|
||||||
@@ -31,6 +32,7 @@ class ChatMessageController extends ChangeNotifier {
|
|||||||
_ud = context.read<UserDirectoryProvider>();
|
_ud = context.read<UserDirectoryProvider>();
|
||||||
_ws = context.read<WebSocketProvider>();
|
_ws = context.read<WebSocketProvider>();
|
||||||
_attach = context.read<SnAttachmentProvider>();
|
_attach = context.read<SnAttachmentProvider>();
|
||||||
|
_dt = context.read<DatabaseProvider>();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isPending = true;
|
bool isPending = true;
|
||||||
@@ -38,9 +40,9 @@ class ChatMessageController extends ChangeNotifier {
|
|||||||
|
|
||||||
int? messageTotal;
|
int? messageTotal;
|
||||||
|
|
||||||
bool get isAllLoaded => messageTotal != null && messages.length >= messageTotal!;
|
bool get isAllLoaded =>
|
||||||
|
messageTotal != null && messages.length >= messageTotal!;
|
||||||
|
|
||||||
String? _boxKey;
|
|
||||||
SnChannel? channel;
|
SnChannel? channel;
|
||||||
SnChannelMember? profile;
|
SnChannelMember? profile;
|
||||||
|
|
||||||
@@ -51,25 +53,17 @@ class ChatMessageController extends ChangeNotifier {
|
|||||||
/// Stored as a list of nonce to provide the loading state
|
/// Stored as a list of nonce to provide the loading state
|
||||||
final List<String> unconfirmedMessages = List.empty(growable: true);
|
final List<String> unconfirmedMessages = List.empty(growable: true);
|
||||||
|
|
||||||
Box<SnChatMessage>? get _box => (_boxKey == null || isPending) ? null : Hive.box<SnChatMessage>(_boxKey!);
|
|
||||||
|
|
||||||
final List<SnChannelMember> typingMembers = List.empty(growable: true);
|
final List<SnChannelMember> typingMembers = List.empty(growable: true);
|
||||||
final Map<int, Timer> typingInactiveTimer = {};
|
final Map<int, Timer> typingInactiveTimer = {};
|
||||||
|
|
||||||
Future<void> initialize(SnChannel chan) async {
|
Future<void> initialize(SnChannel chan) async {
|
||||||
channel = chan;
|
channel = chan;
|
||||||
|
|
||||||
// Initialize local data
|
|
||||||
_boxKey = '$kChatMessageBoxPrefix${chan.id}';
|
|
||||||
await Hive.openBox<SnChatMessage>(_boxKey!);
|
|
||||||
|
|
||||||
// Fetch channel profile
|
// Fetch channel profile
|
||||||
final resp = await _sn.client.get(
|
final resp = await _sn.client.get(
|
||||||
'/cgi/im/channels/${chan.keyPath}/me',
|
'/cgi/im/channels/${chan.keyPath}/me',
|
||||||
);
|
);
|
||||||
profile = SnChannelMember.fromJson(
|
profile = SnChannelMember.fromJson(resp.data);
|
||||||
resp.data as Map<String, dynamic>,
|
|
||||||
);
|
|
||||||
|
|
||||||
_wsSubscription = _ws.pk.stream.listen((event) {
|
_wsSubscription = _ws.pk.stream.listen((event) {
|
||||||
switch (event.method) {
|
switch (event.method) {
|
||||||
@@ -87,7 +81,8 @@ class ChatMessageController extends ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
typingInactiveTimer[member.id]?.cancel();
|
typingInactiveTimer[member.id]?.cancel();
|
||||||
typingInactiveTimer[member.id] = Timer(const Duration(seconds: 3), () {
|
typingInactiveTimer[member.id] =
|
||||||
|
Timer(const Duration(seconds: 3), () {
|
||||||
typingMembers.removeWhere((x) => x.id == member.id);
|
typingMembers.removeWhere((x) => x.id == member.id);
|
||||||
typingInactiveTimer.remove(member.id);
|
typingInactiveTimer.remove(member.id);
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
@@ -129,10 +124,16 @@ class ChatMessageController extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _saveMessageToLocal(Iterable<SnChatMessage> messages) async {
|
Future<void> _saveMessageToLocal(Iterable<SnChatMessage> messages) async {
|
||||||
if (_box == null) return;
|
await _dt.db.snLocalChatMessage.insertAll(
|
||||||
await _box!.putAll({
|
messages.map(
|
||||||
for (final message in messages) message.id: message,
|
(ele) => SnLocalChatMessageCompanion.insert(
|
||||||
});
|
id: Value(ele.id),
|
||||||
|
content: ele,
|
||||||
|
channelId: channel!.id,
|
||||||
|
createdAt: Value(ele.createdAt),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onConflict: DoNothing());
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _addUnconfirmedMessage(SnChatMessage message) async {
|
Future<void> _addUnconfirmedMessage(SnChatMessage message) async {
|
||||||
@@ -184,8 +185,21 @@ class ChatMessageController extends ChangeNotifier {
|
|||||||
await _applyMessage(message);
|
await _applyMessage(message);
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
|
||||||
if (_box == null) return;
|
if (isCheckedUpdate) {
|
||||||
await _box!.put(message.id, message);
|
await _dt.db.snLocalChatMessage.insertOne(
|
||||||
|
SnLocalChatMessageCompanion.insert(
|
||||||
|
id: Value(message.id),
|
||||||
|
content: message,
|
||||||
|
channelId: channel!.id,
|
||||||
|
createdAt: Value(message.createdAt),
|
||||||
|
),
|
||||||
|
onConflict: DoUpdate((_) => SnLocalChatMessageCompanion.custom(
|
||||||
|
content: Constant(jsonEncode(message.toJson())),
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
incomeStrandedQueue.add(message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _applyMessage(SnChatMessage message) async {
|
Future<void> _applyMessage(SnChatMessage message) async {
|
||||||
@@ -194,7 +208,8 @@ class ChatMessageController extends ChangeNotifier {
|
|||||||
switch (message.type) {
|
switch (message.type) {
|
||||||
case 'messages.edit':
|
case 'messages.edit':
|
||||||
if (message.relatedEventId != null) {
|
if (message.relatedEventId != null) {
|
||||||
final idx = messages.indexWhere((x) => x.id == message.relatedEventId);
|
final idx =
|
||||||
|
messages.indexWhere((x) => x.id == message.relatedEventId);
|
||||||
if (idx != -1) {
|
if (idx != -1) {
|
||||||
final newBody = message.body;
|
final newBody = message.body;
|
||||||
newBody.remove('related_event');
|
newBody.remove('related_event');
|
||||||
@@ -202,16 +217,24 @@ class ChatMessageController extends ChangeNotifier {
|
|||||||
body: newBody,
|
body: newBody,
|
||||||
updatedAt: message.updatedAt,
|
updatedAt: message.updatedAt,
|
||||||
);
|
);
|
||||||
if (_box!.containsKey(message.relatedEventId)) {
|
if (message.relatedEventId != null) {
|
||||||
await _box!.put(message.relatedEventId, messages[idx]);
|
await (_dt.db.snLocalChatMessage.update()
|
||||||
|
..where((e) => e.id.equals(message.relatedEventId!)))
|
||||||
|
.write(
|
||||||
|
SnLocalChatMessageCompanion.custom(
|
||||||
|
content: Constant(jsonEncode(messages[idx].toJson())),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case 'messages.delete':
|
case 'messages.delete':
|
||||||
if (message.relatedEventId != null) {
|
if (message.relatedEventId != null) {
|
||||||
messages.removeWhere((x) => x.id == message.relatedEventId);
|
messages.removeWhere((x) => x.id == message.relatedEventId);
|
||||||
if (_box!.containsKey(message.relatedEventId)) {
|
if (message.relatedEventId != null) {
|
||||||
await _box!.delete(message.relatedEventId);
|
await (_dt.db.snLocalChatMessage.delete()
|
||||||
|
..where((e) => e.id.equals(message.relatedEventId!)))
|
||||||
|
.go();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -233,7 +256,8 @@ class ChatMessageController extends ChangeNotifier {
|
|||||||
'algorithm': 'plain',
|
'algorithm': 'plain',
|
||||||
if (quoteId != null) 'quote_event': quoteId,
|
if (quoteId != null) 'quote_event': quoteId,
|
||||||
if (relatedId != null) 'related_event': relatedId,
|
if (relatedId != null) 'related_event': relatedId,
|
||||||
if (attachments != null && attachments.isNotEmpty) 'attachments': attachments,
|
if (attachments != null && attachments.isNotEmpty)
|
||||||
|
'attachments': attachments,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Mock the message locally
|
// Mock the message locally
|
||||||
@@ -287,20 +311,34 @@ class ChatMessageController extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isCheckedUpdate = false;
|
||||||
|
List<SnChatMessage> incomeStrandedQueue = List.empty(growable: true);
|
||||||
|
|
||||||
/// Check the local storage is up to date with the server.
|
/// Check the local storage is up to date with the server.
|
||||||
/// If the local storage is not up to date, it will be updated.
|
/// If the local storage is not up to date, it will be updated.
|
||||||
Future<void> checkUpdate() async {
|
Future<void> checkUpdate() async {
|
||||||
if (_box == null) return;
|
|
||||||
if (_box!.isEmpty) return;
|
|
||||||
|
|
||||||
isLoading = true;
|
isLoading = true;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
|
||||||
|
final mostRecentMessage = await (_dt.db.snLocalChatMessage.select()
|
||||||
|
..limit(1)
|
||||||
|
..orderBy([
|
||||||
|
(e) =>
|
||||||
|
OrderingTerm(expression: e.createdAt, mode: OrderingMode.desc)
|
||||||
|
]))
|
||||||
|
.getSingleOrNull();
|
||||||
|
if (mostRecentMessage == null) {
|
||||||
|
// Initial load
|
||||||
|
await loadMessages(take: 20);
|
||||||
|
isCheckedUpdate = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final resp = await _sn.client.get(
|
final resp = await _sn.client.get(
|
||||||
'/cgi/im/channels/${channel!.keyPath}/events/update',
|
'/cgi/im/channels/${channel!.keyPath}/events/update',
|
||||||
queryParameters: {
|
queryParameters: {
|
||||||
'pivot': _box!.values.last.id,
|
'pivot': mostRecentMessage.content.id,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
if (resp.data['up_to_date'] == true) return;
|
if (resp.data['up_to_date'] == true) return;
|
||||||
@@ -316,6 +354,12 @@ class ChatMessageController extends ChangeNotifier {
|
|||||||
} finally {
|
} finally {
|
||||||
await loadMessages();
|
await loadMessages();
|
||||||
isLoading = false;
|
isLoading = false;
|
||||||
|
|
||||||
|
isCheckedUpdate = true;
|
||||||
|
_saveMessageToLocal(incomeStrandedQueue).then((_) {
|
||||||
|
incomeStrandedQueue.clear();
|
||||||
|
});
|
||||||
|
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -324,13 +368,18 @@ class ChatMessageController extends ChangeNotifier {
|
|||||||
/// If it was not found in local storage we will look it up in remote
|
/// If it was not found in local storage we will look it up in remote
|
||||||
Future<SnChatMessage?> getMessage(int id) async {
|
Future<SnChatMessage?> getMessage(int id) async {
|
||||||
SnChatMessage? out;
|
SnChatMessage? out;
|
||||||
if (_box != null && _box!.containsKey(id)) {
|
final local = await (_dt.db.snLocalChatMessage.select()
|
||||||
out = _box!.get(id);
|
..limit(1)
|
||||||
|
..where((e) => e.id.equals(id)))
|
||||||
|
.getSingleOrNull();
|
||||||
|
if (local != null) {
|
||||||
|
out = local.content;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (out == null) {
|
if (out == null) {
|
||||||
try {
|
try {
|
||||||
final resp = await _sn.client.get('/cgi/im/channels/${channel!.keyPath}/events/$id');
|
final resp = await _sn.client
|
||||||
|
.get('/cgi/im/channels/${channel!.keyPath}/events/$id');
|
||||||
out = SnChatMessage.fromJson(resp.data);
|
out = SnChatMessage.fromJson(resp.data);
|
||||||
_saveMessageToLocal([out]);
|
_saveMessageToLocal([out]);
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
@@ -364,16 +413,21 @@ class ChatMessageController extends ChangeNotifier {
|
|||||||
bool forceLocal = false,
|
bool forceLocal = false,
|
||||||
bool forceRemote = false,
|
bool forceRemote = false,
|
||||||
}) async {
|
}) async {
|
||||||
|
final localTotal = await _dt.db.snLocalChatMessage
|
||||||
|
.count(where: (e) => e.channelId.equals(channel!.id))
|
||||||
|
.getSingle();
|
||||||
|
|
||||||
late List<SnChatMessage> out;
|
late List<SnChatMessage> out;
|
||||||
if (_box != null && (_box!.length >= take + offset || forceLocal) && !forceRemote) {
|
if ((localTotal >= take + offset || forceLocal) && !forceRemote) {
|
||||||
out = _box!.keys
|
final result = await (_dt.db.snLocalChatMessage.select()
|
||||||
.toList()
|
..where((e) => e.channelId.equals(channel!.id))
|
||||||
.cast<int>()
|
..orderBy([
|
||||||
.sorted((a, b) => b.compareTo(a))
|
(e) =>
|
||||||
.skip(offset)
|
OrderingTerm(expression: e.createdAt, mode: OrderingMode.desc)
|
||||||
.take(take)
|
])
|
||||||
.map((key) => _box!.get(key)!)
|
..limit(take, offset: offset))
|
||||||
.toList();
|
.get();
|
||||||
|
out = result.map((e) => e.content).toList();
|
||||||
} else {
|
} else {
|
||||||
final resp = await _sn.client.get(
|
final resp = await _sn.client.get(
|
||||||
'/cgi/im/channels/${channel!.keyPath}/events',
|
'/cgi/im/channels/${channel!.keyPath}/events',
|
||||||
@@ -408,7 +462,8 @@ class ChatMessageController extends ChangeNotifier {
|
|||||||
quoteEvent: quoteEvent,
|
quoteEvent: quoteEvent,
|
||||||
attachments: attachments
|
attachments: attachments
|
||||||
.where(
|
.where(
|
||||||
(ele) => out[i].body['attachments']?.contains(ele?.rid) ?? false,
|
(ele) =>
|
||||||
|
out[i].body['attachments']?.contains(ele?.rid) ?? false,
|
||||||
)
|
)
|
||||||
.toList(),
|
.toList(),
|
||||||
),
|
),
|
||||||
@@ -416,7 +471,10 @@ class ChatMessageController extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Preload sender accounts
|
// Preload sender accounts
|
||||||
final accountId = out.where((ele) => ele.sender.accountId >= 0).map((ele) => ele.sender.accountId).toSet();
|
final accountId = out
|
||||||
|
.where((ele) => ele.sender.accountId >= 0)
|
||||||
|
.map((ele) => ele.sender.accountId)
|
||||||
|
.toSet();
|
||||||
await _ud.listAccount(accountId);
|
await _ud.listAccount(accountId);
|
||||||
|
|
||||||
return out;
|
return out;
|
||||||
@@ -443,7 +501,6 @@ class ChatMessageController extends ChangeNotifier {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_box?.close();
|
|
||||||
_wsSubscription?.cancel();
|
_wsSubscription?.cancel();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import 'package:surface/providers/sn_network.dart';
|
|||||||
import 'package:surface/types/attachment.dart';
|
import 'package:surface/types/attachment.dart';
|
||||||
import 'package:surface/types/poll.dart';
|
import 'package:surface/types/poll.dart';
|
||||||
import 'package:surface/types/post.dart';
|
import 'package:surface/types/post.dart';
|
||||||
|
import 'package:surface/types/realm.dart';
|
||||||
import 'package:surface/widgets/dialog.dart';
|
import 'package:surface/widgets/dialog.dart';
|
||||||
import 'package:surface/widgets/universal_image.dart';
|
import 'package:surface/widgets/universal_image.dart';
|
||||||
import 'package:video_compress/video_compress.dart';
|
import 'package:video_compress/video_compress.dart';
|
||||||
@@ -159,12 +160,13 @@ class PostWriteController extends ChangeNotifier {
|
|||||||
final TextEditingController rewardController = TextEditingController();
|
final TextEditingController rewardController = TextEditingController();
|
||||||
|
|
||||||
ContentInsertionConfiguration get contentInsertionConfiguration => ContentInsertionConfiguration(
|
ContentInsertionConfiguration get contentInsertionConfiguration => ContentInsertionConfiguration(
|
||||||
onContentInserted: (KeyboardInsertedContent content) {
|
onContentInserted: (KeyboardInsertedContent content) {
|
||||||
if (content.hasData) {
|
if (content.hasData) {
|
||||||
addAttachments([PostWriteMedia.fromBytes(content.data!, 'attachmentInsertedImage'.tr(), SnMediaType.image)]);
|
addAttachments(
|
||||||
}
|
[PostWriteMedia.fromBytes(content.data!, 'attachmentInsertedImage'.tr(), SnMediaType.image)]);
|
||||||
},
|
}
|
||||||
);
|
},
|
||||||
|
);
|
||||||
|
|
||||||
bool _temporarySaveActive = false;
|
bool _temporarySaveActive = false;
|
||||||
|
|
||||||
@@ -196,6 +198,7 @@ class PostWriteController extends ChangeNotifier {
|
|||||||
bool isLoading = false, isBusy = false;
|
bool isLoading = false, isBusy = false;
|
||||||
double? progress;
|
double? progress;
|
||||||
|
|
||||||
|
SnRealm? realm;
|
||||||
SnPublisher? publisher;
|
SnPublisher? publisher;
|
||||||
SnPost? editingPost, repostingPost, replyingPost;
|
SnPost? editingPost, repostingPost, replyingPost;
|
||||||
|
|
||||||
@@ -244,6 +247,9 @@ class PostWriteController extends ChangeNotifier {
|
|||||||
if (post.preload?.thumbnail != null && (post.preload?.thumbnail?.rid.isNotEmpty ?? false)) {
|
if (post.preload?.thumbnail != null && (post.preload?.thumbnail?.rid.isNotEmpty ?? false)) {
|
||||||
thumbnail = PostWriteMedia(post.preload!.thumbnail);
|
thumbnail = PostWriteMedia(post.preload!.thumbnail);
|
||||||
}
|
}
|
||||||
|
if (post.preload?.realm != null) {
|
||||||
|
realm = post.preload!.realm!;
|
||||||
|
}
|
||||||
|
|
||||||
editingPost = post;
|
editingPost = post;
|
||||||
}
|
}
|
||||||
@@ -379,6 +385,7 @@ class PostWriteController extends ChangeNotifier {
|
|||||||
if (replyingPost != null) 'reply_to': replyingPost!.toJson(),
|
if (replyingPost != null) 'reply_to': replyingPost!.toJson(),
|
||||||
if (repostingPost != null) 'repost_to': repostingPost!.toJson(),
|
if (repostingPost != null) 'repost_to': repostingPost!.toJson(),
|
||||||
if (poll != null) 'poll': poll!.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;
|
replyingPost = data['reply_to'] != null ? SnPost.fromJson(data['reply_to']) : null;
|
||||||
repostingPost = data['repost_to'] != null ? SnPost.fromJson(data['repost_to']) : null;
|
repostingPost = data['repost_to'] != null ? SnPost.fromJson(data['repost_to']) : null;
|
||||||
poll = data['poll'] != null ? SnPoll.fromJson(data['poll']) : null;
|
poll = data['poll'] != null ? SnPoll.fromJson(data['poll']) : null;
|
||||||
|
realm = data['realm'] != null ? SnRealm.fromJson(data['realm']) : null;
|
||||||
temporaryRestored = true;
|
temporaryRestored = true;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
});
|
});
|
||||||
@@ -525,6 +533,7 @@ class PostWriteController extends ChangeNotifier {
|
|||||||
if (reward != null) 'reward': reward,
|
if (reward != null) 'reward': reward,
|
||||||
if (videoAttachment != null) 'video': videoAttachment!.rid,
|
if (videoAttachment != null) 'video': videoAttachment!.rid,
|
||||||
if (poll != null) 'poll': poll!.id,
|
if (poll != null) 'poll': poll!.id,
|
||||||
|
if (realm != null) 'realm': realm!.id,
|
||||||
},
|
},
|
||||||
onSendProgress: (count, total) {
|
onSendProgress: (count, total) {
|
||||||
progress = baseProgressVal + (count / total) * (kPostingProgressWeight / 2);
|
progress = baseProgressVal + (count / total) * (kPostingProgressWeight / 2);
|
||||||
@@ -571,17 +580,8 @@ class PostWriteController extends ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
void setThumbnail(int? idx) {
|
void setThumbnail(SnAttachment? value) {
|
||||||
if (idx == null) {
|
thumbnail = value == null ? null : PostWriteMedia(value);
|
||||||
attachments.add(thumbnail!);
|
|
||||||
thumbnail = null;
|
|
||||||
} else {
|
|
||||||
if (thumbnail != null) {
|
|
||||||
attachments.add(thumbnail!);
|
|
||||||
}
|
|
||||||
thumbnail = attachments[idx];
|
|
||||||
attachments.removeAt(idx);
|
|
||||||
}
|
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -633,6 +633,11 @@ class PostWriteController extends ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setRealm(SnRealm? value) {
|
||||||
|
realm = value;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
void setProgress(double? value) {
|
void setProgress(double? value) {
|
||||||
progress = value;
|
progress = value;
|
||||||
_temporaryPlanSave();
|
_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();
|
||||||
@@ -24,6 +24,7 @@ import 'package:surface/firebase_options.dart';
|
|||||||
import 'package:surface/providers/channel.dart';
|
import 'package:surface/providers/channel.dart';
|
||||||
import 'package:surface/providers/chat_call.dart';
|
import 'package:surface/providers/chat_call.dart';
|
||||||
import 'package:surface/providers/config.dart';
|
import 'package:surface/providers/config.dart';
|
||||||
|
import 'package:surface/providers/database.dart';
|
||||||
import 'package:surface/providers/link_preview.dart';
|
import 'package:surface/providers/link_preview.dart';
|
||||||
import 'package:surface/providers/navigation.dart';
|
import 'package:surface/providers/navigation.dart';
|
||||||
import 'package:surface/providers/notification.dart';
|
import 'package:surface/providers/notification.dart';
|
||||||
@@ -31,6 +32,7 @@ import 'package:surface/providers/post.dart';
|
|||||||
import 'package:surface/providers/relationship.dart';
|
import 'package:surface/providers/relationship.dart';
|
||||||
import 'package:surface/providers/sn_attachment.dart';
|
import 'package:surface/providers/sn_attachment.dart';
|
||||||
import 'package:surface/providers/sn_network.dart';
|
import 'package:surface/providers/sn_network.dart';
|
||||||
|
import 'package:surface/providers/sn_realm.dart';
|
||||||
import 'package:surface/providers/sn_sticker.dart';
|
import 'package:surface/providers/sn_sticker.dart';
|
||||||
import 'package:surface/providers/special_day.dart';
|
import 'package:surface/providers/special_day.dart';
|
||||||
import 'package:surface/providers/theme.dart';
|
import 'package:surface/providers/theme.dart';
|
||||||
@@ -87,7 +89,7 @@ void main() async {
|
|||||||
Hive.registerAdapter(SnChannelMemberImplAdapter());
|
Hive.registerAdapter(SnChannelMemberImplAdapter());
|
||||||
Hive.registerAdapter(SnChatMessageImplAdapter());
|
Hive.registerAdapter(SnChatMessageImplAdapter());
|
||||||
|
|
||||||
if (kIsWeb && !Platform.isLinux) {
|
if (!kIsWeb && !Platform.isLinux) {
|
||||||
await Firebase.initializeApp(
|
await Firebase.initializeApp(
|
||||||
options: DefaultFirebaseOptions.currentPlatform,
|
options: DefaultFirebaseOptions.currentPlatform,
|
||||||
);
|
);
|
||||||
@@ -141,6 +143,9 @@ class SolianApp extends StatelessWidget {
|
|||||||
assetLoader: JsonAssetLoader(),
|
assetLoader: JsonAssetLoader(),
|
||||||
child: MultiProvider(
|
child: MultiProvider(
|
||||||
providers: [
|
providers: [
|
||||||
|
// Infrastructure layer
|
||||||
|
Provider(create: (ctx) => DatabaseProvider(ctx)),
|
||||||
|
|
||||||
// System extensions layer
|
// System extensions layer
|
||||||
Provider(create: (ctx) => HomeWidgetProvider(ctx)),
|
Provider(create: (ctx) => HomeWidgetProvider(ctx)),
|
||||||
|
|
||||||
@@ -155,6 +160,7 @@ class SolianApp extends StatelessWidget {
|
|||||||
Provider(create: (ctx) => SnNetworkProvider(ctx)),
|
Provider(create: (ctx) => SnNetworkProvider(ctx)),
|
||||||
Provider(create: (ctx) => UserDirectoryProvider(ctx)),
|
Provider(create: (ctx) => UserDirectoryProvider(ctx)),
|
||||||
Provider(create: (ctx) => SnAttachmentProvider(ctx)),
|
Provider(create: (ctx) => SnAttachmentProvider(ctx)),
|
||||||
|
Provider(create: (ctx) => SnRealmProvider(ctx)),
|
||||||
Provider(create: (ctx) => SnPostContentProvider(ctx)),
|
Provider(create: (ctx) => SnPostContentProvider(ctx)),
|
||||||
Provider(create: (ctx) => SnRelationshipProvider(ctx)),
|
Provider(create: (ctx) => SnRelationshipProvider(ctx)),
|
||||||
Provider(create: (ctx) => SnLinkPreviewProvider(ctx)),
|
Provider(create: (ctx) => SnLinkPreviewProvider(ctx)),
|
||||||
@@ -427,8 +433,16 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener {
|
|||||||
});
|
});
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
child: SizeChangedLayoutNotifier(
|
child: OrientationBuilder(
|
||||||
child: widget.child,
|
builder: (context, orientation) {
|
||||||
|
final cfg = context.read<ConfigProvider>();
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
cfg.calcDrawerSize(context);
|
||||||
|
});
|
||||||
|
return SizeChangedLayoutNotifier(
|
||||||
|
child: widget.child,
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:drift/drift.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hive_flutter/hive_flutter.dart';
|
import 'package:hive_flutter/hive_flutter.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:surface/controllers/chat_message_controller.dart';
|
import 'package:surface/database/database.dart';
|
||||||
|
import 'package:surface/providers/database.dart';
|
||||||
import 'package:surface/providers/sn_network.dart';
|
import 'package:surface/providers/sn_network.dart';
|
||||||
import 'package:surface/providers/user_directory.dart';
|
import 'package:surface/providers/user_directory.dart';
|
||||||
import 'package:surface/types/chat.dart';
|
import 'package:surface/types/chat.dart';
|
||||||
@@ -12,12 +16,12 @@ class ChatChannelProvider extends ChangeNotifier {
|
|||||||
|
|
||||||
late final SnNetworkProvider _sn;
|
late final SnNetworkProvider _sn;
|
||||||
late final UserDirectoryProvider _ud;
|
late final UserDirectoryProvider _ud;
|
||||||
|
late final DatabaseProvider _dt;
|
||||||
Box<SnChannel>? get _channelBox => Hive.box<SnChannel>(kChatChannelBoxName);
|
|
||||||
|
|
||||||
ChatChannelProvider(BuildContext context) {
|
ChatChannelProvider(BuildContext context) {
|
||||||
_sn = context.read<SnNetworkProvider>();
|
_sn = context.read<SnNetworkProvider>();
|
||||||
_ud = context.read<UserDirectoryProvider>();
|
_ud = context.read<UserDirectoryProvider>();
|
||||||
|
_dt = context.read<DatabaseProvider>();
|
||||||
_initializeLocalData();
|
_initializeLocalData();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,10 +30,23 @@ class ChatChannelProvider extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _saveChannelToLocal(Iterable<SnChannel> channels) async {
|
Future<void> _saveChannelToLocal(Iterable<SnChannel> channels) async {
|
||||||
if (_channelBox == null) return;
|
await Future.wait(
|
||||||
await _channelBox!.putAll({
|
channels.map(
|
||||||
for (final channel in channels) channel.key: channel,
|
(ele) => _dt.db.snLocalChatChannel.insertOne(
|
||||||
});
|
SnLocalChatChannelCompanion.insert(
|
||||||
|
id: Value(ele.id),
|
||||||
|
alias: ele.key,
|
||||||
|
content: ele,
|
||||||
|
createdAt: Value(ele.createdAt),
|
||||||
|
),
|
||||||
|
onConflict: DoUpdate(
|
||||||
|
(_) => SnLocalChatChannelCompanion.custom(
|
||||||
|
content: Constant(jsonEncode(ele.toJson())),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<SnChannel>> _fetchChannelsFromServer({
|
Future<List<SnChannel>> _fetchChannelsFromServer({
|
||||||
@@ -54,12 +71,13 @@ class ChatChannelProvider extends ChangeNotifier {
|
|||||||
/// It will use the local storage as much as possible.
|
/// It will use the local storage as much as possible.
|
||||||
/// The alias should include the scope, formatted as `scope:alias`.
|
/// The alias should include the scope, formatted as `scope:alias`.
|
||||||
Future<SnChannel> getChannel(String key) async {
|
Future<SnChannel> getChannel(String key) async {
|
||||||
if (_channelBox != null) {
|
final local = await (_dt.db.snLocalChatChannel.select()
|
||||||
final local = _channelBox!.get(key);
|
..where((e) => e.alias.equals(key)))
|
||||||
if (local != null) return local;
|
.getSingleOrNull();
|
||||||
}
|
if (local != null) return local.content;
|
||||||
|
|
||||||
var resp = await _sn.client.get('/cgi/im/channels/$key');
|
var resp =
|
||||||
|
await _sn.client.get('/cgi/im/channels/${key.replaceAll(':', '/')}');
|
||||||
var out = SnChannel.fromJson(resp.data);
|
var out = SnChannel.fromJson(resp.data);
|
||||||
|
|
||||||
// Preload realm of the channel
|
// Preload realm of the channel
|
||||||
@@ -77,8 +95,19 @@ class ChatChannelProvider extends ChangeNotifier {
|
|||||||
/// And the second time is when the data was fetched from the server.
|
/// And the second time is when the data was fetched from the server.
|
||||||
/// But there is some exception that will only cause one of them to be emitted.
|
/// But there is some exception that will only cause one of them to be emitted.
|
||||||
/// Like the local storage is broken or the server is down.
|
/// Like the local storage is broken or the server is down.
|
||||||
Stream<List<SnChannel>> fetchChannels() async* {
|
Stream<List<SnChannel>> fetchChannels(
|
||||||
if (_channelBox != null) yield _channelBox!.values.toList();
|
{bool noRemote = false, bool noLocal = false}) async* {
|
||||||
|
if (!noLocal) {
|
||||||
|
final local = await (_dt.db.snLocalChatChannel.select()
|
||||||
|
..orderBy([
|
||||||
|
(e) =>
|
||||||
|
OrderingTerm(expression: e.createdAt, mode: OrderingMode.desc)
|
||||||
|
]))
|
||||||
|
.get();
|
||||||
|
yield local.map((e) => e.content).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (noRemote) return;
|
||||||
|
|
||||||
var resp = await _sn.client.get('/cgi/id/realms/me/available');
|
var resp = await _sn.client.get('/cgi/id/realms/me/available');
|
||||||
final realms = List<SnRealm>.from(
|
final realms = List<SnRealm>.from(
|
||||||
@@ -120,23 +149,23 @@ class ChatChannelProvider extends ChangeNotifier {
|
|||||||
Future<List<SnChatMessage>> getLastMessages(
|
Future<List<SnChatMessage>> getLastMessages(
|
||||||
Iterable<SnChannel> channels,
|
Iterable<SnChannel> channels,
|
||||||
) async {
|
) async {
|
||||||
final result = List<SnChatMessage>.empty(growable: true);
|
final result = List<Future<SnLocalChatMessageData?>>.empty(growable: true);
|
||||||
for (final channel in channels) {
|
for (final channel in channels) {
|
||||||
final channelBox = await Hive.openBox<SnChatMessage>(
|
final out = (_dt.db.snLocalChatMessage.select()
|
||||||
'${ChatMessageController.kChatMessageBoxPrefix}${channel.id}',
|
..where((e) => e.channelId.equals(channel.id))
|
||||||
);
|
..orderBy([
|
||||||
final lastMessage =
|
(e) =>
|
||||||
channelBox.isNotEmpty ? channelBox.values.reduce((a, b) => a.createdAt.isAfter(b.createdAt) ? a : b) : null;
|
OrderingTerm(expression: e.createdAt, mode: OrderingMode.desc)
|
||||||
if (lastMessage != null) result.add(lastMessage);
|
])
|
||||||
channelBox.close();
|
..limit(1))
|
||||||
|
.getSingleOrNull();
|
||||||
|
result.add(out);
|
||||||
}
|
}
|
||||||
await _ud.listAccount(result.map((ele) => ele.sender.accountId).toSet());
|
final out = (await Future.wait(result))
|
||||||
return result;
|
.where((e) => e != null)
|
||||||
}
|
.map((e) => e!.content)
|
||||||
|
.toList();
|
||||||
@override
|
await _ud.listAccount(out.map((ele) => ele.sender.accountId).toSet());
|
||||||
void dispose() {
|
return out;
|
||||||
_channelBox?.close();
|
|
||||||
super.dispose();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ const kAppDrawerPreferCollapse = 'app_drawer_prefer_collapse';
|
|||||||
const kAppNotifyWithHaptic = 'app_notify_with_haptic';
|
const kAppNotifyWithHaptic = 'app_notify_with_haptic';
|
||||||
const kAppExpandPostLink = 'app_expand_post_link';
|
const kAppExpandPostLink = 'app_expand_post_link';
|
||||||
const kAppExpandChatLink = 'app_expand_chat_link';
|
const kAppExpandChatLink = 'app_expand_chat_link';
|
||||||
|
const kAppRealmCompactView = 'app_realm_compact_view';
|
||||||
|
|
||||||
const Map<String, FilterQuality> kImageQualityLevel = {
|
const Map<String, FilterQuality> kImageQualityLevel = {
|
||||||
'settingsImageQualityLowest': FilterQuality.none,
|
'settingsImageQualityLowest': FilterQuality.none,
|
||||||
@@ -72,6 +73,13 @@ class ConfigProvider extends ChangeNotifier {
|
|||||||
return prefs.getString(kNetworkServerStoreKey) ?? kNetworkServerDefault;
|
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) {
|
set serverUrl(String url) {
|
||||||
prefs.setString(kNetworkServerStoreKey, url);
|
prefs.setString(kNetworkServerStoreKey, url);
|
||||||
_home.saveWidgetData("nex_server_url", 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,19 +2,23 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:surface/providers/sn_attachment.dart';
|
import 'package:surface/providers/sn_attachment.dart';
|
||||||
import 'package:surface/providers/sn_network.dart';
|
import 'package:surface/providers/sn_network.dart';
|
||||||
|
import 'package:surface/providers/sn_realm.dart';
|
||||||
import 'package:surface/providers/user_directory.dart';
|
import 'package:surface/providers/user_directory.dart';
|
||||||
import 'package:surface/types/poll.dart';
|
import 'package:surface/types/poll.dart';
|
||||||
import 'package:surface/types/post.dart';
|
import 'package:surface/types/post.dart';
|
||||||
|
import 'package:surface/types/realm.dart';
|
||||||
|
|
||||||
class SnPostContentProvider {
|
class SnPostContentProvider {
|
||||||
late final SnNetworkProvider _sn;
|
late final SnNetworkProvider _sn;
|
||||||
late final UserDirectoryProvider _ud;
|
late final UserDirectoryProvider _ud;
|
||||||
late final SnAttachmentProvider _attach;
|
late final SnAttachmentProvider _attach;
|
||||||
|
late final SnRealmProvider _realm;
|
||||||
|
|
||||||
SnPostContentProvider(BuildContext context) {
|
SnPostContentProvider(BuildContext context) {
|
||||||
_sn = context.read<SnNetworkProvider>();
|
_sn = context.read<SnNetworkProvider>();
|
||||||
_ud = context.read<UserDirectoryProvider>();
|
_ud = context.read<UserDirectoryProvider>();
|
||||||
_attach = context.read<SnAttachmentProvider>();
|
_attach = context.read<SnAttachmentProvider>();
|
||||||
|
_realm = context.read<SnRealmProvider>();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<SnPoll> _fetchPoll(int id) async {
|
Future<SnPoll> _fetchPoll(int id) async {
|
||||||
@@ -42,9 +46,13 @@ class SnPostContentProvider {
|
|||||||
final attachments = await _attach.getMultiple(rids.toList());
|
final attachments = await _attach.getMultiple(rids.toList());
|
||||||
for (var i = 0; i < out.length; i++) {
|
for (var i = 0; i < out.length; i++) {
|
||||||
SnPoll? poll;
|
SnPoll? poll;
|
||||||
|
SnRealm? realm;
|
||||||
if (out[i].pollId != null) {
|
if (out[i].pollId != null) {
|
||||||
poll = await _fetchPoll(out[i].pollId!);
|
poll = await _fetchPoll(out[i].pollId!);
|
||||||
}
|
}
|
||||||
|
if (out[i].realmId != null) {
|
||||||
|
realm = await _realm.getRealm(out[i].realmId!);
|
||||||
|
}
|
||||||
|
|
||||||
out[i] = out[i].copyWith(
|
out[i] = out[i].copyWith(
|
||||||
preload: SnPostPreload(
|
preload: SnPostPreload(
|
||||||
@@ -52,6 +60,7 @@ class SnPostContentProvider {
|
|||||||
attachments: attachments.where((ele) => out[i].body['attachments']?.contains(ele?.rid) ?? false).toList(),
|
attachments: attachments.where((ele) => out[i].body['attachments']?.contains(ele?.rid) ?? false).toList(),
|
||||||
video: attachments.where((ele) => ele?.rid == out[i].body['video']).firstOrNull,
|
video: attachments.where((ele) => ele?.rid == out[i].body['video']).firstOrNull,
|
||||||
poll: poll,
|
poll: poll,
|
||||||
|
realm: realm,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -81,9 +90,13 @@ class SnPostContentProvider {
|
|||||||
final attachments = await _attach.getMultiple(rids.toList());
|
final attachments = await _attach.getMultiple(rids.toList());
|
||||||
|
|
||||||
SnPoll? poll;
|
SnPoll? poll;
|
||||||
|
SnRealm? realm;
|
||||||
if (out.pollId != null) {
|
if (out.pollId != null) {
|
||||||
poll = await _fetchPoll(out.pollId!);
|
poll = await _fetchPoll(out.pollId!);
|
||||||
}
|
}
|
||||||
|
if (out.realmId != null) {
|
||||||
|
realm = await _realm.getRealm(out.realmId!);
|
||||||
|
}
|
||||||
|
|
||||||
out = out.copyWith(
|
out = out.copyWith(
|
||||||
preload: SnPostPreload(
|
preload: SnPostPreload(
|
||||||
@@ -91,6 +104,7 @@ class SnPostContentProvider {
|
|||||||
attachments: attachments.where((ele) => out.body['attachments']?.contains(ele?.rid) ?? false).toList(),
|
attachments: attachments.where((ele) => out.body['attachments']?.contains(ele?.rid) ?? false).toList(),
|
||||||
video: attachments.where((ele) => ele?.rid == out.body['video']).firstOrNull,
|
video: attachments.where((ele) => ele?.rid == out.body['video']).firstOrNull,
|
||||||
poll: poll,
|
poll: poll,
|
||||||
|
realm: realm,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -112,6 +126,8 @@ class SnPostContentProvider {
|
|||||||
String? author,
|
String? author,
|
||||||
Iterable<String>? categories,
|
Iterable<String>? categories,
|
||||||
Iterable<String>? tags,
|
Iterable<String>? tags,
|
||||||
|
String? realm,
|
||||||
|
String? channel,
|
||||||
}) async {
|
}) async {
|
||||||
final resp = await _sn.client.get('/cgi/co/posts', queryParameters: {
|
final resp = await _sn.client.get('/cgi/co/posts', queryParameters: {
|
||||||
'take': take,
|
'take': take,
|
||||||
@@ -120,6 +136,8 @@ class SnPostContentProvider {
|
|||||||
if (author != null) 'author': author,
|
if (author != null) 'author': author,
|
||||||
if (tags?.isNotEmpty ?? false) 'tags': tags!.join(','),
|
if (tags?.isNotEmpty ?? false) 'tags': tags!.join(','),
|
||||||
if (categories?.isNotEmpty ?? false) 'categories': categories!.join(','),
|
if (categories?.isNotEmpty ?? false) 'categories': categories!.join(','),
|
||||||
|
if (realm != null) 'realm': realm,
|
||||||
|
if (channel != null) 'channel': channel,
|
||||||
});
|
});
|
||||||
final List<SnPost> out = await _preloadRelatedDataInBatch(
|
final List<SnPost> out = await _preloadRelatedDataInBatch(
|
||||||
List.from(resp.data['data']?.map((e) => SnPost.fromJson(e)) ?? []),
|
List.from(resp.data['data']?.map((e) => SnPost.fromJson(e)) ?? []),
|
||||||
|
|||||||
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,9 @@ import 'package:dismissible_page/dismissible_page.dart';
|
|||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
|
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
|
||||||
|
import 'package:gap/gap.dart';
|
||||||
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
import 'package:surface/providers/sn_network.dart';
|
import 'package:surface/providers/sn_network.dart';
|
||||||
@@ -27,9 +30,23 @@ class _AlbumScreenState extends State<AlbumScreen> {
|
|||||||
bool _isBusy = false;
|
bool _isBusy = false;
|
||||||
int? _totalCount;
|
int? _totalCount;
|
||||||
|
|
||||||
|
SnAttachmentBilling? _billing;
|
||||||
|
|
||||||
final List<SnAttachment> _attachments = List.empty(growable: true);
|
final List<SnAttachment> _attachments = List.empty(growable: true);
|
||||||
final List<String> _heroTags = 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 {
|
Future<void> _fetchAttachments() async {
|
||||||
setState(() => _isBusy = true);
|
setState(() => _isBusy = true);
|
||||||
|
|
||||||
@@ -62,6 +79,7 @@ class _AlbumScreenState extends State<AlbumScreen> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
_fetchBillingStatus();
|
||||||
_fetchAttachments();
|
_fetchAttachments();
|
||||||
_scrollController.addListener(() {
|
_scrollController.addListener(() {
|
||||||
if (_scrollController.position.atEdge) {
|
if (_scrollController.position.atEdge) {
|
||||||
@@ -91,6 +109,48 @@ class _AlbumScreenState extends State<AlbumScreen> {
|
|||||||
leading: AutoAppBarLeading(),
|
leading: AutoAppBarLeading(),
|
||||||
title: Text('screenAlbum').tr(),
|
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(
|
SliverMasonryGrid.extent(
|
||||||
childCount: _attachments.length,
|
childCount: _attachments.length,
|
||||||
maxCrossAxisExtent: 320,
|
maxCrossAxisExtent: 320,
|
||||||
|
|||||||
@@ -6,7 +6,9 @@ import 'package:go_router/go_router.dart';
|
|||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:surface/providers/channel.dart';
|
import 'package:surface/providers/channel.dart';
|
||||||
|
import 'package:surface/providers/sn_network.dart';
|
||||||
import 'package:surface/providers/user_directory.dart';
|
import 'package:surface/providers/user_directory.dart';
|
||||||
|
import 'package:surface/providers/userinfo.dart';
|
||||||
import 'package:surface/types/chat.dart';
|
import 'package:surface/types/chat.dart';
|
||||||
import 'package:surface/widgets/account/account_image.dart';
|
import 'package:surface/widgets/account/account_image.dart';
|
||||||
import 'package:surface/widgets/account/account_select.dart';
|
import 'package:surface/widgets/account/account_select.dart';
|
||||||
@@ -17,9 +19,6 @@ import 'package:surface/widgets/navigation/app_scaffold.dart';
|
|||||||
import 'package:surface/widgets/unauthorized_hint.dart';
|
import 'package:surface/widgets/unauthorized_hint.dart';
|
||||||
import 'package:uuid/uuid.dart';
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
import '../providers/sn_network.dart';
|
|
||||||
import '../providers/userinfo.dart';
|
|
||||||
|
|
||||||
class ChatScreen extends StatefulWidget {
|
class ChatScreen extends StatefulWidget {
|
||||||
const ChatScreen({super.key});
|
const ChatScreen({super.key});
|
||||||
|
|
||||||
@@ -35,7 +34,7 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
List<SnChannel>? _channels;
|
List<SnChannel>? _channels;
|
||||||
Map<int, SnChatMessage>? _lastMessages;
|
Map<int, SnChatMessage>? _lastMessages;
|
||||||
|
|
||||||
void _refreshChannels() {
|
void _refreshChannels({bool noRemote = false}) {
|
||||||
final ua = context.read<UserProvider>();
|
final ua = context.read<UserProvider>();
|
||||||
if (!ua.isAuthorized) {
|
if (!ua.isAuthorized) {
|
||||||
setState(() => _isBusy = false);
|
setState(() => _isBusy = false);
|
||||||
@@ -43,12 +42,15 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final chan = context.read<ChatChannelProvider>();
|
final chan = context.read<ChatChannelProvider>();
|
||||||
chan.fetchChannels().listen((channels) async {
|
chan.fetchChannels(noRemote: noRemote).listen((channels) async {
|
||||||
final lastMessages = await chan.getLastMessages(channels);
|
final lastMessages = await chan.getLastMessages(channels);
|
||||||
_lastMessages = {for (final val in lastMessages) val.channelId: val};
|
_lastMessages = {for (final val in lastMessages) val.channelId: val};
|
||||||
channels.sort((a, b) {
|
channels.sort((a, b) {
|
||||||
if (_lastMessages!.containsKey(a.id) && _lastMessages!.containsKey(b.id)) {
|
if (_lastMessages!.containsKey(a.id) &&
|
||||||
return _lastMessages![b.id]!.createdAt.compareTo(_lastMessages![a.id]!.createdAt);
|
_lastMessages!.containsKey(b.id)) {
|
||||||
|
return _lastMessages![b.id]!
|
||||||
|
.createdAt
|
||||||
|
.compareTo(_lastMessages![a.id]!.createdAt);
|
||||||
}
|
}
|
||||||
if (_lastMessages!.containsKey(a.id)) return -1;
|
if (_lastMessages!.containsKey(a.id)) return -1;
|
||||||
if (_lastMessages!.containsKey(b.id)) return 1;
|
if (_lastMessages!.containsKey(b.id)) return 1;
|
||||||
@@ -86,7 +88,8 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
void _newDirectMessage() async {
|
void _newDirectMessage() async {
|
||||||
final user = await showModalBottomSheet(
|
final user = await showModalBottomSheet(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => AccountSelect(title: 'channelNewDirectMessage'.tr()),
|
builder: (context) =>
|
||||||
|
AccountSelect(title: 'channelNewDirectMessage'.tr()),
|
||||||
);
|
);
|
||||||
if (user == null) return;
|
if (user == null) return;
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
@@ -98,7 +101,8 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
await sn.client.post('/cgi/im/channels/global/dm', data: {
|
await sn.client.post('/cgi/im/channels/global/dm', data: {
|
||||||
'alias': uuid.v4().replaceAll('-', '').substring(0, 12),
|
'alias': uuid.v4().replaceAll('-', '').substring(0, 12),
|
||||||
'name': 'DM',
|
'name': 'DM',
|
||||||
'description': 'A direct message channel between @${ua.user?.name} and @${user.name}',
|
'description':
|
||||||
|
'A direct message channel between @${ua.user?.name} and @${user.name}',
|
||||||
'related_user': user.id,
|
'related_user': user.id,
|
||||||
});
|
});
|
||||||
_fabKey.currentState!.toggle();
|
_fabKey.currentState!.toggle();
|
||||||
@@ -144,20 +148,27 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
type: ExpandableFabType.up,
|
type: ExpandableFabType.up,
|
||||||
childrenAnimation: ExpandableFabAnimation.none,
|
childrenAnimation: ExpandableFabAnimation.none,
|
||||||
overlayStyle: ExpandableFabOverlayStyle(
|
overlayStyle: ExpandableFabOverlayStyle(
|
||||||
color: Theme.of(context).colorScheme.surface.withAlpha((255 * 0.5).round()),
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.surface
|
||||||
|
.withAlpha((255 * 0.5).round()),
|
||||||
),
|
),
|
||||||
openButtonBuilder: RotateFloatingActionButtonBuilder(
|
openButtonBuilder: RotateFloatingActionButtonBuilder(
|
||||||
child: const Icon(Symbols.add, size: 28),
|
child: const Icon(Symbols.add, size: 28),
|
||||||
fabSize: ExpandableFabSize.regular,
|
fabSize: ExpandableFabSize.regular,
|
||||||
foregroundColor: Theme.of(context).floatingActionButtonTheme.foregroundColor,
|
foregroundColor:
|
||||||
backgroundColor: Theme.of(context).floatingActionButtonTheme.backgroundColor,
|
Theme.of(context).floatingActionButtonTheme.foregroundColor,
|
||||||
|
backgroundColor:
|
||||||
|
Theme.of(context).floatingActionButtonTheme.backgroundColor,
|
||||||
shape: const CircleBorder(),
|
shape: const CircleBorder(),
|
||||||
),
|
),
|
||||||
closeButtonBuilder: DefaultFloatingActionButtonBuilder(
|
closeButtonBuilder: DefaultFloatingActionButtonBuilder(
|
||||||
child: const Icon(Symbols.close, size: 28),
|
child: const Icon(Symbols.close, size: 28),
|
||||||
fabSize: ExpandableFabSize.regular,
|
fabSize: ExpandableFabSize.regular,
|
||||||
foregroundColor: Theme.of(context).floatingActionButtonTheme.foregroundColor,
|
foregroundColor:
|
||||||
backgroundColor: Theme.of(context).floatingActionButtonTheme.backgroundColor,
|
Theme.of(context).floatingActionButtonTheme.foregroundColor,
|
||||||
|
backgroundColor:
|
||||||
|
Theme.of(context).floatingActionButtonTheme.backgroundColor,
|
||||||
shape: const CircleBorder(),
|
shape: const CircleBorder(),
|
||||||
),
|
),
|
||||||
children: [
|
children: [
|
||||||
@@ -208,13 +219,17 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
final lastMessage = _lastMessages?[channel.id];
|
final lastMessage = _lastMessages?[channel.id];
|
||||||
|
|
||||||
if (channel.type == 1) {
|
if (channel.type == 1) {
|
||||||
final otherMember = channel.members?.cast<SnChannelMember?>().firstWhere(
|
final otherMember =
|
||||||
(ele) => ele?.accountId != ua.user?.id,
|
channel.members?.cast<SnChannelMember?>().firstWhere(
|
||||||
orElse: () => null,
|
(ele) => ele?.accountId != ua.user?.id,
|
||||||
);
|
orElse: () => null,
|
||||||
|
);
|
||||||
|
|
||||||
return ListTile(
|
return ListTile(
|
||||||
title: Text(ud.getAccountFromCache(otherMember?.accountId)?.nick ?? channel.name),
|
title: Text(ud
|
||||||
|
.getAccountFromCache(otherMember?.accountId)
|
||||||
|
?.nick ??
|
||||||
|
channel.name),
|
||||||
subtitle: lastMessage != null
|
subtitle: lastMessage != null
|
||||||
? Text(
|
? Text(
|
||||||
'${ud.getAccountFromCache(lastMessage.sender.accountId)?.nick}: ${lastMessage.body['text'] ?? 'Unable preview'}',
|
'${ud.getAccountFromCache(lastMessage.sender.accountId)?.nick}: ${lastMessage.body['text'] ?? 'Unable preview'}',
|
||||||
@@ -228,9 +243,12 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
|
contentPadding:
|
||||||
|
const EdgeInsets.symmetric(horizontal: 16),
|
||||||
leading: AccountImage(
|
leading: AccountImage(
|
||||||
content: ud.getAccountFromCache(otherMember?.accountId)?.avatar,
|
content: ud
|
||||||
|
.getAccountFromCache(otherMember?.accountId)
|
||||||
|
?.avatar,
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
GoRouter.of(context).pushNamed(
|
GoRouter.of(context).pushNamed(
|
||||||
@@ -240,7 +258,7 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
'alias': channel.alias,
|
'alias': channel.alias,
|
||||||
},
|
},
|
||||||
).then((value) {
|
).then((value) {
|
||||||
if (mounted) _refreshChannels();
|
if (mounted) _refreshChannels(noRemote: true);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -259,7 +277,8 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
|
contentPadding:
|
||||||
|
const EdgeInsets.symmetric(horizontal: 16),
|
||||||
leading: AccountImage(
|
leading: AccountImage(
|
||||||
content: null,
|
content: null,
|
||||||
fallbackWidget: const Icon(Symbols.chat, size: 20),
|
fallbackWidget: const Icon(Symbols.chat, size: 20),
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ class _ChannelDetailScreenState extends State<ChannelDetailScreen> {
|
|||||||
try {
|
try {
|
||||||
final sn = context.read<SnNetworkProvider>();
|
final sn = context.read<SnNetworkProvider>();
|
||||||
await sn.client.delete(
|
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;
|
if (!mounted) return;
|
||||||
Navigator.pop(context, false);
|
Navigator.pop(context, false);
|
||||||
|
|||||||
@@ -95,6 +95,10 @@ class _ChatManageScreenState extends State<ChatManageScreen> {
|
|||||||
'description': _descriptionController.text,
|
'description': _descriptionController.text,
|
||||||
'is_public': _isPublic,
|
'is_public': _isPublic,
|
||||||
'is_community': _isCommunity,
|
'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 {
|
try {
|
||||||
@@ -171,7 +175,6 @@ class _ChatManageScreenState extends State<ChatManageScreen> {
|
|||||||
items: [
|
items: [
|
||||||
...(_realms?.map(
|
...(_realms?.map(
|
||||||
(SnRealm item) => DropdownMenuItem<SnRealm>(
|
(SnRealm item) => DropdownMenuItem<SnRealm>(
|
||||||
enabled: _editingChannel == null || _editingChannel?.realmId == item.id,
|
|
||||||
value: item,
|
value: item,
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
@@ -204,7 +207,6 @@ class _ChatManageScreenState extends State<ChatManageScreen> {
|
|||||||
) ??
|
) ??
|
||||||
[]),
|
[]),
|
||||||
DropdownMenuItem<SnRealm>(
|
DropdownMenuItem<SnRealm>(
|
||||||
enabled: _editingChannel == null,
|
|
||||||
value: null,
|
value: null,
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ class _ChatRoomScreenState extends State<ChatRoomScreen> {
|
|||||||
|
|
||||||
StreamSubscription? _wsSubscription;
|
StreamSubscription? _wsSubscription;
|
||||||
|
|
||||||
|
// TODO fetch user identity and ask them to join the channel or not
|
||||||
Future<void> _fetchChannel() async {
|
Future<void> _fetchChannel() async {
|
||||||
setState(() => _isBusy = true);
|
setState(() => _isBusy = true);
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:dropdown_button2/dropdown_button2.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_expandable_fab/flutter_expandable_fab.dart';
|
import 'package:flutter_expandable_fab/flutter_expandable_fab.dart';
|
||||||
@@ -8,7 +9,10 @@ import 'package:provider/provider.dart';
|
|||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
import 'package:surface/providers/post.dart';
|
import 'package:surface/providers/post.dart';
|
||||||
import 'package:surface/providers/sn_network.dart';
|
import 'package:surface/providers/sn_network.dart';
|
||||||
|
import 'package:surface/providers/sn_realm.dart';
|
||||||
import 'package:surface/types/post.dart';
|
import 'package:surface/types/post.dart';
|
||||||
|
import 'package:surface/types/realm.dart';
|
||||||
|
import 'package:surface/widgets/account/account_image.dart';
|
||||||
import 'package:surface/widgets/app_bar_leading.dart';
|
import 'package:surface/widgets/app_bar_leading.dart';
|
||||||
import 'package:surface/widgets/dialog.dart';
|
import 'package:surface/widgets/dialog.dart';
|
||||||
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
||||||
@@ -35,61 +39,49 @@ class ExploreScreen extends StatefulWidget {
|
|||||||
State<ExploreScreen> createState() => _ExploreScreenState();
|
State<ExploreScreen> createState() => _ExploreScreenState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ExploreScreenState extends State<ExploreScreen> {
|
// You know what? I'm not going to make this a global variable.
|
||||||
|
// Cuz the global key make the selected category not update to child widget when the category is changed.
|
||||||
|
SnPostCategory? _selectedCategory;
|
||||||
|
|
||||||
|
class _ExploreScreenState extends State<ExploreScreen> with SingleTickerProviderStateMixin {
|
||||||
|
late final TabController _tabController = TabController(length: 4, vsync: this);
|
||||||
|
|
||||||
final _fabKey = GlobalKey<ExpandableFabState>();
|
final _fabKey = GlobalKey<ExpandableFabState>();
|
||||||
|
final _listKeys = List.generate(4, (_) => GlobalKey<_PostListWidgetState>());
|
||||||
|
|
||||||
bool _isBusy = true;
|
|
||||||
|
|
||||||
final List<SnPost> _posts = List.empty(growable: true);
|
|
||||||
final List<SnPostCategory> _categories = List.empty(growable: true);
|
final List<SnPostCategory> _categories = List.empty(growable: true);
|
||||||
int? _postCount;
|
|
||||||
|
|
||||||
String? _selectedCategory;
|
|
||||||
|
|
||||||
Future<void> _fetchCategories() async {
|
Future<void> _fetchCategories() async {
|
||||||
_categories.clear();
|
_categories.clear();
|
||||||
try {
|
try {
|
||||||
final sn = context.read<SnNetworkProvider>();
|
final sn = context.read<SnNetworkProvider>();
|
||||||
final resp = await sn.client.get('/cgi/co/categories?take=100');
|
final resp = await sn.client.get('/cgi/co/categories?take=100');
|
||||||
_categories.addAll(resp.data.map((e) => SnPostCategory.fromJson(e)).cast<SnPostCategory>() ?? []);
|
setState(() {
|
||||||
|
_categories.addAll(resp.data.map((e) => SnPostCategory.fromJson(e)).cast<SnPostCategory>() ?? []);
|
||||||
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (!mounted) return;
|
if (mounted) context.showErrorDialog(err);
|
||||||
context.showErrorDialog(err);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _fetchPosts() async {
|
void _clearFilter() {
|
||||||
if (_postCount != null && _posts.length >= _postCount!) return;
|
_selectedCategory = null;
|
||||||
|
|
||||||
setState(() => _isBusy = true);
|
|
||||||
|
|
||||||
final pt = context.read<SnPostContentProvider>();
|
|
||||||
final result = await pt.listPosts(
|
|
||||||
take: 10,
|
|
||||||
offset: _posts.length,
|
|
||||||
categories: _selectedCategory != null ? [_selectedCategory!] : null,
|
|
||||||
);
|
|
||||||
final out = result.$1;
|
|
||||||
|
|
||||||
if (!mounted) return;
|
|
||||||
|
|
||||||
_postCount = result.$2;
|
|
||||||
_posts.addAll(out);
|
|
||||||
|
|
||||||
if (mounted) setState(() => _isBusy = false);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _refreshPosts() {
|
|
||||||
_postCount = null;
|
|
||||||
_posts.clear();
|
|
||||||
return _fetchPosts();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
|
||||||
_fetchPosts();
|
|
||||||
_fetchCategories();
|
_fetchCategories();
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_tabController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> refreshPosts() async {
|
||||||
|
await _listKeys[_tabController.index].currentState?.refreshPosts();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -131,7 +123,7 @@ class _ExploreScreenState extends State<ExploreScreen> {
|
|||||||
'mode': 'stories',
|
'mode': 'stories',
|
||||||
}).then((value) {
|
}).then((value) {
|
||||||
if (value == true) {
|
if (value == true) {
|
||||||
_refreshPosts();
|
refreshPosts();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
_fabKey.currentState!.toggle();
|
_fabKey.currentState!.toggle();
|
||||||
@@ -152,7 +144,7 @@ class _ExploreScreenState extends State<ExploreScreen> {
|
|||||||
'mode': 'articles',
|
'mode': 'articles',
|
||||||
}).then((value) {
|
}).then((value) {
|
||||||
if (value == true) {
|
if (value == true) {
|
||||||
_refreshPosts();
|
refreshPosts();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
_fabKey.currentState!.toggle();
|
_fabKey.currentState!.toggle();
|
||||||
@@ -173,7 +165,7 @@ class _ExploreScreenState extends State<ExploreScreen> {
|
|||||||
'mode': 'questions',
|
'mode': 'questions',
|
||||||
}).then((value) {
|
}).then((value) {
|
||||||
if (value == true) {
|
if (value == true) {
|
||||||
_refreshPosts();
|
refreshPosts();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
_fabKey.currentState!.toggle();
|
_fabKey.currentState!.toggle();
|
||||||
@@ -194,7 +186,7 @@ class _ExploreScreenState extends State<ExploreScreen> {
|
|||||||
'mode': 'videos',
|
'mode': 'videos',
|
||||||
}).then((value) {
|
}).then((value) {
|
||||||
if (value == true) {
|
if (value == true) {
|
||||||
_refreshPosts();
|
refreshPosts();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
_fabKey.currentState!.toggle();
|
_fabKey.currentState!.toggle();
|
||||||
@@ -205,74 +197,137 @@ class _ExploreScreenState extends State<ExploreScreen> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: RefreshIndicator(
|
body: NestedScrollView(
|
||||||
displacement: 40 + MediaQuery.of(context).padding.top,
|
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
|
||||||
onRefresh: () => _refreshPosts(),
|
return [
|
||||||
child: CustomScrollView(
|
SliverOverlapAbsorber(
|
||||||
slivers: [
|
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
||||||
SliverAppBar(
|
sliver: SliverAppBar(
|
||||||
leading: AutoAppBarLeading(),
|
leading: AutoAppBarLeading(),
|
||||||
title: Text('screenExplore').tr(),
|
title: Text('screenExplore').tr(),
|
||||||
floating: true,
|
floating: true,
|
||||||
snap: true,
|
snap: true,
|
||||||
actions: [
|
actions: [
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Symbols.search),
|
icon: const Icon(Symbols.category),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
GoRouter.of(context).pushNamed('postSearch');
|
showModalBottomSheet(
|
||||||
},
|
context: context,
|
||||||
),
|
builder: (context) => _PostCategoryPickerPopup(
|
||||||
const Gap(8),
|
categories: _categories,
|
||||||
],
|
selected: _selectedCategory,
|
||||||
bottom: PreferredSize(
|
),
|
||||||
preferredSize: const Size.fromHeight(50),
|
).then((value) {
|
||||||
child: SizedBox(
|
if (value != null && context.mounted) {
|
||||||
height: 50,
|
_selectedCategory = value == false ? null : value;
|
||||||
child: SingleChildScrollView(
|
refreshPosts();
|
||||||
scrollDirection: Axis.horizontal,
|
}
|
||||||
padding: const EdgeInsets.only(left: 8, right: 8, bottom: 12),
|
});
|
||||||
child: Row(
|
},
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: _categories.map((ele) {
|
|
||||||
return StyledWidget(ChoiceChip(
|
|
||||||
avatar: Icon(kCategoryIcons[ele.alias] ?? Symbols.question_mark),
|
|
||||||
label: Text(
|
|
||||||
'postCategory${ele.alias.capitalize()}'.trExists()
|
|
||||||
? 'postCategory${ele.alias.capitalize()}'.tr()
|
|
||||||
: ele.name,
|
|
||||||
),
|
|
||||||
selected: _selectedCategory == ele.alias,
|
|
||||||
onSelected: (value) {
|
|
||||||
_selectedCategory = value ? ele.alias : null;
|
|
||||||
_refreshPosts();
|
|
||||||
},
|
|
||||||
)).padding(horizontal: 4);
|
|
||||||
}).toList(),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Symbols.search),
|
||||||
|
onPressed: () {
|
||||||
|
GoRouter.of(context).pushNamed('postSearch');
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const Gap(8),
|
||||||
|
],
|
||||||
|
bottom: TabBar(
|
||||||
|
controller: _tabController,
|
||||||
|
tabs: [
|
||||||
|
Tab(
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(Symbols.globe, size: 20, color: Theme.of(context).appBarTheme.foregroundColor),
|
||||||
|
const Gap(8),
|
||||||
|
Flexible(
|
||||||
|
child: Text(
|
||||||
|
'postChannelGlobal',
|
||||||
|
maxLines: 1,
|
||||||
|
).tr().textColor(Theme.of(context).appBarTheme.foregroundColor),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Tab(
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(Symbols.group, size: 20, color: Theme.of(context).appBarTheme.foregroundColor),
|
||||||
|
const Gap(8),
|
||||||
|
Flexible(
|
||||||
|
child: Text(
|
||||||
|
'postChannelFriends',
|
||||||
|
maxLines: 1,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
).tr().textColor(Theme.of(context).appBarTheme.foregroundColor),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Tab(
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(Symbols.subscriptions, size: 20, color: Theme.of(context).appBarTheme.foregroundColor),
|
||||||
|
const Gap(8),
|
||||||
|
Flexible(
|
||||||
|
child: Text(
|
||||||
|
'postChannelFollowing',
|
||||||
|
maxLines: 1,
|
||||||
|
).tr().textColor(Theme.of(context).appBarTheme.foregroundColor),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Tab(
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(Symbols.workspaces, size: 20, color: Theme.of(context).appBarTheme.foregroundColor),
|
||||||
|
const Gap(8),
|
||||||
|
Flexible(
|
||||||
|
child: Text(
|
||||||
|
'postChannelRealm',
|
||||||
|
maxLines: 1,
|
||||||
|
).tr().textColor(Theme.of(context).appBarTheme.foregroundColor),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SliverGap(12),
|
];
|
||||||
SliverInfiniteList(
|
},
|
||||||
itemCount: _posts.length,
|
body: TabBarView(
|
||||||
isLoading: _isBusy,
|
controller: _tabController,
|
||||||
centerLoading: true,
|
children: [
|
||||||
hasReachedMax: _postCount != null && _posts.length >= _postCount!,
|
_PostListWidget(
|
||||||
onFetchData: _fetchPosts,
|
key: _listKeys[0],
|
||||||
itemBuilder: (context, idx) {
|
onClearFilter: _clearFilter,
|
||||||
return OpenablePostItem(
|
),
|
||||||
data: _posts[idx],
|
_PostListWidget(
|
||||||
maxWidth: 640,
|
key: _listKeys[1],
|
||||||
onChanged: (data) {
|
channel: 'friends',
|
||||||
setState(() => _posts[idx] = data);
|
onClearFilter: _clearFilter,
|
||||||
},
|
),
|
||||||
onDeleted: () {
|
_PostListWidget(
|
||||||
_refreshPosts();
|
key: _listKeys[2],
|
||||||
},
|
channel: 'following',
|
||||||
);
|
onClearFilter: _clearFilter,
|
||||||
},
|
),
|
||||||
separatorBuilder: (_, __) => const Gap(8),
|
_PostListWidget(
|
||||||
|
key: _listKeys[3],
|
||||||
|
withRealm: true,
|
||||||
|
onClearFilter: _clearFilter,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -280,3 +335,246 @@ 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),
|
||||||
|
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/providers/sn_network.dart';
|
||||||
import 'package:surface/types/attachment.dart';
|
import 'package:surface/types/attachment.dart';
|
||||||
import 'package:surface/types/post.dart';
|
import 'package:surface/types/post.dart';
|
||||||
|
import 'package:surface/types/realm.dart';
|
||||||
import 'package:surface/widgets/account/account_image.dart';
|
import 'package:surface/widgets/account/account_image.dart';
|
||||||
import 'package:surface/widgets/attachment/attachment_input.dart';
|
import 'package:surface/widgets/attachment/attachment_input.dart';
|
||||||
import 'package:surface/widgets/attachment/attachment_item.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:surface/widgets/post/post_poll_editor.dart';
|
||||||
import 'package:uuid/uuid.dart';
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
|
import '../../providers/sn_realm.dart';
|
||||||
|
|
||||||
class PostEditorExtra {
|
class PostEditorExtra {
|
||||||
final String? text;
|
final String? text;
|
||||||
final String? title;
|
final String? title;
|
||||||
@@ -79,6 +82,7 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
bool get _isLoading => _isFetching || _writeController.isLoading;
|
bool get _isLoading => _isFetching || _writeController.isLoading;
|
||||||
|
|
||||||
List<SnPublisher>? _publishers;
|
List<SnPublisher>? _publishers;
|
||||||
|
List<SnRealm>? _realms;
|
||||||
|
|
||||||
Future<void> _fetchPublishers() async {
|
Future<void> _fetchPublishers() async {
|
||||||
setState(() => _isFetching = true);
|
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() {
|
void _updateMeta() {
|
||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
context: context,
|
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 {
|
void _showPollEditorDialog() async {
|
||||||
final poll = await showDialog<dynamic>(
|
final poll = await showDialog<dynamic>(
|
||||||
context: context,
|
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
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_writeController.dispose();
|
_writeController.dispose();
|
||||||
@@ -180,6 +221,7 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
} else {
|
} else {
|
||||||
_writeController.setMode(widget.mode);
|
_writeController.setMode(widget.mode);
|
||||||
}
|
}
|
||||||
|
_fetchRealms();
|
||||||
_fetchPublishers();
|
_fetchPublishers();
|
||||||
_writeController.fetchRelatedPost(
|
_writeController.fetchRelatedPost(
|
||||||
context,
|
context,
|
||||||
@@ -321,18 +363,22 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
'stories' => _PostStoryEditor(
|
'stories' => _PostStoryEditor(
|
||||||
controller: _writeController,
|
controller: _writeController,
|
||||||
onTapPublisher: _showPublisherPopup,
|
onTapPublisher: _showPublisherPopup,
|
||||||
|
onTapRealm: _showRealmPopup,
|
||||||
),
|
),
|
||||||
'articles' => _PostArticleEditor(
|
'articles' => _PostArticleEditor(
|
||||||
controller: _writeController,
|
controller: _writeController,
|
||||||
onTapPublisher: _showPublisherPopup,
|
onTapPublisher: _showPublisherPopup,
|
||||||
|
onTapRealm: _showRealmPopup,
|
||||||
),
|
),
|
||||||
'questions' => _PostQuestionEditor(
|
'questions' => _PostQuestionEditor(
|
||||||
controller: _writeController,
|
controller: _writeController,
|
||||||
onTapPublisher: _showPublisherPopup,
|
onTapPublisher: _showPublisherPopup,
|
||||||
|
onTapRealm: _showRealmPopup,
|
||||||
),
|
),
|
||||||
'videos' => _PostVideoEditor(
|
'videos' => _PostVideoEditor(
|
||||||
controller: _writeController,
|
controller: _writeController,
|
||||||
onTapPublisher: _showPublisherPopup,
|
onTapPublisher: _showPublisherPopup,
|
||||||
|
onTapRealm: _showRealmPopup,
|
||||||
),
|
),
|
||||||
_ => const Placeholder(),
|
_ => const Placeholder(),
|
||||||
})
|
})
|
||||||
@@ -344,15 +390,11 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
left: 0,
|
left: 0,
|
||||||
right: 0,
|
right: 0,
|
||||||
child: PostMediaPendingList(
|
child: PostMediaPendingList(
|
||||||
thumbnail: _writeController.thumbnail,
|
|
||||||
attachments: _writeController.attachments,
|
attachments: _writeController.attachments,
|
||||||
isBusy: _writeController.isBusy,
|
isBusy: _writeController.isBusy,
|
||||||
onUpload: (int idx) async {
|
onUpload: (int idx) async {
|
||||||
await _writeController.uploadSingleAttachment(context, idx);
|
await _writeController.uploadSingleAttachment(context, idx);
|
||||||
},
|
},
|
||||||
onPostSetThumbnail: (int? idx) {
|
|
||||||
_writeController.setThumbnail(idx);
|
|
||||||
},
|
|
||||||
onInsertLink: (int idx) async {
|
onInsertLink: (int idx) async {
|
||||||
_writeController.contentController.text +=
|
_writeController.contentController.text +=
|
||||||
'\n';
|
'\n';
|
||||||
@@ -453,6 +495,22 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
_showPollEditorDialog();
|
_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 {
|
class _PostStoryEditor extends StatelessWidget {
|
||||||
final PostWriteController controller;
|
final PostWriteController controller;
|
||||||
final Function? onTapPublisher;
|
final Function? onTapPublisher;
|
||||||
|
final Function? onTapRealm;
|
||||||
|
|
||||||
const _PostStoryEditor({required this.controller, this.onTapPublisher});
|
const _PostStoryEditor({required this.controller, this.onTapPublisher, this.onTapRealm});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -563,17 +675,36 @@ class _PostStoryEditor extends StatelessWidget {
|
|||||||
child: Row(
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Material(
|
Column(
|
||||||
elevation: 2,
|
children: [
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(24)),
|
Material(
|
||||||
child: GestureDetector(
|
elevation: 2,
|
||||||
onTap: () {
|
borderRadius: const BorderRadius.all(Radius.circular(24)),
|
||||||
onTapPublisher?.call();
|
child: GestureDetector(
|
||||||
},
|
onTap: () {
|
||||||
child: AccountImage(
|
onTapPublisher?.call();
|
||||||
content: controller.publisher?.avatar,
|
},
|
||||||
|
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(
|
Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
@@ -616,8 +747,9 @@ class _PostStoryEditor extends StatelessWidget {
|
|||||||
class _PostArticleEditor extends StatelessWidget {
|
class _PostArticleEditor extends StatelessWidget {
|
||||||
final PostWriteController controller;
|
final PostWriteController controller;
|
||||||
final Function? onTapPublisher;
|
final Function? onTapPublisher;
|
||||||
|
final Function? onTapRealm;
|
||||||
|
|
||||||
const _PostArticleEditor({required this.controller, this.onTapPublisher});
|
const _PostArticleEditor({required this.controller, this.onTapPublisher, this.onTapRealm});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
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),
|
).padding(horizontal: 12, vertical: 8),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
@@ -668,7 +815,24 @@ class _PostArticleEditor extends StatelessWidget {
|
|||||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
contentInsertionConfiguration: controller.contentInsertionConfiguration,
|
contentInsertionConfiguration: controller.contentInsertionConfiguration,
|
||||||
).padding(horizontal: 16),
|
).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)) {
|
if (ResponsiveBreakpoints.of(context).largerThan(MOBILE)) {
|
||||||
@@ -740,8 +904,9 @@ class _PostArticleEditor extends StatelessWidget {
|
|||||||
class _PostQuestionEditor extends StatelessWidget {
|
class _PostQuestionEditor extends StatelessWidget {
|
||||||
final PostWriteController controller;
|
final PostWriteController controller;
|
||||||
final Function? onTapPublisher;
|
final Function? onTapPublisher;
|
||||||
|
final Function? onTapRealm;
|
||||||
|
|
||||||
const _PostQuestionEditor({required this.controller, this.onTapPublisher});
|
const _PostQuestionEditor({required this.controller, this.onTapPublisher, this.onTapRealm});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -751,17 +916,36 @@ class _PostQuestionEditor extends StatelessWidget {
|
|||||||
child: Row(
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Material(
|
Column(
|
||||||
elevation: 1,
|
children: [
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(24)),
|
Material(
|
||||||
child: GestureDetector(
|
elevation: 2,
|
||||||
onTap: () {
|
borderRadius: const BorderRadius.all(Radius.circular(24)),
|
||||||
onTapPublisher?.call();
|
child: GestureDetector(
|
||||||
},
|
onTap: () {
|
||||||
child: AccountImage(
|
onTapPublisher?.call();
|
||||||
content: controller.publisher?.avatar,
|
},
|
||||||
|
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(
|
Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
@@ -815,8 +999,9 @@ class _PostQuestionEditor extends StatelessWidget {
|
|||||||
class _PostVideoEditor extends StatelessWidget {
|
class _PostVideoEditor extends StatelessWidget {
|
||||||
final PostWriteController controller;
|
final PostWriteController controller;
|
||||||
final Function? onTapPublisher;
|
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 {
|
void _selectVideo(BuildContext context) async {
|
||||||
final video = await showDialog<SnAttachment?>(
|
final video = await showDialog<SnAttachment?>(
|
||||||
@@ -904,28 +1089,36 @@ class _PostVideoEditor extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
Material(
|
Column(
|
||||||
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
children: [
|
||||||
child: InkWell(
|
Material(
|
||||||
child: Row(
|
elevation: 2,
|
||||||
children: [
|
borderRadius: const BorderRadius.all(Radius.circular(24)),
|
||||||
AccountImage(content: controller.publisher?.avatar, radius: 20),
|
child: GestureDetector(
|
||||||
const Gap(8),
|
onTap: () {
|
||||||
Expanded(
|
onTapPublisher?.call();
|
||||||
child: Column(
|
},
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
child: AccountImage(
|
||||||
children: [
|
content: controller.publisher?.avatar,
|
||||||
Text(controller.publisher?.nick ?? 'loading'.tr()).bold(),
|
|
||||||
Text('@${controller.publisher?.name}'),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
).padding(horizontal: 12, vertical: 8),
|
),
|
||||||
onTap: () {
|
const Gap(11),
|
||||||
onTapPublisher?.call();
|
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),
|
const Gap(16),
|
||||||
TextField(
|
TextField(
|
||||||
|
|||||||
@@ -4,17 +4,16 @@ import 'package:gap/gap.dart';
|
|||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:surface/providers/config.dart';
|
||||||
import 'package:surface/providers/sn_network.dart';
|
import 'package:surface/providers/sn_network.dart';
|
||||||
import 'package:surface/providers/userinfo.dart';
|
import 'package:surface/providers/userinfo.dart';
|
||||||
import 'package:surface/types/realm.dart';
|
import 'package:surface/types/realm.dart';
|
||||||
import 'package:surface/widgets/account/account_image.dart';
|
|
||||||
import 'package:surface/widgets/app_bar_leading.dart';
|
import 'package:surface/widgets/app_bar_leading.dart';
|
||||||
import 'package:surface/widgets/dialog.dart';
|
import 'package:surface/widgets/dialog.dart';
|
||||||
import 'package:surface/widgets/loading_indicator.dart';
|
import 'package:surface/widgets/loading_indicator.dart';
|
||||||
import 'package:surface/widgets/navigation/app_scaffold.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/unauthorized_hint.dart';
|
||||||
import 'package:surface/widgets/universal_image.dart';
|
|
||||||
|
|
||||||
class RealmScreen extends StatefulWidget {
|
class RealmScreen extends StatefulWidget {
|
||||||
const RealmScreen({super.key});
|
const RealmScreen({super.key});
|
||||||
@@ -75,12 +74,12 @@ class _RealmScreenState extends State<RealmScreen> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
_isCompactView = context.read<ConfigProvider>().realmCompactView;
|
||||||
_fetchRealms();
|
_fetchRealms();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final sn = context.read<SnNetworkProvider>();
|
|
||||||
final ua = context.read<UserProvider>();
|
final ua = context.read<UserProvider>();
|
||||||
|
|
||||||
if (!ua.isAuthorized) {
|
if (!ua.isAuthorized) {
|
||||||
@@ -110,6 +109,7 @@ class _RealmScreenState extends State<RealmScreen> {
|
|||||||
icon: !_isCompactView ? const Icon(Symbols.view_list) : const Icon(Symbols.view_module),
|
icon: !_isCompactView ? const Icon(Symbols.view_list) : const Icon(Symbols.view_module),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
setState(() => _isCompactView = !_isCompactView);
|
setState(() => _isCompactView = !_isCompactView);
|
||||||
|
context.read<ConfigProvider>().realmCompactView = _isCompactView;
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
@@ -134,129 +134,46 @@ class _RealmScreenState extends State<RealmScreen> {
|
|||||||
itemCount: _realms?.length ?? 0,
|
itemCount: _realms?.length ?? 0,
|
||||||
itemBuilder: (context, idx) {
|
itemBuilder: (context, idx) {
|
||||||
final realm = _realms![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(
|
return RealmItemWidget(
|
||||||
constraints: BoxConstraints(maxWidth: 640),
|
showPopularity: false,
|
||||||
child: Card(
|
item: realm,
|
||||||
margin: const EdgeInsets.all(12),
|
isListView: _isCompactView,
|
||||||
child: InkWell(
|
actionListView: [
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
PopupMenuItem(
|
||||||
child: Column(
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
children: [
|
||||||
AspectRatio(
|
const Icon(Symbols.edit),
|
||||||
aspectRatio: 16 / 7,
|
const Gap(16),
|
||||||
child: Stack(
|
Text('edit').tr(),
|
||||||
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: () {
|
onTap: () {
|
||||||
GoRouter.of(context).pushNamed(
|
GoRouter.of(context).pushNamed(
|
||||||
'realmDetail',
|
'realmManage',
|
||||||
pathParameters: {'alias': realm.alias},
|
queryParameters: {'editing': realm.alias},
|
||||||
).then((value) {
|
).then((value) {
|
||||||
if (value == true) {
|
if (value != null) {
|
||||||
_fetchRealms();
|
_fetchRealms();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
PopupMenuItem(
|
||||||
).center();
|
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:material_symbols_icons/symbols.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
import 'package:surface/providers/post.dart';
|
||||||
import 'package:surface/providers/sn_network.dart';
|
import 'package:surface/providers/sn_network.dart';
|
||||||
import 'package:surface/providers/user_directory.dart';
|
import 'package:surface/providers/user_directory.dart';
|
||||||
import 'package:surface/providers/userinfo.dart';
|
import 'package:surface/providers/userinfo.dart';
|
||||||
import 'package:surface/types/account.dart';
|
import 'package:surface/types/account.dart';
|
||||||
|
import 'package:surface/types/chat.dart';
|
||||||
import 'package:surface/types/post.dart';
|
import 'package:surface/types/post.dart';
|
||||||
import 'package:surface/types/realm.dart';
|
import 'package:surface/types/realm.dart';
|
||||||
import 'package:surface/widgets/account/account_image.dart';
|
import 'package:surface/widgets/account/account_image.dart';
|
||||||
import 'package:surface/widgets/account/account_select.dart';
|
import 'package:surface/widgets/account/account_select.dart';
|
||||||
import 'package:surface/widgets/dialog.dart';
|
import 'package:surface/widgets/dialog.dart';
|
||||||
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
||||||
|
import 'package:surface/widgets/post/post_item.dart';
|
||||||
import 'package:very_good_infinite_list/very_good_infinite_list.dart';
|
import 'package:very_good_infinite_list/very_good_infinite_list.dart';
|
||||||
|
|
||||||
class RealmDetailScreen extends StatefulWidget {
|
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
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_fetchRealm().then((_) {
|
_fetchRealm().then((_) {
|
||||||
_fetchPublishers();
|
_fetchPublishers();
|
||||||
|
_fetchChannels();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return DefaultTabController(
|
return DefaultTabController(
|
||||||
length: 3,
|
length: 4,
|
||||||
child: AppScaffold(
|
child: AppScaffold(
|
||||||
body: NestedScrollView(
|
body: NestedScrollView(
|
||||||
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
|
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
|
||||||
@@ -83,6 +104,7 @@ class _RealmDetailScreenState extends State<RealmDetailScreen> {
|
|||||||
bottom: TabBar(
|
bottom: TabBar(
|
||||||
tabs: [
|
tabs: [
|
||||||
Tab(icon: Icon(Symbols.home, color: Theme.of(context).appBarTheme.foregroundColor)),
|
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.group, color: Theme.of(context).appBarTheme.foregroundColor)),
|
||||||
Tab(icon: Icon(Symbols.settings, 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(
|
body: TabBarView(
|
||||||
children: [
|
children: [
|
||||||
_RealmDetailHomeWidget(realm: _realm, publishers: _publishers),
|
_RealmDetailHomeWidget(realm: _realm, publishers: _publishers, channels: _channels),
|
||||||
|
_RealmPostListWidget(realm: _realm),
|
||||||
_RealmMemberListWidget(realm: _realm),
|
_RealmMemberListWidget(realm: _realm),
|
||||||
_RealmSettingsWidget(
|
_RealmSettingsWidget(
|
||||||
realm: _realm,
|
realm: _realm,
|
||||||
@@ -112,8 +135,9 @@ class _RealmDetailScreenState extends State<RealmDetailScreen> {
|
|||||||
class _RealmDetailHomeWidget extends StatelessWidget {
|
class _RealmDetailHomeWidget extends StatelessWidget {
|
||||||
final SnRealm? realm;
|
final SnRealm? realm;
|
||||||
final List<SnPublisher>? publishers;
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -135,30 +159,76 @@ class _RealmDetailHomeWidget extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
).padding(horizontal: 24),
|
).padding(horizontal: 24),
|
||||||
const Gap(16),
|
const Gap(16),
|
||||||
const Divider(),
|
const Divider(height: 1),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: ListView.builder(
|
child: CustomScrollView(
|
||||||
padding: EdgeInsets.zero,
|
slivers: [
|
||||||
itemCount: publishers?.length ?? 0,
|
if (publishers?.isNotEmpty ?? false)
|
||||||
itemBuilder: (context, idx) {
|
SliverToBoxAdapter(
|
||||||
final ele = publishers![idx];
|
child: Container(
|
||||||
return ListTile(
|
width: double.infinity,
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 20),
|
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||||
leading: AccountImage(
|
child: Text('realmCommunityPublishersHint'.tr(), style: Theme.of(context).textTheme.bodyMedium)
|
||||||
content: ele.avatar,
|
.padding(horizontal: 24, vertical: 8),
|
||||||
fallbackWidget: const Icon(Symbols.group, size: 24),
|
),
|
||||||
),
|
),
|
||||||
title: Text(ele.nick),
|
SliverList.builder(
|
||||||
subtitle: Text('@${ele.name}'),
|
itemCount: publishers?.length ?? 0,
|
||||||
trailing: const Icon(Symbols.chevron_right),
|
itemBuilder: (context, idx) {
|
||||||
onTap: () {
|
final ele = publishers![idx];
|
||||||
GoRouter.of(context).pushNamed(
|
return ListTile(
|
||||||
'postPublisher',
|
contentPadding: const EdgeInsets.symmetric(horizontal: 20),
|
||||||
pathParameters: {'name': ele.name},
|
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 {
|
class _RealmMemberListWidget extends StatefulWidget {
|
||||||
final SnRealm? realm;
|
final SnRealm? realm;
|
||||||
|
|
||||||
@@ -365,7 +501,7 @@ class _RealmSettingsWidgetState extends State<_RealmSettingsWidget> {
|
|||||||
final sn = context.read<SnNetworkProvider>();
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
|
||||||
try {
|
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;
|
if (!mounted) return;
|
||||||
Navigator.pop(context, true);
|
Navigator.pop(context, true);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import 'package:gap/gap.dart';
|
|||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
import 'package:surface/providers/config.dart';
|
||||||
import 'package:surface/providers/sn_network.dart';
|
import 'package:surface/providers/sn_network.dart';
|
||||||
import 'package:surface/providers/userinfo.dart';
|
import 'package:surface/providers/userinfo.dart';
|
||||||
import 'package:surface/types/chat.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/dialog.dart';
|
||||||
import 'package:surface/widgets/loading_indicator.dart';
|
import 'package:surface/widgets/loading_indicator.dart';
|
||||||
import 'package:surface/widgets/navigation/app_scaffold.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 {
|
class RealmDiscoveryScreen extends StatefulWidget {
|
||||||
const RealmDiscoveryScreen({super.key});
|
const RealmDiscoveryScreen({super.key});
|
||||||
@@ -24,6 +25,7 @@ class RealmDiscoveryScreen extends StatefulWidget {
|
|||||||
class _RealmDiscoveryScreenState extends State<RealmDiscoveryScreen> {
|
class _RealmDiscoveryScreenState extends State<RealmDiscoveryScreen> {
|
||||||
List<SnRealm>? _realms;
|
List<SnRealm>? _realms;
|
||||||
bool _isBusy = false;
|
bool _isBusy = false;
|
||||||
|
bool _isCompactView = false;
|
||||||
|
|
||||||
Future<void> _fetchRealms() async {
|
Future<void> _fetchRealms() async {
|
||||||
try {
|
try {
|
||||||
@@ -44,16 +46,25 @@ class _RealmDiscoveryScreenState extends State<RealmDiscoveryScreen> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
_isCompactView = context.read<ConfigProvider>().realmCompactView;
|
||||||
_fetchRealms();
|
_fetchRealms();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final sn = context.read<SnNetworkProvider>();
|
|
||||||
|
|
||||||
return AppScaffold(
|
return AppScaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text('screenRealmDiscovery').tr(),
|
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(
|
body: Column(
|
||||||
children: [
|
children: [
|
||||||
@@ -66,64 +77,16 @@ class _RealmDiscoveryScreenState extends State<RealmDiscoveryScreen> {
|
|||||||
itemCount: _realms?.length ?? 0,
|
itemCount: _realms?.length ?? 0,
|
||||||
itemBuilder: (context, idx) {
|
itemBuilder: (context, idx) {
|
||||||
final realm = _realms![idx];
|
final realm = _realms![idx];
|
||||||
return Container(
|
return RealmItemWidget(
|
||||||
constraints: BoxConstraints(maxWidth: 640),
|
item: realm,
|
||||||
child: Card(
|
isListView: _isCompactView,
|
||||||
margin: const EdgeInsets.all(12),
|
onTap: () {
|
||||||
child: InkWell(
|
showModalBottomSheet(
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
context: context,
|
||||||
child: Column(
|
builder: (context) => _RealmJoinPopup(realm: realm),
|
||||||
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();
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -235,6 +198,8 @@ class _RealmJoinPopupState extends State<_RealmJoinPopup> {
|
|||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
widget.realm.description,
|
widget.realm.description,
|
||||||
|
maxLines: 3,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
style: Theme.of(context).textTheme.bodyMedium,
|
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:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_colorpicker/flutter_colorpicker.dart';
|
import 'package:flutter_colorpicker/flutter_colorpicker.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
import 'package:image_picker/image_picker.dart';
|
import 'package:image_picker/image_picker.dart';
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
@@ -14,6 +16,7 @@ import 'package:provider/provider.dart';
|
|||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
import 'package:surface/providers/config.dart';
|
import 'package:surface/providers/config.dart';
|
||||||
|
import 'package:surface/providers/database.dart';
|
||||||
import 'package:surface/providers/sn_network.dart';
|
import 'package:surface/providers/sn_network.dart';
|
||||||
import 'package:surface/providers/theme.dart';
|
import 'package:surface/providers/theme.dart';
|
||||||
import 'package:surface/theme.dart';
|
import 'package:surface/theme.dart';
|
||||||
@@ -67,6 +70,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final sn = context.read<SnNetworkProvider>();
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
final dt = context.read<DatabaseProvider>();
|
||||||
|
|
||||||
return AppScaffold(
|
return AppScaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
@@ -81,7 +85,11 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
Column(
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text('settingsAppearance').bold().fontSize(17).tr().padding(horizontal: 20, bottom: 4),
|
Text('settingsAppearance')
|
||||||
|
.bold()
|
||||||
|
.fontSize(17)
|
||||||
|
.tr()
|
||||||
|
.padding(horizontal: 20, bottom: 4),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text('settingsDisplayLanguage').tr(),
|
title: Text('settingsDisplayLanguage').tr(),
|
||||||
subtitle: Text('settingsDisplayLanguageDescription').tr(),
|
subtitle: Text('settingsDisplayLanguageDescription').tr(),
|
||||||
@@ -91,15 +99,21 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
child: DropdownButton2<Locale?>(
|
child: DropdownButton2<Locale?>(
|
||||||
isExpanded: true,
|
isExpanded: true,
|
||||||
items: [
|
items: [
|
||||||
...EasyLocalization.of(context)!.supportedLocales.mapIndexed((idx, ele) {
|
...EasyLocalization.of(context)!
|
||||||
|
.supportedLocales
|
||||||
|
.mapIndexed((idx, ele) {
|
||||||
return DropdownMenuItem<Locale?>(
|
return DropdownMenuItem<Locale?>(
|
||||||
value: ele,
|
value: ele,
|
||||||
child: Text('${ele.languageCode}-${ele.countryCode}').fontSize(14),
|
child:
|
||||||
|
Text('${ele.languageCode}-${ele.countryCode}')
|
||||||
|
.fontSize(14),
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
DropdownMenuItem<Locale?>(
|
DropdownMenuItem<Locale?>(
|
||||||
value: null,
|
value: null,
|
||||||
child: Text('settingsDisplayLanguageSystem').tr().fontSize(14),
|
child: Text('settingsDisplayLanguageSystem')
|
||||||
|
.tr()
|
||||||
|
.fontSize(14),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
value: EasyLocalization.of(context)!.currentLocale,
|
value: EasyLocalization.of(context)!.currentLocale,
|
||||||
@@ -132,10 +146,12 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
leading: const Icon(Symbols.image),
|
leading: const Icon(Symbols.image),
|
||||||
trailing: const Icon(Symbols.chevron_right),
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
final image = await ImagePicker().pickImage(source: ImageSource.gallery);
|
final image = await ImagePicker()
|
||||||
|
.pickImage(source: ImageSource.gallery);
|
||||||
if (image == null) return;
|
if (image == null) return;
|
||||||
|
|
||||||
await File(image.path).copy('$_docBasepath/app_background_image');
|
await File(image.path)
|
||||||
|
.copy('$_docBasepath/app_background_image');
|
||||||
_prefs.setBool(kAppBackgroundStoreKey, true);
|
_prefs.setBool(kAppBackgroundStoreKey, true);
|
||||||
|
|
||||||
setState(() {});
|
setState(() {});
|
||||||
@@ -143,7 +159,8 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
),
|
),
|
||||||
if (!kIsWeb)
|
if (!kIsWeb)
|
||||||
FutureBuilder<bool>(
|
FutureBuilder<bool>(
|
||||||
future: File('$_docBasepath/app_background_image').exists(),
|
future:
|
||||||
|
File('$_docBasepath/app_background_image').exists(),
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (!snapshot.hasData || !snapshot.data!) {
|
if (!snapshot.hasData || !snapshot.data!) {
|
||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
@@ -151,12 +168,16 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
|
|
||||||
return ListTile(
|
return ListTile(
|
||||||
title: Text('settingsBackgroundImageClear').tr(),
|
title: Text('settingsBackgroundImageClear').tr(),
|
||||||
subtitle: Text('settingsBackgroundImageClearDescription').tr(),
|
subtitle:
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
Text('settingsBackgroundImageClearDescription')
|
||||||
|
.tr(),
|
||||||
|
contentPadding:
|
||||||
|
const EdgeInsets.symmetric(horizontal: 24),
|
||||||
leading: const Icon(Symbols.texture),
|
leading: const Icon(Symbols.texture),
|
||||||
trailing: const Icon(Symbols.chevron_right),
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
File('$_docBasepath/app_background_image').deleteSync();
|
File('$_docBasepath/app_background_image')
|
||||||
|
.deleteSync();
|
||||||
_prefs.remove(kAppBackgroundStoreKey);
|
_prefs.remove(kAppBackgroundStoreKey);
|
||||||
setState(() {});
|
setState(() {});
|
||||||
},
|
},
|
||||||
@@ -186,34 +207,35 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
trailing: const Icon(Symbols.chevron_right),
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
Color pickerColor = Color(_prefs.getInt(kAppColorSchemeStoreKey) ?? Colors.indigo.value);
|
Color pickerColor = Color(
|
||||||
|
_prefs.getInt(kAppColorSchemeStoreKey) ??
|
||||||
|
Colors.indigo.value);
|
||||||
final color = await showDialog<Color?>(
|
final color = await showDialog<Color?>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) =>
|
builder: (context) => AlertDialog(
|
||||||
AlertDialog(
|
content: SingleChildScrollView(
|
||||||
content: SingleChildScrollView(
|
child: ColorPicker(
|
||||||
child: ColorPicker(
|
pickerColor: pickerColor,
|
||||||
pickerColor: pickerColor,
|
onColorChanged: (color) => pickerColor = color,
|
||||||
onColorChanged: (color) => pickerColor = color,
|
enableAlpha: false,
|
||||||
enableAlpha: false,
|
hexInputBar: true,
|
||||||
hexInputBar: true,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
actions: <Widget>[
|
|
||||||
TextButton(
|
|
||||||
child: const Text('dialogDismiss').tr(),
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
TextButton(
|
|
||||||
child: const Text('dialogConfirm').tr(),
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.of(context).pop(pickerColor);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
actions: <Widget>[
|
||||||
|
TextButton(
|
||||||
|
child: const Text('dialogDismiss').tr(),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
child: const Text('dialogConfirm').tr(),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop(pickerColor);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (color == null || !context.mounted) return;
|
if (color == null || !context.mounted) return;
|
||||||
@@ -248,16 +270,17 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
],
|
],
|
||||||
value: _prefs.getInt(kAppColorSchemeStoreKey) == null
|
value: _prefs.getInt(kAppColorSchemeStoreKey) == null
|
||||||
? 1
|
? 1
|
||||||
: kColorSchemes.values
|
: kColorSchemes.values.toList().indexWhere((ele) =>
|
||||||
.toList()
|
ele.value ==
|
||||||
.indexWhere((ele) => ele.value == _prefs.getInt(kAppColorSchemeStoreKey)),
|
_prefs.getInt(kAppColorSchemeStoreKey)),
|
||||||
onChanged: (int? value) {
|
onChanged: (int? value) {
|
||||||
if (value != null && value != -1) {
|
if (value != null && value != -1) {
|
||||||
_prefs.setInt(kAppColorSchemeStoreKey, kColorSchemes.values
|
_prefs.setInt(kAppColorSchemeStoreKey,
|
||||||
.elementAt(value)
|
kColorSchemes.values.elementAt(value).value);
|
||||||
.value);
|
|
||||||
final th = context.read<ThemeProvider>();
|
final th = context.read<ThemeProvider>();
|
||||||
th.reloadTheme(seedColorOverride: kColorSchemes.values.elementAt(value));
|
th.reloadTheme(
|
||||||
|
seedColorOverride:
|
||||||
|
kColorSchemes.values.elementAt(value));
|
||||||
setState(() {});
|
setState(() {});
|
||||||
|
|
||||||
context.showSnackbar('colorSchemeApplied'.tr());
|
context.showSnackbar('colorSchemeApplied'.tr());
|
||||||
@@ -293,7 +316,8 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
CheckboxListTile(
|
CheckboxListTile(
|
||||||
secondary: const Icon(Symbols.left_panel_close),
|
secondary: const Icon(Symbols.left_panel_close),
|
||||||
title: Text('settingsDrawerPreferCollapse').tr(),
|
title: Text('settingsDrawerPreferCollapse').tr(),
|
||||||
subtitle: Text('settingsDrawerPreferCollapseDescription').tr(),
|
subtitle:
|
||||||
|
Text('settingsDrawerPreferCollapseDescription').tr(),
|
||||||
contentPadding: const EdgeInsets.only(left: 24, right: 17),
|
contentPadding: const EdgeInsets.only(left: 24, right: 17),
|
||||||
value: _prefs.getBool(kAppDrawerPreferCollapse) ?? false,
|
value: _prefs.getBool(kAppDrawerPreferCollapse) ?? false,
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
@@ -308,7 +332,11 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
Column(
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text('settingsFeatures').bold().fontSize(17).tr().padding(horizontal: 20, bottom: 4),
|
Text('settingsFeatures')
|
||||||
|
.bold()
|
||||||
|
.fontSize(17)
|
||||||
|
.tr()
|
||||||
|
.padding(horizontal: 20, bottom: 4),
|
||||||
CheckboxListTile(
|
CheckboxListTile(
|
||||||
secondary: const Icon(Symbols.vibration),
|
secondary: const Icon(Symbols.vibration),
|
||||||
contentPadding: const EdgeInsets.only(left: 24, right: 17),
|
contentPadding: const EdgeInsets.only(left: 24, right: 17),
|
||||||
@@ -350,7 +378,11 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
Column(
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text('settingsNetwork').bold().fontSize(17).tr().padding(horizontal: 20, bottom: 4),
|
Text('settingsNetwork')
|
||||||
|
.bold()
|
||||||
|
.fontSize(17)
|
||||||
|
.tr()
|
||||||
|
.padding(horizontal: 20, bottom: 4),
|
||||||
TextField(
|
TextField(
|
||||||
controller: _serverUrlController,
|
controller: _serverUrlController,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
@@ -371,7 +403,8 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
onTapOutside: (_) =>
|
||||||
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
).padding(horizontal: 16, top: 8, bottom: 4),
|
).padding(horizontal: 16, top: 8, bottom: 4),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text('settingsNetworkServerPreset').tr(),
|
title: Text('settingsNetworkServerPreset').tr(),
|
||||||
@@ -383,12 +416,13 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
isExpanded: true,
|
isExpanded: true,
|
||||||
items: [
|
items: [
|
||||||
...kNetworkServerDirectory,
|
...kNetworkServerDirectory,
|
||||||
if (!kNetworkServerDirectory.map((ele) => ele.$2).contains(_serverUrlController.text))
|
if (!kNetworkServerDirectory
|
||||||
|
.map((ele) => ele.$2)
|
||||||
|
.contains(_serverUrlController.text))
|
||||||
('Custom', _serverUrlController.text),
|
('Custom', _serverUrlController.text),
|
||||||
]
|
]
|
||||||
.map(
|
.map(
|
||||||
(item) =>
|
(item) => DropdownMenuItem<String>(
|
||||||
DropdownMenuItem<String>(
|
|
||||||
value: item.$2,
|
value: item.$2,
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.max,
|
mainAxisSize: MainAxisSize.max,
|
||||||
@@ -396,11 +430,12 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(item.$1).fontSize(14),
|
Text(item.$1).fontSize(14),
|
||||||
Text(item.$2, overflow: TextOverflow.ellipsis).fontSize(11)
|
Text(item.$2, overflow: TextOverflow.ellipsis)
|
||||||
|
.fontSize(11)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.toList(),
|
.toList(),
|
||||||
value: _serverUrlController.text,
|
value: _serverUrlController.text,
|
||||||
onChanged: (String? value) {
|
onChanged: (String? value) {
|
||||||
@@ -442,7 +477,11 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
Column(
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text('settingsPerformance').bold().fontSize(17).tr().padding(horizontal: 20, bottom: 4),
|
Text('settingsPerformance')
|
||||||
|
.bold()
|
||||||
|
.fontSize(17)
|
||||||
|
.tr()
|
||||||
|
.padding(horizontal: 20, bottom: 4),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text('settingsImageQuality').tr(),
|
title: Text('settingsImageQuality').tr(),
|
||||||
subtitle: Text('settingsImageQualityDescription').tr(),
|
subtitle: Text('settingsImageQualityDescription').tr(),
|
||||||
@@ -450,21 +489,22 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
leading: const Icon(Symbols.image),
|
leading: const Icon(Symbols.image),
|
||||||
trailing: DropdownButtonHideUnderline(
|
trailing: DropdownButtonHideUnderline(
|
||||||
child: DropdownButton2<FilterQuality>(
|
child: DropdownButton2<FilterQuality>(
|
||||||
value: kImageQualityLevel.values.elementAtOrNull(_prefs.getInt('app_image_quality') ?? 3) ??
|
value: kImageQualityLevel.values.elementAtOrNull(
|
||||||
|
_prefs.getInt('app_image_quality') ?? 3) ??
|
||||||
FilterQuality.high,
|
FilterQuality.high,
|
||||||
isExpanded: true,
|
isExpanded: true,
|
||||||
items: kImageQualityLevel.entries
|
items: kImageQualityLevel.entries
|
||||||
.map(
|
.map(
|
||||||
(item) =>
|
(item) => DropdownMenuItem<FilterQuality>(
|
||||||
DropdownMenuItem<FilterQuality>(
|
|
||||||
value: item.value,
|
value: item.value,
|
||||||
child: Text(item.key).tr().fontSize(14),
|
child: Text(item.key).tr().fontSize(14),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.toList(),
|
.toList(),
|
||||||
onChanged: (FilterQuality? value) {
|
onChanged: (FilterQuality? value) {
|
||||||
if (value == null) return;
|
if (value == null) return;
|
||||||
_prefs.setInt('app_image_quality', kImageQualityLevel.values.toList().indexOf(value));
|
_prefs.setInt('app_image_quality',
|
||||||
|
kImageQualityLevel.values.toList().indexOf(value));
|
||||||
setState(() {});
|
setState(() {});
|
||||||
},
|
},
|
||||||
buttonStyleData: const ButtonStyleData(
|
buttonStyleData: const ButtonStyleData(
|
||||||
@@ -486,7 +526,42 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
Column(
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text('settingsMisc').bold().fontSize(17).tr().padding(horizontal: 20, bottom: 4),
|
Text('settingsMisc')
|
||||||
|
.bold()
|
||||||
|
.fontSize(17)
|
||||||
|
.tr()
|
||||||
|
.padding(horizontal: 20, bottom: 4),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Symbols.database),
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
title: Text('databaseSize').tr(),
|
||||||
|
subtitle: FutureBuilder(
|
||||||
|
future: dt.getDatabaseSize(),
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (!snapshot.hasData || kIsWeb) {
|
||||||
|
return Text('unknown').tr();
|
||||||
|
}
|
||||||
|
return Text(
|
||||||
|
snapshot.data!.formatBytes(),
|
||||||
|
style: GoogleFonts.robotoMono(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Symbols.database_off),
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
title: Text('databaseDelete').tr(),
|
||||||
|
subtitle: Text('databaseDeleteDescription').tr(),
|
||||||
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
|
onTap: () async {
|
||||||
|
await dt.removeDatabase();
|
||||||
|
if (!context.mounted) return;
|
||||||
|
HapticFeedback.heavyImpact();
|
||||||
|
context.showSnackbar('databaseDeleted'.tr());
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text('settingsMiscAbout').tr(),
|
title: Text('settingsMiscAbout').tr(),
|
||||||
subtitle: Text('settingsMiscAboutDescription').tr(),
|
subtitle: Text('settingsMiscAboutDescription').tr(),
|
||||||
|
|||||||
@@ -177,3 +177,14 @@ class SnStickerPack with _$SnStickerPack {
|
|||||||
|
|
||||||
factory SnStickerPack.fromJson(Map<String, Object?> json) => _$SnStickerPackFromJson(json);
|
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 =>
|
_$$SnStickerPackImplCopyWith<_$SnStickerPackImpl> get copyWith =>
|
||||||
throw _privateConstructorUsedError;
|
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(),
|
'stickers': instance.stickers?.map((e) => e.toJson()).toList(),
|
||||||
'account_id': instance.accountId,
|
'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,6 +1,7 @@
|
|||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
import 'package:surface/types/attachment.dart';
|
import 'package:surface/types/attachment.dart';
|
||||||
import 'package:surface/types/poll.dart';
|
import 'package:surface/types/poll.dart';
|
||||||
|
import 'package:surface/types/realm.dart';
|
||||||
|
|
||||||
part 'post.freezed.dart';
|
part 'post.freezed.dart';
|
||||||
part 'post.g.dart';
|
part 'post.g.dart';
|
||||||
@@ -24,6 +25,7 @@ class SnPost with _$SnPost {
|
|||||||
required List<SnPost>? replies,
|
required List<SnPost>? replies,
|
||||||
required int? replyId,
|
required int? replyId,
|
||||||
required int? repostId,
|
required int? repostId,
|
||||||
|
required int? realmId,
|
||||||
required SnPost? replyTo,
|
required SnPost? replyTo,
|
||||||
required SnPost? repostTo,
|
required SnPost? repostTo,
|
||||||
required List<int>? visibleUsersList,
|
required List<int>? visibleUsersList,
|
||||||
@@ -95,6 +97,7 @@ class SnPostPreload with _$SnPostPreload {
|
|||||||
required List<SnAttachment?>? attachments,
|
required List<SnAttachment?>? attachments,
|
||||||
required SnAttachment? video,
|
required SnAttachment? video,
|
||||||
required SnPoll? poll,
|
required SnPoll? poll,
|
||||||
|
required SnRealm? realm,
|
||||||
}) = _SnPostPreload;
|
}) = _SnPostPreload;
|
||||||
|
|
||||||
factory SnPostPreload.fromJson(Map<String, Object?> json) =>
|
factory SnPostPreload.fromJson(Map<String, Object?> json) =>
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ mixin _$SnPost {
|
|||||||
List<SnPost>? get replies => throw _privateConstructorUsedError;
|
List<SnPost>? get replies => throw _privateConstructorUsedError;
|
||||||
int? get replyId => throw _privateConstructorUsedError;
|
int? get replyId => throw _privateConstructorUsedError;
|
||||||
int? get repostId => throw _privateConstructorUsedError;
|
int? get repostId => throw _privateConstructorUsedError;
|
||||||
|
int? get realmId => throw _privateConstructorUsedError;
|
||||||
SnPost? get replyTo => throw _privateConstructorUsedError;
|
SnPost? get replyTo => throw _privateConstructorUsedError;
|
||||||
SnPost? get repostTo => throw _privateConstructorUsedError;
|
SnPost? get repostTo => throw _privateConstructorUsedError;
|
||||||
List<int>? get visibleUsersList => throw _privateConstructorUsedError;
|
List<int>? get visibleUsersList => throw _privateConstructorUsedError;
|
||||||
@@ -84,6 +85,7 @@ abstract class $SnPostCopyWith<$Res> {
|
|||||||
List<SnPost>? replies,
|
List<SnPost>? replies,
|
||||||
int? replyId,
|
int? replyId,
|
||||||
int? repostId,
|
int? repostId,
|
||||||
|
int? realmId,
|
||||||
SnPost? replyTo,
|
SnPost? replyTo,
|
||||||
SnPost? repostTo,
|
SnPost? repostTo,
|
||||||
List<int>? visibleUsersList,
|
List<int>? visibleUsersList,
|
||||||
@@ -141,6 +143,7 @@ class _$SnPostCopyWithImpl<$Res, $Val extends SnPost>
|
|||||||
Object? replies = freezed,
|
Object? replies = freezed,
|
||||||
Object? replyId = freezed,
|
Object? replyId = freezed,
|
||||||
Object? repostId = freezed,
|
Object? repostId = freezed,
|
||||||
|
Object? realmId = freezed,
|
||||||
Object? replyTo = freezed,
|
Object? replyTo = freezed,
|
||||||
Object? repostTo = freezed,
|
Object? repostTo = freezed,
|
||||||
Object? visibleUsersList = freezed,
|
Object? visibleUsersList = freezed,
|
||||||
@@ -219,6 +222,10 @@ class _$SnPostCopyWithImpl<$Res, $Val extends SnPost>
|
|||||||
? _value.repostId
|
? _value.repostId
|
||||||
: repostId // ignore: cast_nullable_to_non_nullable
|
: repostId // ignore: cast_nullable_to_non_nullable
|
||||||
as int?,
|
as int?,
|
||||||
|
realmId: freezed == realmId
|
||||||
|
? _value.realmId
|
||||||
|
: realmId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int?,
|
||||||
replyTo: freezed == replyTo
|
replyTo: freezed == replyTo
|
||||||
? _value.replyTo
|
? _value.replyTo
|
||||||
: replyTo // ignore: cast_nullable_to_non_nullable
|
: replyTo // ignore: cast_nullable_to_non_nullable
|
||||||
@@ -387,6 +394,7 @@ abstract class _$$SnPostImplCopyWith<$Res> implements $SnPostCopyWith<$Res> {
|
|||||||
List<SnPost>? replies,
|
List<SnPost>? replies,
|
||||||
int? replyId,
|
int? replyId,
|
||||||
int? repostId,
|
int? repostId,
|
||||||
|
int? realmId,
|
||||||
SnPost? replyTo,
|
SnPost? replyTo,
|
||||||
SnPost? repostTo,
|
SnPost? repostTo,
|
||||||
List<int>? visibleUsersList,
|
List<int>? visibleUsersList,
|
||||||
@@ -447,6 +455,7 @@ class __$$SnPostImplCopyWithImpl<$Res>
|
|||||||
Object? replies = freezed,
|
Object? replies = freezed,
|
||||||
Object? replyId = freezed,
|
Object? replyId = freezed,
|
||||||
Object? repostId = freezed,
|
Object? repostId = freezed,
|
||||||
|
Object? realmId = freezed,
|
||||||
Object? replyTo = freezed,
|
Object? replyTo = freezed,
|
||||||
Object? repostTo = freezed,
|
Object? repostTo = freezed,
|
||||||
Object? visibleUsersList = freezed,
|
Object? visibleUsersList = freezed,
|
||||||
@@ -525,6 +534,10 @@ class __$$SnPostImplCopyWithImpl<$Res>
|
|||||||
? _value.repostId
|
? _value.repostId
|
||||||
: repostId // ignore: cast_nullable_to_non_nullable
|
: repostId // ignore: cast_nullable_to_non_nullable
|
||||||
as int?,
|
as int?,
|
||||||
|
realmId: freezed == realmId
|
||||||
|
? _value.realmId
|
||||||
|
: realmId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int?,
|
||||||
replyTo: freezed == replyTo
|
replyTo: freezed == replyTo
|
||||||
? _value.replyTo
|
? _value.replyTo
|
||||||
: replyTo // ignore: cast_nullable_to_non_nullable
|
: replyTo // ignore: cast_nullable_to_non_nullable
|
||||||
@@ -627,6 +640,7 @@ class _$SnPostImpl extends _SnPost {
|
|||||||
required final List<SnPost>? replies,
|
required final List<SnPost>? replies,
|
||||||
required this.replyId,
|
required this.replyId,
|
||||||
required this.repostId,
|
required this.repostId,
|
||||||
|
required this.realmId,
|
||||||
required this.replyTo,
|
required this.replyTo,
|
||||||
required this.repostTo,
|
required this.repostTo,
|
||||||
required final List<int>? visibleUsersList,
|
required final List<int>? visibleUsersList,
|
||||||
@@ -715,6 +729,8 @@ class _$SnPostImpl extends _SnPost {
|
|||||||
@override
|
@override
|
||||||
final int? repostId;
|
final int? repostId;
|
||||||
@override
|
@override
|
||||||
|
final int? realmId;
|
||||||
|
@override
|
||||||
final SnPost? replyTo;
|
final SnPost? replyTo;
|
||||||
@override
|
@override
|
||||||
final SnPost? repostTo;
|
final SnPost? repostTo;
|
||||||
@@ -777,7 +793,7 @@ class _$SnPostImpl extends _SnPost {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
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
|
@override
|
||||||
@@ -806,6 +822,7 @@ class _$SnPostImpl extends _SnPost {
|
|||||||
(identical(other.replyId, replyId) || other.replyId == replyId) &&
|
(identical(other.replyId, replyId) || other.replyId == replyId) &&
|
||||||
(identical(other.repostId, repostId) ||
|
(identical(other.repostId, repostId) ||
|
||||||
other.repostId == repostId) &&
|
other.repostId == repostId) &&
|
||||||
|
(identical(other.realmId, realmId) || other.realmId == realmId) &&
|
||||||
(identical(other.replyTo, replyTo) || other.replyTo == replyTo) &&
|
(identical(other.replyTo, replyTo) || other.replyTo == replyTo) &&
|
||||||
(identical(other.repostTo, repostTo) ||
|
(identical(other.repostTo, repostTo) ||
|
||||||
other.repostTo == repostTo) &&
|
other.repostTo == repostTo) &&
|
||||||
@@ -861,6 +878,7 @@ class _$SnPostImpl extends _SnPost {
|
|||||||
const DeepCollectionEquality().hash(_replies),
|
const DeepCollectionEquality().hash(_replies),
|
||||||
replyId,
|
replyId,
|
||||||
repostId,
|
repostId,
|
||||||
|
realmId,
|
||||||
replyTo,
|
replyTo,
|
||||||
repostTo,
|
repostTo,
|
||||||
const DeepCollectionEquality().hash(_visibleUsersList),
|
const DeepCollectionEquality().hash(_visibleUsersList),
|
||||||
@@ -915,6 +933,7 @@ abstract class _SnPost extends SnPost {
|
|||||||
required final List<SnPost>? replies,
|
required final List<SnPost>? replies,
|
||||||
required final int? replyId,
|
required final int? replyId,
|
||||||
required final int? repostId,
|
required final int? repostId,
|
||||||
|
required final int? realmId,
|
||||||
required final SnPost? replyTo,
|
required final SnPost? replyTo,
|
||||||
required final SnPost? repostTo,
|
required final SnPost? repostTo,
|
||||||
required final List<int>? visibleUsersList,
|
required final List<int>? visibleUsersList,
|
||||||
@@ -968,6 +987,8 @@ abstract class _SnPost extends SnPost {
|
|||||||
@override
|
@override
|
||||||
int? get repostId;
|
int? get repostId;
|
||||||
@override
|
@override
|
||||||
|
int? get realmId;
|
||||||
|
@override
|
||||||
SnPost? get replyTo;
|
SnPost? get replyTo;
|
||||||
@override
|
@override
|
||||||
SnPost? get repostTo;
|
SnPost? get repostTo;
|
||||||
@@ -1636,6 +1657,7 @@ mixin _$SnPostPreload {
|
|||||||
List<SnAttachment?>? get attachments => throw _privateConstructorUsedError;
|
List<SnAttachment?>? get attachments => throw _privateConstructorUsedError;
|
||||||
SnAttachment? get video => throw _privateConstructorUsedError;
|
SnAttachment? get video => throw _privateConstructorUsedError;
|
||||||
SnPoll? get poll => throw _privateConstructorUsedError;
|
SnPoll? get poll => throw _privateConstructorUsedError;
|
||||||
|
SnRealm? get realm => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
/// Serializes this SnPostPreload to a JSON map.
|
/// Serializes this SnPostPreload to a JSON map.
|
||||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||||
@@ -1657,11 +1679,13 @@ abstract class $SnPostPreloadCopyWith<$Res> {
|
|||||||
{SnAttachment? thumbnail,
|
{SnAttachment? thumbnail,
|
||||||
List<SnAttachment?>? attachments,
|
List<SnAttachment?>? attachments,
|
||||||
SnAttachment? video,
|
SnAttachment? video,
|
||||||
SnPoll? poll});
|
SnPoll? poll,
|
||||||
|
SnRealm? realm});
|
||||||
|
|
||||||
$SnAttachmentCopyWith<$Res>? get thumbnail;
|
$SnAttachmentCopyWith<$Res>? get thumbnail;
|
||||||
$SnAttachmentCopyWith<$Res>? get video;
|
$SnAttachmentCopyWith<$Res>? get video;
|
||||||
$SnPollCopyWith<$Res>? get poll;
|
$SnPollCopyWith<$Res>? get poll;
|
||||||
|
$SnRealmCopyWith<$Res>? get realm;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
@@ -1683,6 +1707,7 @@ class _$SnPostPreloadCopyWithImpl<$Res, $Val extends SnPostPreload>
|
|||||||
Object? attachments = freezed,
|
Object? attachments = freezed,
|
||||||
Object? video = freezed,
|
Object? video = freezed,
|
||||||
Object? poll = freezed,
|
Object? poll = freezed,
|
||||||
|
Object? realm = freezed,
|
||||||
}) {
|
}) {
|
||||||
return _then(_value.copyWith(
|
return _then(_value.copyWith(
|
||||||
thumbnail: freezed == thumbnail
|
thumbnail: freezed == thumbnail
|
||||||
@@ -1701,6 +1726,10 @@ class _$SnPostPreloadCopyWithImpl<$Res, $Val extends SnPostPreload>
|
|||||||
? _value.poll
|
? _value.poll
|
||||||
: poll // ignore: cast_nullable_to_non_nullable
|
: poll // ignore: cast_nullable_to_non_nullable
|
||||||
as SnPoll?,
|
as SnPoll?,
|
||||||
|
realm: freezed == realm
|
||||||
|
? _value.realm
|
||||||
|
: realm // ignore: cast_nullable_to_non_nullable
|
||||||
|
as SnRealm?,
|
||||||
) as $Val);
|
) as $Val);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1745,6 +1774,20 @@ class _$SnPostPreloadCopyWithImpl<$Res, $Val extends SnPostPreload>
|
|||||||
return _then(_value.copyWith(poll: value) as $Val);
|
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
|
/// @nodoc
|
||||||
@@ -1759,7 +1802,8 @@ abstract class _$$SnPostPreloadImplCopyWith<$Res>
|
|||||||
{SnAttachment? thumbnail,
|
{SnAttachment? thumbnail,
|
||||||
List<SnAttachment?>? attachments,
|
List<SnAttachment?>? attachments,
|
||||||
SnAttachment? video,
|
SnAttachment? video,
|
||||||
SnPoll? poll});
|
SnPoll? poll,
|
||||||
|
SnRealm? realm});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
$SnAttachmentCopyWith<$Res>? get thumbnail;
|
$SnAttachmentCopyWith<$Res>? get thumbnail;
|
||||||
@@ -1767,6 +1811,8 @@ abstract class _$$SnPostPreloadImplCopyWith<$Res>
|
|||||||
$SnAttachmentCopyWith<$Res>? get video;
|
$SnAttachmentCopyWith<$Res>? get video;
|
||||||
@override
|
@override
|
||||||
$SnPollCopyWith<$Res>? get poll;
|
$SnPollCopyWith<$Res>? get poll;
|
||||||
|
@override
|
||||||
|
$SnRealmCopyWith<$Res>? get realm;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
@@ -1786,6 +1832,7 @@ class __$$SnPostPreloadImplCopyWithImpl<$Res>
|
|||||||
Object? attachments = freezed,
|
Object? attachments = freezed,
|
||||||
Object? video = freezed,
|
Object? video = freezed,
|
||||||
Object? poll = freezed,
|
Object? poll = freezed,
|
||||||
|
Object? realm = freezed,
|
||||||
}) {
|
}) {
|
||||||
return _then(_$SnPostPreloadImpl(
|
return _then(_$SnPostPreloadImpl(
|
||||||
thumbnail: freezed == thumbnail
|
thumbnail: freezed == thumbnail
|
||||||
@@ -1804,6 +1851,10 @@ class __$$SnPostPreloadImplCopyWithImpl<$Res>
|
|||||||
? _value.poll
|
? _value.poll
|
||||||
: poll // ignore: cast_nullable_to_non_nullable
|
: poll // ignore: cast_nullable_to_non_nullable
|
||||||
as SnPoll?,
|
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 this.thumbnail,
|
||||||
required final List<SnAttachment?>? attachments,
|
required final List<SnAttachment?>? attachments,
|
||||||
required this.video,
|
required this.video,
|
||||||
required this.poll})
|
required this.poll,
|
||||||
|
required this.realm})
|
||||||
: _attachments = attachments;
|
: _attachments = attachments;
|
||||||
|
|
||||||
factory _$SnPostPreloadImpl.fromJson(Map<String, dynamic> json) =>
|
factory _$SnPostPreloadImpl.fromJson(Map<String, dynamic> json) =>
|
||||||
@@ -1837,10 +1889,12 @@ class _$SnPostPreloadImpl implements _SnPostPreload {
|
|||||||
final SnAttachment? video;
|
final SnAttachment? video;
|
||||||
@override
|
@override
|
||||||
final SnPoll? poll;
|
final SnPoll? poll;
|
||||||
|
@override
|
||||||
|
final SnRealm? realm;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
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
|
@override
|
||||||
@@ -1853,13 +1907,14 @@ class _$SnPostPreloadImpl implements _SnPostPreload {
|
|||||||
const DeepCollectionEquality()
|
const DeepCollectionEquality()
|
||||||
.equals(other._attachments, _attachments) &&
|
.equals(other._attachments, _attachments) &&
|
||||||
(identical(other.video, video) || other.video == video) &&
|
(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)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(runtimeType, thumbnail,
|
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
|
/// Create a copy of SnPostPreload
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// 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 SnAttachment? thumbnail,
|
||||||
required final List<SnAttachment?>? attachments,
|
required final List<SnAttachment?>? attachments,
|
||||||
required final SnAttachment? video,
|
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) =
|
factory _SnPostPreload.fromJson(Map<String, dynamic> json) =
|
||||||
_$SnPostPreloadImpl.fromJson;
|
_$SnPostPreloadImpl.fromJson;
|
||||||
@@ -1895,6 +1951,8 @@ abstract class _SnPostPreload implements SnPostPreload {
|
|||||||
SnAttachment? get video;
|
SnAttachment? get video;
|
||||||
@override
|
@override
|
||||||
SnPoll? get poll;
|
SnPoll? get poll;
|
||||||
|
@override
|
||||||
|
SnRealm? get realm;
|
||||||
|
|
||||||
/// Create a copy of SnPostPreload
|
/// Create a copy of SnPostPreload
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ _$SnPostImpl _$$SnPostImplFromJson(Map<String, dynamic> json) => _$SnPostImpl(
|
|||||||
.toList(),
|
.toList(),
|
||||||
replyId: (json['reply_id'] as num?)?.toInt(),
|
replyId: (json['reply_id'] as num?)?.toInt(),
|
||||||
repostId: (json['repost_id'] as num?)?.toInt(),
|
repostId: (json['repost_id'] as num?)?.toInt(),
|
||||||
|
realmId: (json['realm_id'] as num?)?.toInt(),
|
||||||
replyTo: json['reply_to'] == null
|
replyTo: json['reply_to'] == null
|
||||||
? null
|
? null
|
||||||
: SnPost.fromJson(json['reply_to'] as Map<String, dynamic>),
|
: 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(),
|
'replies': instance.replies?.map((e) => e.toJson()).toList(),
|
||||||
'reply_id': instance.replyId,
|
'reply_id': instance.replyId,
|
||||||
'repost_id': instance.repostId,
|
'repost_id': instance.repostId,
|
||||||
|
'realm_id': instance.realmId,
|
||||||
'reply_to': instance.replyTo?.toJson(),
|
'reply_to': instance.replyTo?.toJson(),
|
||||||
'repost_to': instance.repostTo?.toJson(),
|
'repost_to': instance.repostTo?.toJson(),
|
||||||
'visible_users_list': instance.visibleUsersList,
|
'visible_users_list': instance.visibleUsersList,
|
||||||
@@ -178,6 +180,9 @@ _$SnPostPreloadImpl _$$SnPostPreloadImplFromJson(Map<String, dynamic> json) =>
|
|||||||
poll: json['poll'] == null
|
poll: json['poll'] == null
|
||||||
? null
|
? null
|
||||||
: SnPoll.fromJson(json['poll'] as Map<String, dynamic>),
|
: 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) =>
|
Map<String, dynamic> _$$SnPostPreloadImplToJson(_$SnPostPreloadImpl instance) =>
|
||||||
@@ -186,6 +191,7 @@ Map<String, dynamic> _$$SnPostPreloadImplToJson(_$SnPostPreloadImpl instance) =>
|
|||||||
'attachments': instance.attachments?.map((e) => e?.toJson()).toList(),
|
'attachments': instance.attachments?.map((e) => e?.toJson()).toList(),
|
||||||
'video': instance.video?.toJson(),
|
'video': instance.video?.toJson(),
|
||||||
'poll': instance.poll?.toJson(),
|
'poll': instance.poll?.toJson(),
|
||||||
|
'realm': instance.realm?.toJson(),
|
||||||
};
|
};
|
||||||
|
|
||||||
_$SnBodyImpl _$$SnBodyImplFromJson(Map<String, dynamic> json) => _$SnBodyImpl(
|
_$SnBodyImpl _$$SnBodyImplFromJson(Map<String, dynamic> json) => _$SnBodyImpl(
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ class SnRealm with _$SnRealm {
|
|||||||
@HiveField(10) required int accountId,
|
@HiveField(10) required int accountId,
|
||||||
@HiveField(11) required bool isPublic,
|
@HiveField(11) required bool isPublic,
|
||||||
@HiveField(12) required bool isCommunity,
|
@HiveField(12) required bool isCommunity,
|
||||||
|
@Default(0) int popularity,
|
||||||
}) = _SnRealm;
|
}) = _SnRealm;
|
||||||
|
|
||||||
factory SnRealm.fromJson(Map<String, dynamic> json) =>
|
factory SnRealm.fromJson(Map<String, dynamic> json) =>
|
||||||
|
|||||||
@@ -394,6 +394,7 @@ mixin _$SnRealm {
|
|||||||
bool get isPublic => throw _privateConstructorUsedError;
|
bool get isPublic => throw _privateConstructorUsedError;
|
||||||
@HiveField(12)
|
@HiveField(12)
|
||||||
bool get isCommunity => throw _privateConstructorUsedError;
|
bool get isCommunity => throw _privateConstructorUsedError;
|
||||||
|
int get popularity => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
/// Serializes this SnRealm to a JSON map.
|
/// Serializes this SnRealm to a JSON map.
|
||||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||||
@@ -423,7 +424,8 @@ abstract class $SnRealmCopyWith<$Res> {
|
|||||||
@HiveField(9) Map<String, dynamic>? accessPolicy,
|
@HiveField(9) Map<String, dynamic>? accessPolicy,
|
||||||
@HiveField(10) int accountId,
|
@HiveField(10) int accountId,
|
||||||
@HiveField(11) bool isPublic,
|
@HiveField(11) bool isPublic,
|
||||||
@HiveField(12) bool isCommunity});
|
@HiveField(12) bool isCommunity,
|
||||||
|
int popularity});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
@@ -455,6 +457,7 @@ class _$SnRealmCopyWithImpl<$Res, $Val extends SnRealm>
|
|||||||
Object? accountId = null,
|
Object? accountId = null,
|
||||||
Object? isPublic = null,
|
Object? isPublic = null,
|
||||||
Object? isCommunity = null,
|
Object? isCommunity = null,
|
||||||
|
Object? popularity = null,
|
||||||
}) {
|
}) {
|
||||||
return _then(_value.copyWith(
|
return _then(_value.copyWith(
|
||||||
id: null == id
|
id: null == id
|
||||||
@@ -513,6 +516,10 @@ class _$SnRealmCopyWithImpl<$Res, $Val extends SnRealm>
|
|||||||
? _value.isCommunity
|
? _value.isCommunity
|
||||||
: isCommunity // ignore: cast_nullable_to_non_nullable
|
: isCommunity // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,
|
as bool,
|
||||||
|
popularity: null == popularity
|
||||||
|
? _value.popularity
|
||||||
|
: popularity // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,
|
||||||
) as $Val);
|
) as $Val);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -538,7 +545,8 @@ abstract class _$$SnRealmImplCopyWith<$Res> implements $SnRealmCopyWith<$Res> {
|
|||||||
@HiveField(9) Map<String, dynamic>? accessPolicy,
|
@HiveField(9) Map<String, dynamic>? accessPolicy,
|
||||||
@HiveField(10) int accountId,
|
@HiveField(10) int accountId,
|
||||||
@HiveField(11) bool isPublic,
|
@HiveField(11) bool isPublic,
|
||||||
@HiveField(12) bool isCommunity});
|
@HiveField(12) bool isCommunity,
|
||||||
|
int popularity});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
@@ -568,6 +576,7 @@ class __$$SnRealmImplCopyWithImpl<$Res>
|
|||||||
Object? accountId = null,
|
Object? accountId = null,
|
||||||
Object? isPublic = null,
|
Object? isPublic = null,
|
||||||
Object? isCommunity = null,
|
Object? isCommunity = null,
|
||||||
|
Object? popularity = null,
|
||||||
}) {
|
}) {
|
||||||
return _then(_$SnRealmImpl(
|
return _then(_$SnRealmImpl(
|
||||||
id: null == id
|
id: null == id
|
||||||
@@ -626,6 +635,10 @@ class __$$SnRealmImplCopyWithImpl<$Res>
|
|||||||
? _value.isCommunity
|
? _value.isCommunity
|
||||||
: isCommunity // ignore: cast_nullable_to_non_nullable
|
: isCommunity // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,
|
as bool,
|
||||||
|
popularity: null == popularity
|
||||||
|
? _value.popularity
|
||||||
|
: popularity // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -648,7 +661,8 @@ class _$SnRealmImpl extends _SnRealm {
|
|||||||
@HiveField(9) required final Map<String, dynamic>? accessPolicy,
|
@HiveField(9) required final Map<String, dynamic>? accessPolicy,
|
||||||
@HiveField(10) required this.accountId,
|
@HiveField(10) required this.accountId,
|
||||||
@HiveField(11) required this.isPublic,
|
@HiveField(11) required this.isPublic,
|
||||||
@HiveField(12) required this.isCommunity})
|
@HiveField(12) required this.isCommunity,
|
||||||
|
this.popularity = 0})
|
||||||
: _members = members,
|
: _members = members,
|
||||||
_accessPolicy = accessPolicy,
|
_accessPolicy = accessPolicy,
|
||||||
super._();
|
super._();
|
||||||
@@ -713,10 +727,13 @@ class _$SnRealmImpl extends _SnRealm {
|
|||||||
@override
|
@override
|
||||||
@HiveField(12)
|
@HiveField(12)
|
||||||
final bool isCommunity;
|
final bool isCommunity;
|
||||||
|
@override
|
||||||
|
@JsonKey()
|
||||||
|
final int popularity;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
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
|
@override
|
||||||
@@ -745,7 +762,9 @@ class _$SnRealmImpl extends _SnRealm {
|
|||||||
(identical(other.isPublic, isPublic) ||
|
(identical(other.isPublic, isPublic) ||
|
||||||
other.isPublic == isPublic) &&
|
other.isPublic == isPublic) &&
|
||||||
(identical(other.isCommunity, isCommunity) ||
|
(identical(other.isCommunity, isCommunity) ||
|
||||||
other.isCommunity == isCommunity));
|
other.isCommunity == isCommunity) &&
|
||||||
|
(identical(other.popularity, popularity) ||
|
||||||
|
other.popularity == popularity));
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@@ -765,7 +784,8 @@ class _$SnRealmImpl extends _SnRealm {
|
|||||||
const DeepCollectionEquality().hash(_accessPolicy),
|
const DeepCollectionEquality().hash(_accessPolicy),
|
||||||
accountId,
|
accountId,
|
||||||
isPublic,
|
isPublic,
|
||||||
isCommunity);
|
isCommunity,
|
||||||
|
popularity);
|
||||||
|
|
||||||
/// Create a copy of SnRealm
|
/// Create a copy of SnRealm
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@@ -798,7 +818,8 @@ abstract class _SnRealm extends SnRealm {
|
|||||||
@HiveField(9) required final Map<String, dynamic>? accessPolicy,
|
@HiveField(9) required final Map<String, dynamic>? accessPolicy,
|
||||||
@HiveField(10) required final int accountId,
|
@HiveField(10) required final int accountId,
|
||||||
@HiveField(11) required final bool isPublic,
|
@HiveField(11) required final bool isPublic,
|
||||||
@HiveField(12) required final bool isCommunity}) = _$SnRealmImpl;
|
@HiveField(12) required final bool isCommunity,
|
||||||
|
final int popularity}) = _$SnRealmImpl;
|
||||||
const _SnRealm._() : super._();
|
const _SnRealm._() : super._();
|
||||||
|
|
||||||
factory _SnRealm.fromJson(Map<String, dynamic> json) = _$SnRealmImpl.fromJson;
|
factory _SnRealm.fromJson(Map<String, dynamic> json) = _$SnRealmImpl.fromJson;
|
||||||
@@ -844,6 +865,8 @@ abstract class _SnRealm extends SnRealm {
|
|||||||
@override
|
@override
|
||||||
@HiveField(12)
|
@HiveField(12)
|
||||||
bool get isCommunity;
|
bool get isCommunity;
|
||||||
|
@override
|
||||||
|
int get popularity;
|
||||||
|
|
||||||
/// Create a copy of SnRealm
|
/// Create a copy of SnRealm
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
|||||||
@@ -128,6 +128,7 @@ _$SnRealmImpl _$$SnRealmImplFromJson(Map<String, dynamic> json) =>
|
|||||||
accountId: (json['account_id'] as num).toInt(),
|
accountId: (json['account_id'] as num).toInt(),
|
||||||
isPublic: json['is_public'] as bool,
|
isPublic: json['is_public'] as bool,
|
||||||
isCommunity: json['is_community'] as bool,
|
isCommunity: json['is_community'] as bool,
|
||||||
|
popularity: (json['popularity'] as num?)?.toInt() ?? 0,
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$$SnRealmImplToJson(_$SnRealmImpl instance) =>
|
Map<String, dynamic> _$$SnRealmImplToJson(_$SnRealmImpl instance) =>
|
||||||
@@ -146,4 +147,5 @@ Map<String, dynamic> _$$SnRealmImplToJson(_$SnRealmImpl instance) =>
|
|||||||
'account_id': instance.accountId,
|
'account_id': instance.accountId,
|
||||||
'is_public': instance.isPublic,
|
'is_public': instance.isPublic,
|
||||||
'is_community': instance.isCommunity,
|
'is_community': instance.isCommunity,
|
||||||
|
'popularity': instance.popularity,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -97,6 +97,13 @@ class AboutScreen extends StatelessWidget {
|
|||||||
launchUrlString('https://status.solsynth.dev');
|
launchUrlString('https://status.solsynth.dev');
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
TextButton(
|
||||||
|
style: denseButtonStyle,
|
||||||
|
child: Text('projectDetail').tr(),
|
||||||
|
onPressed: () {
|
||||||
|
launchUrlString('https://solsynth.dev/products/solar-network');
|
||||||
|
},
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
).center(),
|
).center(),
|
||||||
@@ -108,6 +115,12 @@ class AboutScreen extends StatelessWidget {
|
|||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
InkWell(
|
||||||
|
child: Text('GitHub', style: TextStyle(fontSize: 12)),
|
||||||
|
onTap: () {
|
||||||
|
launchUrlString('https://github.com/Solsynth/HyperNet.Surface');
|
||||||
|
},
|
||||||
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -30,25 +30,21 @@ import 'package:surface/widgets/universal_image.dart';
|
|||||||
import '../attachment/pending_attachment_compress.dart';
|
import '../attachment/pending_attachment_compress.dart';
|
||||||
|
|
||||||
class PostMediaPendingList extends StatelessWidget {
|
class PostMediaPendingList extends StatelessWidget {
|
||||||
final PostWriteMedia? thumbnail;
|
|
||||||
final List<PostWriteMedia> attachments;
|
final List<PostWriteMedia> attachments;
|
||||||
final bool isBusy;
|
final bool isBusy;
|
||||||
final Future<void> Function(int idx, PostWriteMedia updatedMedia)? onUpdate;
|
final Future<void> Function(int idx, PostWriteMedia updatedMedia)? onUpdate;
|
||||||
final Future<void> Function(int idx)? onRemove;
|
final Future<void> Function(int idx)? onRemove;
|
||||||
final Future<void> Function(int idx)? onUpload;
|
final Future<void> Function(int idx)? onUpload;
|
||||||
final void Function(int? idx)? onPostSetThumbnail;
|
|
||||||
final void Function(int idx)? onInsertLink;
|
final void Function(int idx)? onInsertLink;
|
||||||
final void Function(bool state)? onUpdateBusy;
|
final void Function(bool state)? onUpdateBusy;
|
||||||
|
|
||||||
const PostMediaPendingList({
|
const PostMediaPendingList({
|
||||||
super.key,
|
super.key,
|
||||||
this.thumbnail,
|
|
||||||
required this.attachments,
|
required this.attachments,
|
||||||
required this.isBusy,
|
required this.isBusy,
|
||||||
this.onUpdate,
|
this.onUpdate,
|
||||||
this.onRemove,
|
this.onRemove,
|
||||||
this.onUpload,
|
this.onUpload,
|
||||||
this.onPostSetThumbnail,
|
|
||||||
this.onInsertLink,
|
this.onInsertLink,
|
||||||
this.onUpdateBusy,
|
this.onUpdateBusy,
|
||||||
});
|
});
|
||||||
@@ -116,7 +112,7 @@ class PostMediaPendingList extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _deleteAttachment(BuildContext context, int idx) async {
|
Future<void> _deleteAttachment(BuildContext context, int idx) async {
|
||||||
final media = idx == -1 ? thumbnail! : attachments[idx];
|
final media = attachments[idx];
|
||||||
if (media.attachment == null) return;
|
if (media.attachment == null) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -212,22 +208,6 @@ class PostMediaPendingList extends StatelessWidget {
|
|||||||
onSelected: () {
|
onSelected: () {
|
||||||
onUpload!(idx);
|
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)
|
if (media.attachment != null && onInsertLink != null)
|
||||||
MenuItem(
|
MenuItem(
|
||||||
label: 'attachmentInsertLink'.tr(),
|
label: 'attachmentInsertLink'.tr(),
|
||||||
@@ -291,35 +271,18 @@ class PostMediaPendingList extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Container(
|
return Container(
|
||||||
constraints: const BoxConstraints(maxHeight: 120),
|
constraints: const BoxConstraints(maxHeight: 120),
|
||||||
child: Row(
|
child: ListView.separated(
|
||||||
children: [
|
scrollDirection: Axis.horizontal,
|
||||||
const Gap(16),
|
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||||
if (thumbnail != null)
|
separatorBuilder: (context, index) => const Gap(8),
|
||||||
ContextMenuArea(
|
itemCount: attachments.length,
|
||||||
contextMenu: _createContextMenu(context, -1, thumbnail!),
|
itemBuilder: (context, idx) {
|
||||||
child: _PostMediaPendingItem(media: thumbnail!),
|
final media = attachments[idx];
|
||||||
),
|
return ContextMenuArea(
|
||||||
if (thumbnail != null)
|
contextMenu: _createContextMenu(context, idx, media),
|
||||||
const VerticalDivider(width: 1, thickness: 1).padding(
|
child: _PostMediaPendingItem(media: media),
|
||||||
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),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,6 +15,7 @@
|
|||||||
#include <media_kit_libs_linux/media_kit_libs_linux_plugin.h>
|
#include <media_kit_libs_linux/media_kit_libs_linux_plugin.h>
|
||||||
#include <media_kit_video/media_kit_video_plugin.h>
|
#include <media_kit_video/media_kit_video_plugin.h>
|
||||||
#include <pasteboard/pasteboard_plugin.h>
|
#include <pasteboard/pasteboard_plugin.h>
|
||||||
|
#include <sqlite3_flutter_libs/sqlite3_flutter_libs_plugin.h>
|
||||||
#include <tray_manager/tray_manager_plugin.h>
|
#include <tray_manager/tray_manager_plugin.h>
|
||||||
#include <url_launcher_linux/url_launcher_plugin.h>
|
#include <url_launcher_linux/url_launcher_plugin.h>
|
||||||
|
|
||||||
@@ -46,6 +47,9 @@ void fl_register_plugins(FlPluginRegistry* registry) {
|
|||||||
g_autoptr(FlPluginRegistrar) pasteboard_registrar =
|
g_autoptr(FlPluginRegistrar) pasteboard_registrar =
|
||||||
fl_plugin_registry_get_registrar_for_plugin(registry, "PasteboardPlugin");
|
fl_plugin_registry_get_registrar_for_plugin(registry, "PasteboardPlugin");
|
||||||
pasteboard_plugin_register_with_registrar(pasteboard_registrar);
|
pasteboard_plugin_register_with_registrar(pasteboard_registrar);
|
||||||
|
g_autoptr(FlPluginRegistrar) sqlite3_flutter_libs_registrar =
|
||||||
|
fl_plugin_registry_get_registrar_for_plugin(registry, "Sqlite3FlutterLibsPlugin");
|
||||||
|
sqlite3_flutter_libs_plugin_register_with_registrar(sqlite3_flutter_libs_registrar);
|
||||||
g_autoptr(FlPluginRegistrar) tray_manager_registrar =
|
g_autoptr(FlPluginRegistrar) tray_manager_registrar =
|
||||||
fl_plugin_registry_get_registrar_for_plugin(registry, "TrayManagerPlugin");
|
fl_plugin_registry_get_registrar_for_plugin(registry, "TrayManagerPlugin");
|
||||||
tray_manager_plugin_register_with_registrar(tray_manager_registrar);
|
tray_manager_plugin_register_with_registrar(tray_manager_registrar);
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
|||||||
media_kit_libs_linux
|
media_kit_libs_linux
|
||||||
media_kit_video
|
media_kit_video
|
||||||
pasteboard
|
pasteboard
|
||||||
|
sqlite3_flutter_libs
|
||||||
tray_manager
|
tray_manager
|
||||||
url_launcher_linux
|
url_launcher_linux
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import screen_brightness_macos
|
|||||||
import share_plus
|
import share_plus
|
||||||
import shared_preferences_foundation
|
import shared_preferences_foundation
|
||||||
import sqflite_darwin
|
import sqflite_darwin
|
||||||
|
import sqlite3_flutter_libs
|
||||||
import tray_manager
|
import tray_manager
|
||||||
import url_launcher_macos
|
import url_launcher_macos
|
||||||
import video_compress
|
import video_compress
|
||||||
@@ -61,6 +62,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
|||||||
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
|
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
|
||||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||||
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
|
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
|
||||||
|
Sqlite3FlutterLibsPlugin.register(with: registry.registrar(forPlugin: "Sqlite3FlutterLibsPlugin"))
|
||||||
TrayManagerPlugin.register(with: registry.registrar(forPlugin: "TrayManagerPlugin"))
|
TrayManagerPlugin.register(with: registry.registrar(forPlugin: "TrayManagerPlugin"))
|
||||||
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
||||||
VideoCompressPlugin.register(with: registry.registrar(forPlugin: "VideoCompressPlugin"))
|
VideoCompressPlugin.register(with: registry.registrar(forPlugin: "VideoCompressPlugin"))
|
||||||
|
|||||||
68
pubspec.lock
68
pubspec.lock
@@ -191,7 +191,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "3.4.1"
|
version: "3.4.1"
|
||||||
cached_network_image_platform_interface:
|
cached_network_image_platform_interface:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: cached_network_image_platform_interface
|
name: cached_network_image_platform_interface
|
||||||
sha256: "35814b016e37fbdc91f7ae18c8caf49ba5c88501813f73ce8a07027a395e2829"
|
sha256: "35814b016e37fbdc91f7ae18c8caf49ba5c88501813f73ce8a07027a395e2829"
|
||||||
@@ -230,6 +230,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.0"
|
version: "1.3.0"
|
||||||
|
charcode:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: charcode
|
||||||
|
sha256: fb0f1107cac15a5ea6ef0a6ef71a807b9e4267c713bb93e00e92d737cc8dbd8a
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.0"
|
||||||
checked_yaml:
|
checked_yaml:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -414,6 +422,30 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.2"
|
version: "1.0.2"
|
||||||
|
drift:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: drift
|
||||||
|
sha256: "97d5832657d49f26e7a8e07de397ddc63790b039372878d5117af816d0fdb5cb"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.25.1"
|
||||||
|
drift_dev:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: drift_dev
|
||||||
|
sha256: f1db88482dbb016b9bbddddf746d5d0a6938b156ff20e07320052981f97388cc
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.25.2"
|
||||||
|
drift_flutter:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: drift_flutter
|
||||||
|
sha256: "0cadbf3b8733409a6cf61d18ba2e94e149df81df7de26f48ae0695b48fd71922"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.2.4"
|
||||||
dropdown_button2:
|
dropdown_button2:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -1083,7 +1115,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "0.2.1+2"
|
version: "0.2.1+2"
|
||||||
image_picker_platform_interface:
|
image_picker_platform_interface:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: image_picker_platform_interface
|
name: image_picker_platform_interface
|
||||||
sha256: "886d57f0be73c4b140004e78b9f28a8914a09e50c2d816bdd0520051a71236a0"
|
sha256: "886d57f0be73c4b140004e78b9f28a8914a09e50c2d816bdd0520051a71236a0"
|
||||||
@@ -1642,6 +1674,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.1.0"
|
version: "4.1.0"
|
||||||
|
recase:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: recase
|
||||||
|
sha256: e4eb4ec2dcdee52dcf99cb4ceabaffc631d7424ee55e56f280bc039737f89213
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.1.0"
|
||||||
receive_sharing_intent:
|
receive_sharing_intent:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -1935,6 +1975,30 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.0"
|
version: "2.4.0"
|
||||||
|
sqlite3:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: sqlite3
|
||||||
|
sha256: "32b632dda27d664f85520093ed6f735ae5c49b5b75345afb8b19411bc59bb53d"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.7.4"
|
||||||
|
sqlite3_flutter_libs:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: sqlite3_flutter_libs
|
||||||
|
sha256: "57fafacd815c981735406215966ff7caaa8eab984b094f52e692accefcbd9233"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.5.30"
|
||||||
|
sqlparser:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: sqlparser
|
||||||
|
sha256: "27dd0a9f0c02e22ac0eb42a23df9ea079ce69b52bb4a3b478d64e0ef34a263ee"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.41.0"
|
||||||
stack_trace:
|
stack_trace:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -121,6 +121,10 @@ dependencies:
|
|||||||
tray_manager: ^0.3.2
|
tray_manager: ^0.3.2
|
||||||
hotkey_manager: ^0.2.3
|
hotkey_manager: ^0.2.3
|
||||||
image_picker_android: ^0.8.12+20
|
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
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
@@ -132,13 +136,14 @@ dev_dependencies:
|
|||||||
# package. See that file for information about deactivating specific lint
|
# package. See that file for information about deactivating specific lint
|
||||||
# rules and activating additional ones.
|
# rules and activating additional ones.
|
||||||
flutter_lints: ^5.0.0
|
flutter_lints: ^5.0.0
|
||||||
build_runner: ^2.4.13
|
build_runner: ^2.4.15
|
||||||
freezed: ^2.5.7
|
freezed: ^2.5.7
|
||||||
json_serializable: ^6.8.0
|
json_serializable: ^6.8.0
|
||||||
icons_launcher: ^3.0.0
|
icons_launcher: ^3.0.0
|
||||||
flutter_native_splash: ^2.4.2
|
flutter_native_splash: ^2.4.2
|
||||||
hive_generator: ^2.0.1
|
hive_generator: ^2.0.1
|
||||||
flutter_launcher_icons: ^0.14.1
|
flutter_launcher_icons: ^0.14.1
|
||||||
|
drift_dev: ^2.25.2
|
||||||
|
|
||||||
# For information on the generic Dart part of this file, see the
|
# For information on the generic Dart part of this file, see the
|
||||||
# following page: https://dart.dev/tools/pub/pubspec
|
# following page: https://dart.dev/tools/pub/pubspec
|
||||||
|
|||||||
13631
web/drift_worker.dart.js
Normal file
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",
|
"name": "Solar Network",
|
||||||
"short_name": "surface",
|
"short_name": "Solian",
|
||||||
"start_url": ".",
|
"start_url": ".",
|
||||||
"display": "standalone",
|
"display": "standalone",
|
||||||
"background_color": "#ffffff",
|
"background_color": "#ffffff",
|
||||||
"theme_color": "#ffffff",
|
"theme_color": "#ffffff",
|
||||||
"description": "A new Flutter project.",
|
"description": "The Solar Network is a social network app.",
|
||||||
"orientation": "portrait-primary",
|
"orientation": "portrait-primary",
|
||||||
"prefer_related_applications": false,
|
"prefer_related_applications": false,
|
||||||
"icons": [
|
"icons": [
|
||||||
|
|||||||
BIN
web/sqlite3.wasm
Normal file
BIN
web/sqlite3.wasm
Normal file
Binary file not shown.
@@ -23,6 +23,7 @@
|
|||||||
#include <permission_handler_windows/permission_handler_windows_plugin.h>
|
#include <permission_handler_windows/permission_handler_windows_plugin.h>
|
||||||
#include <screen_brightness_windows/screen_brightness_windows_plugin.h>
|
#include <screen_brightness_windows/screen_brightness_windows_plugin.h>
|
||||||
#include <share_plus/share_plus_windows_plugin_c_api.h>
|
#include <share_plus/share_plus_windows_plugin_c_api.h>
|
||||||
|
#include <sqlite3_flutter_libs/sqlite3_flutter_libs_plugin.h>
|
||||||
#include <tray_manager/tray_manager_plugin.h>
|
#include <tray_manager/tray_manager_plugin.h>
|
||||||
#include <url_launcher_windows/url_launcher_windows.h>
|
#include <url_launcher_windows/url_launcher_windows.h>
|
||||||
|
|
||||||
@@ -61,6 +62,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
|
|||||||
registry->GetRegistrarForPlugin("ScreenBrightnessWindowsPlugin"));
|
registry->GetRegistrarForPlugin("ScreenBrightnessWindowsPlugin"));
|
||||||
SharePlusWindowsPluginCApiRegisterWithRegistrar(
|
SharePlusWindowsPluginCApiRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi"));
|
registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi"));
|
||||||
|
Sqlite3FlutterLibsPluginRegisterWithRegistrar(
|
||||||
|
registry->GetRegistrarForPlugin("Sqlite3FlutterLibsPlugin"));
|
||||||
TrayManagerPluginRegisterWithRegistrar(
|
TrayManagerPluginRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("TrayManagerPlugin"));
|
registry->GetRegistrarForPlugin("TrayManagerPlugin"));
|
||||||
UrlLauncherWindowsRegisterWithRegistrar(
|
UrlLauncherWindowsRegisterWithRegistrar(
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
|||||||
permission_handler_windows
|
permission_handler_windows
|
||||||
screen_brightness_windows
|
screen_brightness_windows
|
||||||
share_plus
|
share_plus
|
||||||
|
sqlite3_flutter_libs
|
||||||
tray_manager
|
tray_manager
|
||||||
url_launcher_windows
|
url_launcher_windows
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user