Compare commits
68 Commits
Author | SHA1 | Date | |
---|---|---|---|
e2ecb573a2 | |||
8cb5dff498 | |||
a5629975ed | |||
972b304969 | |||
e8ded55055 | |||
04875eb164 | |||
54a59aa470 | |||
365f330629 | |||
a7829d15b2 | |||
a3868a4281 | |||
1d1d61d60c | |||
03c2491587 | |||
2c1adc988c | |||
c0fbee55e4 | |||
6e544c0b6c | |||
7d56c5ef31 | |||
c2df1af16d | |||
a8143c6453 | |||
04065061e0 | |||
226eb452e5 | |||
a6715b0872 | |||
43e3404dbb | |||
c91cf7c813 | |||
9cd1cad695 | |||
dde280833b | |||
42ac12b53e | |||
63567bf708 | |||
5d3cadefef | |||
251fbb2503 | |||
0b31d32217 | |||
5ddd4fed2e | |||
48b6d5f6c1 | |||
b83b0b5efb | |||
cb24bd953d | |||
4937dee182 | |||
d612097bb1 | |||
058d668b6b | |||
8b19462c3a | |||
0a381ef09b | |||
9b84e912b2 | |||
b3254e0f2f | |||
f0a3bbe023 | |||
df81c84438 | |||
8b12395fca | |||
cb2b71d194 | |||
7ed508e2bb | |||
dad869967e | |||
2d5b3b554e | |||
74882116e3 | |||
a97c3bce3a | |||
1aa70827dc | |||
fe028860e9 | |||
a2d2ce4d38 | |||
167c11b9eb | |||
8cb3933fcc | |||
3818328afe | |||
11627e2455 | |||
3f82c06ff8 | |||
2350f59131 | |||
9fe7c9530a | |||
52f1826e91 | |||
28a4c86dbf | |||
85e48ce03b | |||
efef61a8ea | |||
10ead95af9 | |||
838ee4d55d | |||
13e42429a9 | |||
c6ce3fe2b7 |
25
.github/workflows/nightly.yml
vendored
25
.github/workflows/nightly.yml
vendored
@ -38,4 +38,27 @@ jobs:
|
|||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: build-output-windows
|
name: build-output-windows
|
||||||
path: build/windows/x64/runner/Release
|
path: build/windows/x64/runner/Release
|
||||||
|
build-linux:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Clone repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: Set up Flutter
|
||||||
|
uses: subosito/flutter-action@v2
|
||||||
|
with:
|
||||||
|
channel: stable
|
||||||
|
cache: true
|
||||||
|
- run: |
|
||||||
|
sudo apt-get update -y
|
||||||
|
sudo apt-get install -y ninja-build libgtk-3-dev
|
||||||
|
sudo apt-get install libmpv-dev mpv
|
||||||
|
sudo apt-get install libayatana-appindicator3-dev
|
||||||
|
sudo apt-get install keybinder-3.0
|
||||||
|
- run: flutter pub get
|
||||||
|
- run: flutter build linux
|
||||||
|
- name: Archive production artifacts
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: build-output-linux
|
||||||
|
path: build/linux/x64/release/bundle
|
27
README.md
Normal file
27
README.md
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
# Solar Network
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Hello there! Welcome to the main repository of the HyperNet (also known as the Solar Network). The code here is mainly about the frontend app (also known as Solian). But you can still post issues here to get help and request new features!
|
||||||
|
|
||||||
|
## Sub Projects
|
||||||
|
|
||||||
|
HyperNet, the Solar Network is a microservices project in which the backends are stored in separate repositories. Here is a simple index for it.
|
||||||
|
|
||||||
|
- The Core, Gateway: [Nexus](https://github.com/Solsynth/HyperNet.Nexus)
|
||||||
|
- The Auth Service: [Passport](https://github.com/Solsynth/HyperNet.Passport)
|
||||||
|
- The Posting Service: [Interactive](https://github.com/Solsynth/HyperNet.Interactive)
|
||||||
|
- The Messaging Service: [Messaging](https://github.com/Solsynth/HyperNet.Messaging)
|
||||||
|
- The Wallet Service: [Wallet](https://github.com/Solsynth/HyperNet.Wallet)
|
||||||
|
- The Crawler: [Reader](https://github.com/Solsynth/HyperNet.Reader)
|
||||||
|
- Some others may not be listed, you can search in the organization with `HyperNet.` the prefix of all HyperNet projects.
|
||||||
|
|
||||||
|
## Tech Stack
|
||||||
|
|
||||||
|
For those people who want to know the tech stack of this project, the frontend was built by Flutter, which provides the cross-platform ability.
|
||||||
|
|
||||||
|
The backend was built in Go and PostgreSQL with our very own microservice framework included in the nexus.
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
|
The readme will be updated in the future, to be determined. For now, you can check out the link of this repository to learn more on our official website.
|
@ -17,7 +17,6 @@
|
|||||||
android:label="Solian"
|
android:label="Solian"
|
||||||
android:name="${applicationName}"
|
android:name="${applicationName}"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:enableOnBackInvokedCallback="true"
|
|
||||||
android:requestLegacyExternalStorage="true">
|
android:requestLegacyExternalStorage="true">
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="flutterEmbedding"
|
android:name="flutterEmbedding"
|
||||||
|
@ -54,7 +54,7 @@ class CheckInWidget : GlanceAppWidget() {
|
|||||||
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
|
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
|
||||||
.registerTypeAdapter(Instant::class.java, InstantAdapter())
|
.registerTypeAdapter(Instant::class.java, InstantAdapter())
|
||||||
.create()
|
.create()
|
||||||
val resultTierSymbols = listOf("大凶", "凶", "中平", "吉", "大吉")
|
val resultTierSymbols = listOf("Bad", "Poor", "Medium", "Good", "Great")
|
||||||
|
|
||||||
val prefs = currentState.preferences
|
val prefs = currentState.preferences
|
||||||
val checkInRaw: String? = prefs.getString("pas_check_in_record", null)
|
val checkInRaw: String? = prefs.getString("pas_check_in_record", null)
|
||||||
@ -120,7 +120,7 @@ class CheckInWidget : GlanceAppWidget() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = "You haven't checked in today",
|
text = "You haven't divined today",
|
||||||
style = TextStyle(fontSize = 15.sp, color = GlanceTheme.colors.onSurface)
|
style = TextStyle(fontSize = 15.sp, color = GlanceTheme.colors.onSurface)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -12,9 +12,9 @@ post {
|
|||||||
|
|
||||||
body:json {
|
body:json {
|
||||||
{
|
{
|
||||||
"alias": "AteChip",
|
"alias": "BaLoading",
|
||||||
"name": "Cat ate chips",
|
"name": "BaLoading",
|
||||||
"attachment_id": "d0b692cc64054463",
|
"attachment_id": "2JCI2uh21mKkfk9P",
|
||||||
"pack_id": 2
|
"pack_id": 3
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
11
api/Paperclip/Stickers/Get Sticker Packs.bru
Normal file
11
api/Paperclip/Stickers/Get Sticker Packs.bru
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
meta {
|
||||||
|
name: Get Sticker Packs
|
||||||
|
type: http
|
||||||
|
seq: 3
|
||||||
|
}
|
||||||
|
|
||||||
|
get {
|
||||||
|
url: {{endpoint}}/cgi/uc/stickers/packs
|
||||||
|
body: none
|
||||||
|
auth: none
|
||||||
|
}
|
15
api/Paperclip/Stickers/Get Stickers.bru
Normal file
15
api/Paperclip/Stickers/Get Stickers.bru
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
meta {
|
||||||
|
name: Get Stickers
|
||||||
|
type: http
|
||||||
|
seq: 4
|
||||||
|
}
|
||||||
|
|
||||||
|
get {
|
||||||
|
url: {{endpoint}}/cgi/uc/stickers?take=10
|
||||||
|
body: none
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
params:query {
|
||||||
|
take: 10
|
||||||
|
}
|
@ -5,7 +5,7 @@ meta {
|
|||||||
}
|
}
|
||||||
|
|
||||||
post {
|
post {
|
||||||
url: {{endpoint}}/cgi/id/dev/notify/1
|
url: {{endpoint}}/cgi/id/dev/notify/122
|
||||||
body: json
|
body: json
|
||||||
auth: inherit
|
auth: inherit
|
||||||
}
|
}
|
||||||
@ -15,12 +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": "测试",
|
"subject": "处理该帐号 @solian 的决定",
|
||||||
"subtitle": "Alphabot です",
|
"subtitle": "违反用户协议",
|
||||||
"content": "全新通知动画",
|
"content": "您的帐号违反了我们用户协议中关于冒充我们官方的行为,至此做出停权的决定。还请见谅。该决定是最终决定,不接受上诉。",
|
||||||
"metadata": {
|
|
||||||
"image": "D2EDbcrsTugs3xk5"
|
|
||||||
},
|
|
||||||
"priority": 10
|
"priority": 10
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
20
api/Wallet/Create Order.bru
Normal file
20
api/Wallet/Create Order.bru
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
meta {
|
||||||
|
name: Create Order
|
||||||
|
type: http
|
||||||
|
seq: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
post {
|
||||||
|
url: {{endpoint}}/cgi/wa/orders
|
||||||
|
body: json
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
body:json {
|
||||||
|
{
|
||||||
|
"client_id": "highland-mc",
|
||||||
|
"client_secret": "(3^DLAvo3v",
|
||||||
|
"remark": "我是秦始皇,现在被困香港,现在 SN 转我 500 源点,帮助我回到咸阳,到时候封你为太监一职。",
|
||||||
|
"amount": 500
|
||||||
|
}
|
||||||
|
}
|
21
api/Wallet/Create Transaction.bru
Normal file
21
api/Wallet/Create Transaction.bru
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
meta {
|
||||||
|
name: Create Transaction
|
||||||
|
type: http
|
||||||
|
seq: 3
|
||||||
|
}
|
||||||
|
|
||||||
|
post {
|
||||||
|
url: {{endpoint}}/cgi/wa/transactions
|
||||||
|
body: json
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
body:json {
|
||||||
|
{
|
||||||
|
"client_id": "alphabot",
|
||||||
|
"client_secret": "_uR0sVnHTh",
|
||||||
|
"remark": "新年红包",
|
||||||
|
"amount": 150,
|
||||||
|
"payee_id": 18
|
||||||
|
}
|
||||||
|
}
|
20
api/Wallet/Get Order.bru
Normal file
20
api/Wallet/Get Order.bru
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
meta {
|
||||||
|
name: Get Order
|
||||||
|
type: http
|
||||||
|
seq: 2
|
||||||
|
}
|
||||||
|
|
||||||
|
get {
|
||||||
|
url: {{endpoint}}/cgi/wa/orders/4
|
||||||
|
body: none
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
body:json {
|
||||||
|
{
|
||||||
|
"client_id": "highland-mc",
|
||||||
|
"client_secret": "(3^DLAvo3v",
|
||||||
|
"remark": "我是秦始皇,现在被困香港,现在 SN 转我 500 源点,帮助我回到咸阳,到时候封你为太监一职。",
|
||||||
|
"amount": 500
|
||||||
|
}
|
||||||
|
}
|
20
api/Wallet/Get Transaction.bru
Normal file
20
api/Wallet/Get Transaction.bru
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
meta {
|
||||||
|
name: Get Transaction
|
||||||
|
type: http
|
||||||
|
seq: 4
|
||||||
|
}
|
||||||
|
|
||||||
|
get {
|
||||||
|
url: {{endpoint}}/cgi/wa/transactions/67
|
||||||
|
body: none
|
||||||
|
auth: inherit
|
||||||
|
}
|
||||||
|
|
||||||
|
body:json {
|
||||||
|
{
|
||||||
|
"client_id": "highland-mc",
|
||||||
|
"client_secret": "(3^DLAvo3v",
|
||||||
|
"remark": "我是秦始皇,现在被困香港,现在 SN 转我 500 源点,帮助我回到咸阳,到时候封你为太监一职。",
|
||||||
|
"amount": 500
|
||||||
|
}
|
||||||
|
}
|
BIN
assets/icon/tray-icon.ico
Normal file
BIN
assets/icon/tray-icon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
BIN
assets/icon/tray-icon.png
Normal file
BIN
assets/icon/tray-icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 228 KiB |
@ -27,6 +27,7 @@
|
|||||||
"screenChatNew": "New Channel",
|
"screenChatNew": "New Channel",
|
||||||
"screenRealm": "Realm",
|
"screenRealm": "Realm",
|
||||||
"screenRealmManage": "Edit Realm",
|
"screenRealmManage": "Edit Realm",
|
||||||
|
"screenRealmDiscovery": "Realm Discovery",
|
||||||
"screenRealmNew": "New Realm",
|
"screenRealmNew": "New Realm",
|
||||||
"screenNotification": "Notification",
|
"screenNotification": "Notification",
|
||||||
"screenPostSearch": "Search Posts",
|
"screenPostSearch": "Search Posts",
|
||||||
@ -154,9 +155,12 @@
|
|||||||
"fieldPublisherBelongToRealmUnset": "Unset Publisher Belongs to Realm",
|
"fieldPublisherBelongToRealmUnset": "Unset Publisher Belongs to Realm",
|
||||||
"writePostTypeStory": "Post a story",
|
"writePostTypeStory": "Post a story",
|
||||||
"writePostTypeArticle": "Write an article",
|
"writePostTypeArticle": "Write an article",
|
||||||
|
"writePostTypeQuestion": "Ask a question",
|
||||||
|
"writePostTypeVideo": "Post a video",
|
||||||
"fieldPostPublisher": "Post publisher",
|
"fieldPostPublisher": "Post publisher",
|
||||||
"fieldPostContent": "What happened?!",
|
"fieldPostContent": "What happened?!",
|
||||||
"fieldPostTitle": "Title",
|
"fieldPostTitle": "Title",
|
||||||
|
"fieldPostQuestionReward": "Answer Rewards (Source Points)",
|
||||||
"fieldPostDescription": "Description",
|
"fieldPostDescription": "Description",
|
||||||
"fieldPostTags": "Tags",
|
"fieldPostTags": "Tags",
|
||||||
"fieldPostCategories": "Categories",
|
"fieldPostCategories": "Categories",
|
||||||
@ -166,9 +170,9 @@
|
|||||||
"postPosted": "Post has been posted.",
|
"postPosted": "Post has been posted.",
|
||||||
"postPublishedAt": "Published At",
|
"postPublishedAt": "Published At",
|
||||||
"postPublishedUntil": "Published Until",
|
"postPublishedUntil": "Published Until",
|
||||||
"postEditingNotice": "You're about to editing a post that posted {}.",
|
"postEditingNotice": "You're about to editing a post that posted by {}.",
|
||||||
"postReplyingNotice": "You're about to reply to a post that posted {}.",
|
"postReplyingNotice": "You're about to reply to a post that posted by {}.",
|
||||||
"postRepostingNotice": "You're about to repost a post that posted {}.",
|
"postRepostingNotice": "You're about to repost a post that posted by {}.",
|
||||||
"postReact": "React",
|
"postReact": "React",
|
||||||
"postReactions": "Reactions of Post",
|
"postReactions": "Reactions of Post",
|
||||||
"postReactionUpvote": {
|
"postReactionUpvote": {
|
||||||
@ -329,6 +333,7 @@
|
|||||||
"addAttachmentFromRandomId": "Link via RID",
|
"addAttachmentFromRandomId": "Link via RID",
|
||||||
"attachmentDetailInfo": "Attachment details",
|
"attachmentDetailInfo": "Attachment details",
|
||||||
"attachmentPastedImage": "Pasted Image",
|
"attachmentPastedImage": "Pasted Image",
|
||||||
|
"attachmentInsertedImage": "Inserted Image",
|
||||||
"attachmentInsertLink": "Insert Link",
|
"attachmentInsertLink": "Insert Link",
|
||||||
"attachmentSetAsPostThumbnail": "Set as post thumbnail",
|
"attachmentSetAsPostThumbnail": "Set as post thumbnail",
|
||||||
"attachmentUnsetAsPostThumbnail": "Unset as post thumbnail",
|
"attachmentUnsetAsPostThumbnail": "Unset as post thumbnail",
|
||||||
@ -415,7 +420,7 @@
|
|||||||
"callMessageEnded": "Call lasted {}",
|
"callMessageEnded": "Call lasted {}",
|
||||||
"callMessageStarted": "Call started",
|
"callMessageStarted": "Call started",
|
||||||
"dailyCheckIn": "Check In",
|
"dailyCheckIn": "Check In",
|
||||||
"dailyCheckInNone": "You haven't checked in today",
|
"dailyCheckInNone": "You haven't divined today",
|
||||||
"dailyCheckAction": "Check in right now!",
|
"dailyCheckAction": "Check in right now!",
|
||||||
"dailyCheckDetail": "Can't understand the symbol? Master, help me understand it!",
|
"dailyCheckDetail": "Can't understand the symbol? Master, help me understand it!",
|
||||||
"dailyCheckDetailTitle": "{}'s fortune details",
|
"dailyCheckDetailTitle": "{}'s fortune details",
|
||||||
@ -609,5 +614,56 @@
|
|||||||
"other": "{} Source Points"
|
"other": "{} Source Points"
|
||||||
},
|
},
|
||||||
"aiThinkingProcess": "AI Thinking Process",
|
"aiThinkingProcess": "AI Thinking Process",
|
||||||
"accountSettingsApplied": "Account settings have been applied."
|
"accountSettingsApplied": "Account settings have been applied.",
|
||||||
|
"trayMenuExit": "Exit",
|
||||||
|
"postQuestionUnanswered": "Unanswered Question",
|
||||||
|
"postQuestionUnansweredWithReward": "Unanswered Question, reward source points {}",
|
||||||
|
"postQuestionAnswered": "Answered Question",
|
||||||
|
"postQuestionAnswerSelect": "Select as Answer",
|
||||||
|
"postQuestionAnswerSelected": "Answer has been selected, reward has been applied.",
|
||||||
|
"postVideoUpload": "Upload Video",
|
||||||
|
"realmJoin": "Join Realm",
|
||||||
|
"realmCommunityHint": "This realm is a community realm, you can freely join.",
|
||||||
|
"realmCommunityPublicChannelsHint": "The public channels in this realm",
|
||||||
|
"realmJoined": "Joined realm {}.",
|
||||||
|
"join": "Join",
|
||||||
|
"pollEditorNew": "New Poll",
|
||||||
|
"pollEditorEdit": "Edit Poll",
|
||||||
|
"pollEditorDelete": "Delete Poll",
|
||||||
|
"pollEditorDeleteDescription": "Are you sure you want to delete this poll? This operation is irreversible.",
|
||||||
|
"pollEditorUnlink": "Unlink Poll",
|
||||||
|
"pollOptionAdd": "Add Option",
|
||||||
|
"pollOptionName": "Option Name",
|
||||||
|
"pollLinkExisting": "Link existing poll",
|
||||||
|
"pollAnswered": "Answered the poll.",
|
||||||
|
"pollVotes": {
|
||||||
|
"one": "{} vote",
|
||||||
|
"other": "{} votes"
|
||||||
|
},
|
||||||
|
"publisherDelete": "Delete Publisher {}",
|
||||||
|
"publisherDeleteDescription": "Are you sure you want to delete this publisher? This operation is irreversible.",
|
||||||
|
"channelIsPublic": "Public Channel",
|
||||||
|
"channelIsPublicDescription": "The channel is public, anyone can join.",
|
||||||
|
"channelIsCommunity": "Community Channel",
|
||||||
|
"channelIsCommunityDescription": "Currently, community channel has nothing special yet.",
|
||||||
|
"realmIsPublic": "Public Realm",
|
||||||
|
"realmIsPublicDescription": "The realm is public, anyone can join.",
|
||||||
|
"realmIsCommunity": "Community Realm",
|
||||||
|
"realmIsCommunityDescription": "Community realm will be displayed on the discover page.",
|
||||||
|
"realmLeave": "Leave Realm",
|
||||||
|
"realmLeaveDescription": "Leave the current realm and delete the realm's identity.",
|
||||||
|
"checkInResultTier1": "Worst",
|
||||||
|
"checkInResultTier2": "Worse",
|
||||||
|
"checkInResultTier3": "Normal",
|
||||||
|
"checkInResultTier4": "Better",
|
||||||
|
"checkInResultTier5": "Best",
|
||||||
|
"flagPostAction": "Flag the Post",
|
||||||
|
"flagPost": "Flag objectionable content",
|
||||||
|
"flagPostDescription": "If flagged users takes 50% or more of the views, the post will be collapsed. You cannot revoke the action.",
|
||||||
|
"flaggedPost": "Post has been flagged.",
|
||||||
|
"postViews": {
|
||||||
|
"zero": "No views",
|
||||||
|
"one": "{} view",
|
||||||
|
"other": "{} views"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
"screenChatNew": "新建聊天频道",
|
"screenChatNew": "新建聊天频道",
|
||||||
"screenRealm": "领域",
|
"screenRealm": "领域",
|
||||||
"screenRealmManage": "编辑领域",
|
"screenRealmManage": "编辑领域",
|
||||||
|
"screenRealmDiscovery": "发现领域",
|
||||||
"screenRealmNew": "新建领域",
|
"screenRealmNew": "新建领域",
|
||||||
"screenNotification": "通知",
|
"screenNotification": "通知",
|
||||||
"screenPostSearch": "搜索帖子",
|
"screenPostSearch": "搜索帖子",
|
||||||
@ -138,9 +139,12 @@
|
|||||||
"fieldPublisherBelongToRealmUnset": "未设置发布者所属领域",
|
"fieldPublisherBelongToRealmUnset": "未设置发布者所属领域",
|
||||||
"writePostTypeStory": "发动态",
|
"writePostTypeStory": "发动态",
|
||||||
"writePostTypeArticle": "写文章",
|
"writePostTypeArticle": "写文章",
|
||||||
|
"writePostTypeQuestion": "提问题",
|
||||||
|
"writePostTypeVideo": "发视频",
|
||||||
"fieldPostPublisher": "帖子发布者",
|
"fieldPostPublisher": "帖子发布者",
|
||||||
"fieldPostContent": "发生什么事了?!",
|
"fieldPostContent": "发生什么事了?!",
|
||||||
"fieldPostTitle": "标题",
|
"fieldPostTitle": "标题",
|
||||||
|
"fieldPostQuestionReward": "回答奖励源点",
|
||||||
"fieldPostDescription": "描述",
|
"fieldPostDescription": "描述",
|
||||||
"fieldPostTags": "标签",
|
"fieldPostTags": "标签",
|
||||||
"fieldPostCategories": "分类",
|
"fieldPostCategories": "分类",
|
||||||
@ -327,6 +331,7 @@
|
|||||||
"addAttachmentFromRandomId": "通过访问 ID 链接",
|
"addAttachmentFromRandomId": "通过访问 ID 链接",
|
||||||
"attachmentDetailInfo": "附件详细信息",
|
"attachmentDetailInfo": "附件详细信息",
|
||||||
"attachmentPastedImage": "粘贴的图片",
|
"attachmentPastedImage": "粘贴的图片",
|
||||||
|
"attachmentInsertedImage": "插入的图片",
|
||||||
"attachmentInsertLink": "插入连接",
|
"attachmentInsertLink": "插入连接",
|
||||||
"attachmentSetAsPostThumbnail": "设置为帖子缩略图",
|
"attachmentSetAsPostThumbnail": "设置为帖子缩略图",
|
||||||
"attachmentUnsetAsPostThumbnail": "取消设置为帖子缩略图",
|
"attachmentUnsetAsPostThumbnail": "取消设置为帖子缩略图",
|
||||||
@ -607,5 +612,57 @@
|
|||||||
"other": "{} 源点"
|
"other": "{} 源点"
|
||||||
},
|
},
|
||||||
"aiThinkingProcess": "AI 思考过程",
|
"aiThinkingProcess": "AI 思考过程",
|
||||||
"accountSettingsApplied": "帐号设置已应用。"
|
"accountSettingsApplied": "帐号设置已应用。",
|
||||||
|
"trayMenuExit": "退出",
|
||||||
|
"postQuestionUnanswered": "未解答的问题",
|
||||||
|
"postQuestionUnansweredWithReward": "未解答的问题,悬赏源点 {}",
|
||||||
|
"postQuestionAnswered": "已解答的问题",
|
||||||
|
"postQuestionAnswerTitle": "精选解答",
|
||||||
|
"postQuestionAnswerSelect": "选择解答",
|
||||||
|
"postQuestionAnswerSelected": "解答已选择,奖励已发放。",
|
||||||
|
"postVideoUpload": "上传视频",
|
||||||
|
"realmJoin": "加入领域",
|
||||||
|
"realmCommunityHint": "该领域是一个社区领域,你可以自由加入。",
|
||||||
|
"realmCommunityPublicChannelsHint": "该领域包含的公共频道",
|
||||||
|
"realmJoined": "已加入领域 {}。",
|
||||||
|
"join": "加入",
|
||||||
|
"pollEditorNew": "新投票",
|
||||||
|
"pollEditorEdit": "编辑投票",
|
||||||
|
"pollEditorDelete": "删除投票",
|
||||||
|
"pollEditorDeleteDescription": "你确定要删除这个投票吗?该操作不可撤销。",
|
||||||
|
"pollEditorUnlink": "解除链接",
|
||||||
|
"pollOptionAdd": "添加选项",
|
||||||
|
"pollOptionName": "选项名称",
|
||||||
|
"pollLinkExisting": "链接现有投票",
|
||||||
|
"pollAnswered": "答案已经反馈。",
|
||||||
|
"pollVotes": {
|
||||||
|
"one": "{} 票",
|
||||||
|
"other": "{} 票"
|
||||||
|
},
|
||||||
|
"publisherDelete": "删除发布者 {}",
|
||||||
|
"publisherDeleteDescription": "你确定要删除这个发布者吗?该操作不可撤销。",
|
||||||
|
"channelIsPublic": "公开频道",
|
||||||
|
"channelIsPublicDescription": "该频道是公开的,任何人都可以加入。",
|
||||||
|
"channelIsCommunity": "社区频道",
|
||||||
|
"channelIsCommunityDescription": "目前来说,社区频道还没有什么特别之处。",
|
||||||
|
"realmIsPublic": "公开领域",
|
||||||
|
"realmIsPublicDescription": "该领域是公开的,任何人都可以加入。",
|
||||||
|
"realmIsCommunity": "社区领域",
|
||||||
|
"realmIsCommunityDescription": "社区领域会显示在发现页面上。",
|
||||||
|
"realmLeave": "离开领域",
|
||||||
|
"realmLeaveDescription": "离开当前领域,并且删除领域中的身份。",
|
||||||
|
"checkInResultTier1": "大凶",
|
||||||
|
"checkInResultTier2": "凶",
|
||||||
|
"checkInResultTier3": "中平",
|
||||||
|
"checkInResultTier4": "吉",
|
||||||
|
"checkInResultTier5": "大吉",
|
||||||
|
"flagPostAction": "吹哨",
|
||||||
|
"flagPost": "吹哨不良内容",
|
||||||
|
"flagPostDescription": "吹哨不良内容,如果吹哨用户占浏览量的 50% 或以上,则帖子会被折叠。吹哨后不可撤销。",
|
||||||
|
"flaggedPost": "哨子已经吹响。",
|
||||||
|
"postViews": {
|
||||||
|
"zero": "{} 次浏览",
|
||||||
|
"one": "{} 次浏览",
|
||||||
|
"other": "{} 次浏览"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
"screenChatNew": "新建聊天頻道",
|
"screenChatNew": "新建聊天頻道",
|
||||||
"screenRealm": "領域",
|
"screenRealm": "領域",
|
||||||
"screenRealmManage": "編輯領域",
|
"screenRealmManage": "編輯領域",
|
||||||
|
"screenRealmDiscovery": "發現領域",
|
||||||
"screenRealmNew": "新建領域",
|
"screenRealmNew": "新建領域",
|
||||||
"screenNotification": "通知",
|
"screenNotification": "通知",
|
||||||
"screenPostSearch": "搜索帖子",
|
"screenPostSearch": "搜索帖子",
|
||||||
@ -138,9 +139,12 @@
|
|||||||
"fieldPublisherBelongToRealmUnset": "未設置發佈者所屬領域",
|
"fieldPublisherBelongToRealmUnset": "未設置發佈者所屬領域",
|
||||||
"writePostTypeStory": "發動態",
|
"writePostTypeStory": "發動態",
|
||||||
"writePostTypeArticle": "寫文章",
|
"writePostTypeArticle": "寫文章",
|
||||||
|
"writePostTypeQuestion": "提問題",
|
||||||
|
"writePostTypeVideo": "發視頻",
|
||||||
"fieldPostPublisher": "帖子發佈者",
|
"fieldPostPublisher": "帖子發佈者",
|
||||||
"fieldPostContent": "發生什麼事了?!",
|
"fieldPostContent": "發生什麼事了?!",
|
||||||
"fieldPostTitle": "標題",
|
"fieldPostTitle": "標題",
|
||||||
|
"fieldPostQuestionReward": "回答獎勵源點",
|
||||||
"fieldPostDescription": "描述",
|
"fieldPostDescription": "描述",
|
||||||
"fieldPostTags": "標籤",
|
"fieldPostTags": "標籤",
|
||||||
"fieldPostCategories": "分類",
|
"fieldPostCategories": "分類",
|
||||||
@ -327,6 +331,7 @@
|
|||||||
"addAttachmentFromRandomId": "通過訪問 ID 鏈接",
|
"addAttachmentFromRandomId": "通過訪問 ID 鏈接",
|
||||||
"attachmentDetailInfo": "附件詳細信息",
|
"attachmentDetailInfo": "附件詳細信息",
|
||||||
"attachmentPastedImage": "粘貼的圖片",
|
"attachmentPastedImage": "粘貼的圖片",
|
||||||
|
"attachmentInsertedImage": "插入的圖片",
|
||||||
"attachmentInsertLink": "插入連接",
|
"attachmentInsertLink": "插入連接",
|
||||||
"attachmentSetAsPostThumbnail": "設置為帖子縮略圖",
|
"attachmentSetAsPostThumbnail": "設置為帖子縮略圖",
|
||||||
"attachmentUnsetAsPostThumbnail": "取消設置為帖子縮略圖",
|
"attachmentUnsetAsPostThumbnail": "取消設置為帖子縮略圖",
|
||||||
@ -607,5 +612,57 @@
|
|||||||
"other": "{} 源點"
|
"other": "{} 源點"
|
||||||
},
|
},
|
||||||
"aiThinkingProcess": "AI 思考過程",
|
"aiThinkingProcess": "AI 思考過程",
|
||||||
"accountSettingsApplied": "帳號設置已應用。"
|
"accountSettingsApplied": "帳號設置已應用。",
|
||||||
|
"trayMenuExit": "退出",
|
||||||
|
"postQuestionUnanswered": "未解答的問題",
|
||||||
|
"postQuestionUnansweredWithReward": "未解答的問題,懸賞源點 {}",
|
||||||
|
"postQuestionAnswered": "已解答的問題",
|
||||||
|
"postQuestionAnswerTitle": "精選解答",
|
||||||
|
"postQuestionAnswerSelect": "選擇解答",
|
||||||
|
"postQuestionAnswerSelected": "解答已選擇,獎勵已發放。",
|
||||||
|
"postVideoUpload": "上傳視頻",
|
||||||
|
"realmJoin": "加入領域",
|
||||||
|
"realmCommunityHint": "該領域是一個社區領域,你可以自由加入。",
|
||||||
|
"realmCommunityPublicChannelsHint": "該領域包含的公共頻道",
|
||||||
|
"realmJoined": "已加入領域 {}。",
|
||||||
|
"join": "加入",
|
||||||
|
"pollEditorNew": "新投票",
|
||||||
|
"pollEditorEdit": "編輯投票",
|
||||||
|
"pollEditorDelete": "刪除投票",
|
||||||
|
"pollEditorDeleteDescription": "你確定要刪除這個投票嗎?該操作不可撤銷。",
|
||||||
|
"pollEditorUnlink": "解除鏈接",
|
||||||
|
"pollOptionAdd": "添加選項",
|
||||||
|
"pollOptionName": "選項名稱",
|
||||||
|
"pollLinkExisting": "鏈接現有投票",
|
||||||
|
"pollAnswered": "答案已經反饋。",
|
||||||
|
"pollVotes": {
|
||||||
|
"one": "{} 票",
|
||||||
|
"other": "{} 票"
|
||||||
|
},
|
||||||
|
"publisherDelete": "刪除發佈者 {}",
|
||||||
|
"publisherDeleteDescription": "你確定要刪除這個發佈者嗎?該操作不可撤銷。",
|
||||||
|
"channelIsPublic": "公開頻道",
|
||||||
|
"channelIsPublicDescription": "該頻道是公開的,任何人都可以加入。",
|
||||||
|
"channelIsCommunity": "社區頻道",
|
||||||
|
"channelIsCommunityDescription": "目前來説,社區頻道還沒有什麼特別之處。",
|
||||||
|
"realmIsPublic": "公開領域",
|
||||||
|
"realmIsPublicDescription": "該領域是公開的,任何人都可以加入。",
|
||||||
|
"realmIsCommunity": "社區領域",
|
||||||
|
"realmIsCommunityDescription": "社區領域會顯示在發現頁面上。",
|
||||||
|
"realmLeave": "離開領域",
|
||||||
|
"realmLeaveDescription": "離開當前領域,並且刪除領域中的身份。",
|
||||||
|
"checkInResultTier1": "大凶",
|
||||||
|
"checkInResultTier2": "兇",
|
||||||
|
"checkInResultTier3": "中平",
|
||||||
|
"checkInResultTier4": "吉",
|
||||||
|
"checkInResultTier5": "大吉",
|
||||||
|
"flagPostAction": "吹哨",
|
||||||
|
"flagPost": "吹哨不良內容",
|
||||||
|
"flagPostDescription": "吹哨不良內容,如果吹哨用户佔瀏覽量的 50% 或以上,則帖子會被摺疊。吹哨後不可撤銷。",
|
||||||
|
"flaggedPost": "哨子已經吹響。",
|
||||||
|
"postViews": {
|
||||||
|
"zero": "{} 次瀏覽",
|
||||||
|
"one": "{} 次瀏覽",
|
||||||
|
"other": "{} 次瀏覽"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
"screenChatNew": "新建聊天頻道",
|
"screenChatNew": "新建聊天頻道",
|
||||||
"screenRealm": "領域",
|
"screenRealm": "領域",
|
||||||
"screenRealmManage": "編輯領域",
|
"screenRealmManage": "編輯領域",
|
||||||
|
"screenRealmDiscovery": "發現領域",
|
||||||
"screenRealmNew": "新建領域",
|
"screenRealmNew": "新建領域",
|
||||||
"screenNotification": "通知",
|
"screenNotification": "通知",
|
||||||
"screenPostSearch": "搜索帖子",
|
"screenPostSearch": "搜索帖子",
|
||||||
@ -138,9 +139,12 @@
|
|||||||
"fieldPublisherBelongToRealmUnset": "未設置發佈者所屬領域",
|
"fieldPublisherBelongToRealmUnset": "未設置發佈者所屬領域",
|
||||||
"writePostTypeStory": "發動態",
|
"writePostTypeStory": "發動態",
|
||||||
"writePostTypeArticle": "寫文章",
|
"writePostTypeArticle": "寫文章",
|
||||||
|
"writePostTypeQuestion": "提問題",
|
||||||
|
"writePostTypeVideo": "發視頻",
|
||||||
"fieldPostPublisher": "帖子發佈者",
|
"fieldPostPublisher": "帖子發佈者",
|
||||||
"fieldPostContent": "發生什麼事了?!",
|
"fieldPostContent": "發生什麼事了?!",
|
||||||
"fieldPostTitle": "標題",
|
"fieldPostTitle": "標題",
|
||||||
|
"fieldPostQuestionReward": "回答獎勵源點",
|
||||||
"fieldPostDescription": "描述",
|
"fieldPostDescription": "描述",
|
||||||
"fieldPostTags": "標籤",
|
"fieldPostTags": "標籤",
|
||||||
"fieldPostCategories": "分類",
|
"fieldPostCategories": "分類",
|
||||||
@ -327,6 +331,7 @@
|
|||||||
"addAttachmentFromRandomId": "通過訪問 ID 鏈接",
|
"addAttachmentFromRandomId": "通過訪問 ID 鏈接",
|
||||||
"attachmentDetailInfo": "附件詳細信息",
|
"attachmentDetailInfo": "附件詳細信息",
|
||||||
"attachmentPastedImage": "粘貼的圖片",
|
"attachmentPastedImage": "粘貼的圖片",
|
||||||
|
"attachmentInsertedImage": "插入的圖片",
|
||||||
"attachmentInsertLink": "插入連接",
|
"attachmentInsertLink": "插入連接",
|
||||||
"attachmentSetAsPostThumbnail": "設置為帖子縮略圖",
|
"attachmentSetAsPostThumbnail": "設置為帖子縮略圖",
|
||||||
"attachmentUnsetAsPostThumbnail": "取消設置為帖子縮略圖",
|
"attachmentUnsetAsPostThumbnail": "取消設置為帖子縮略圖",
|
||||||
@ -607,5 +612,57 @@
|
|||||||
"other": "{} 源點"
|
"other": "{} 源點"
|
||||||
},
|
},
|
||||||
"aiThinkingProcess": "AI 思考過程",
|
"aiThinkingProcess": "AI 思考過程",
|
||||||
"accountSettingsApplied": "帳號設置已應用。"
|
"accountSettingsApplied": "帳號設置已應用。",
|
||||||
|
"trayMenuExit": "退出",
|
||||||
|
"postQuestionUnanswered": "未解答的問題",
|
||||||
|
"postQuestionUnansweredWithReward": "未解答的問題,懸賞源點 {}",
|
||||||
|
"postQuestionAnswered": "已解答的問題",
|
||||||
|
"postQuestionAnswerTitle": "精選解答",
|
||||||
|
"postQuestionAnswerSelect": "選擇解答",
|
||||||
|
"postQuestionAnswerSelected": "解答已選擇,獎勵已發放。",
|
||||||
|
"postVideoUpload": "上傳視頻",
|
||||||
|
"realmJoin": "加入領域",
|
||||||
|
"realmCommunityHint": "該領域是一個社區領域,你可以自由加入。",
|
||||||
|
"realmCommunityPublicChannelsHint": "該領域包含的公共頻道",
|
||||||
|
"realmJoined": "已加入領域 {}。",
|
||||||
|
"join": "加入",
|
||||||
|
"pollEditorNew": "新投票",
|
||||||
|
"pollEditorEdit": "編輯投票",
|
||||||
|
"pollEditorDelete": "刪除投票",
|
||||||
|
"pollEditorDeleteDescription": "你確定要刪除這個投票嗎?該操作不可撤銷。",
|
||||||
|
"pollEditorUnlink": "解除鏈接",
|
||||||
|
"pollOptionAdd": "添加選項",
|
||||||
|
"pollOptionName": "選項名稱",
|
||||||
|
"pollLinkExisting": "鏈接現有投票",
|
||||||
|
"pollAnswered": "答案已經反饋。",
|
||||||
|
"pollVotes": {
|
||||||
|
"one": "{} 票",
|
||||||
|
"other": "{} 票"
|
||||||
|
},
|
||||||
|
"publisherDelete": "刪除發佈者 {}",
|
||||||
|
"publisherDeleteDescription": "你確定要刪除這個發佈者嗎?該操作不可撤銷。",
|
||||||
|
"channelIsPublic": "公開頻道",
|
||||||
|
"channelIsPublicDescription": "該頻道是公開的,任何人都可以加入。",
|
||||||
|
"channelIsCommunity": "社區頻道",
|
||||||
|
"channelIsCommunityDescription": "目前來說,社區頻道還沒有什麼特別之處。",
|
||||||
|
"realmIsPublic": "公開領域",
|
||||||
|
"realmIsPublicDescription": "該領域是公開的,任何人都可以加入。",
|
||||||
|
"realmIsCommunity": "社區領域",
|
||||||
|
"realmIsCommunityDescription": "社區領域會顯示在發現頁面上。",
|
||||||
|
"realmLeave": "離開領域",
|
||||||
|
"realmLeaveDescription": "離開當前領域,並且刪除領域中的身份。",
|
||||||
|
"checkInResultTier1": "大凶",
|
||||||
|
"checkInResultTier2": "兇",
|
||||||
|
"checkInResultTier3": "中平",
|
||||||
|
"checkInResultTier4": "吉",
|
||||||
|
"checkInResultTier5": "大吉",
|
||||||
|
"flagPostAction": "吹哨",
|
||||||
|
"flagPost": "吹哨不良內容",
|
||||||
|
"flagPostDescription": "吹哨不良內容,如果吹哨用戶佔瀏覽量的 50% 或以上,則帖子會被摺疊。吹哨後不可撤銷。",
|
||||||
|
"flaggedPost": "哨子已經吹響。",
|
||||||
|
"postViews": {
|
||||||
|
"zero": "{} 次瀏覽",
|
||||||
|
"one": "{} 次瀏覽",
|
||||||
|
"other": "{} 次瀏覽"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
14
debian/debian.yml
vendored
Normal file
14
debian/debian.yml
vendored
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
flutter_app:
|
||||||
|
command: surface
|
||||||
|
arch: x64
|
||||||
|
parent: /usr/local/lib
|
||||||
|
nonInteractive: false
|
||||||
|
|
||||||
|
control:
|
||||||
|
Package: solian
|
||||||
|
Version: 2.3.2
|
||||||
|
Architecture: amd64
|
||||||
|
Priority: optional
|
||||||
|
Depends: mpv keybinder-3.0
|
||||||
|
Maintainer: Solsynth LLC
|
||||||
|
Description: The Solar Network Desktop Application
|
9
debian/gui/surface.desktop
vendored
Normal file
9
debian/gui/surface.desktop
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
[Desktop Entry]
|
||||||
|
Version=2.3.2
|
||||||
|
Name=Solian
|
||||||
|
GenericName=Solian
|
||||||
|
Comment=The Solar Network Desktop Application
|
||||||
|
Terminal=false
|
||||||
|
Type=Application
|
||||||
|
Categories=Social Networking
|
||||||
|
Keywords=social;social network;chat;solar network
|
23
debian/gui/surface.svg
vendored
Normal file
23
debian/gui/surface.svg
vendored
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 232 KiB |
@ -2,7 +2,6 @@ PODS:
|
|||||||
- Alamofire (5.10.2)
|
- Alamofire (5.10.2)
|
||||||
- connectivity_plus (0.0.1):
|
- connectivity_plus (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FlutterMacOS
|
|
||||||
- croppy (0.0.1):
|
- croppy (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- device_info_plus (0.0.1):
|
- device_info_plus (0.0.1):
|
||||||
@ -43,58 +42,58 @@ PODS:
|
|||||||
- Flutter
|
- Flutter
|
||||||
- file_saver (0.0.1):
|
- file_saver (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- Firebase/Analytics (11.6.0):
|
- Firebase/Analytics (11.7.0):
|
||||||
- Firebase/Core
|
- Firebase/Core
|
||||||
- Firebase/Core (11.6.0):
|
- Firebase/Core (11.7.0):
|
||||||
- Firebase/CoreOnly
|
- Firebase/CoreOnly
|
||||||
- FirebaseAnalytics (~> 11.6.0)
|
- FirebaseAnalytics (~> 11.7.0)
|
||||||
- Firebase/CoreOnly (11.6.0):
|
- Firebase/CoreOnly (11.7.0):
|
||||||
- FirebaseCore (~> 11.6.0)
|
- FirebaseCore (~> 11.7.0)
|
||||||
- Firebase/Messaging (11.6.0):
|
- Firebase/Messaging (11.7.0):
|
||||||
- Firebase/CoreOnly
|
- Firebase/CoreOnly
|
||||||
- FirebaseMessaging (~> 11.6.0)
|
- FirebaseMessaging (~> 11.7.0)
|
||||||
- firebase_analytics (11.4.1):
|
- firebase_analytics (11.4.2):
|
||||||
- Firebase/Analytics (= 11.6.0)
|
- Firebase/Analytics (= 11.7.0)
|
||||||
- firebase_core
|
- firebase_core
|
||||||
- Flutter
|
- Flutter
|
||||||
- firebase_core (3.10.1):
|
- firebase_core (3.11.0):
|
||||||
- Firebase/CoreOnly (= 11.6.0)
|
- Firebase/CoreOnly (= 11.7.0)
|
||||||
- Flutter
|
- Flutter
|
||||||
- firebase_messaging (15.2.1):
|
- firebase_messaging (15.2.2):
|
||||||
- Firebase/Messaging (= 11.6.0)
|
- Firebase/Messaging (= 11.7.0)
|
||||||
- firebase_core
|
- firebase_core
|
||||||
- Flutter
|
- Flutter
|
||||||
- FirebaseAnalytics (11.6.0):
|
- FirebaseAnalytics (11.7.0):
|
||||||
- FirebaseAnalytics/AdIdSupport (= 11.6.0)
|
- FirebaseAnalytics/AdIdSupport (= 11.7.0)
|
||||||
- FirebaseCore (~> 11.6.0)
|
- FirebaseCore (~> 11.7.0)
|
||||||
- FirebaseInstallations (~> 11.0)
|
- FirebaseInstallations (~> 11.0)
|
||||||
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
|
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
|
||||||
- GoogleUtilities/MethodSwizzler (~> 8.0)
|
- GoogleUtilities/MethodSwizzler (~> 8.0)
|
||||||
- GoogleUtilities/Network (~> 8.0)
|
- GoogleUtilities/Network (~> 8.0)
|
||||||
- "GoogleUtilities/NSData+zlib (~> 8.0)"
|
- "GoogleUtilities/NSData+zlib (~> 8.0)"
|
||||||
- nanopb (~> 3.30910.0)
|
- nanopb (~> 3.30910.0)
|
||||||
- FirebaseAnalytics/AdIdSupport (11.6.0):
|
- FirebaseAnalytics/AdIdSupport (11.7.0):
|
||||||
- FirebaseCore (~> 11.6.0)
|
- FirebaseCore (~> 11.7.0)
|
||||||
- FirebaseInstallations (~> 11.0)
|
- FirebaseInstallations (~> 11.0)
|
||||||
- GoogleAppMeasurement (= 11.6.0)
|
- GoogleAppMeasurement (= 11.7.0)
|
||||||
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
|
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
|
||||||
- GoogleUtilities/MethodSwizzler (~> 8.0)
|
- GoogleUtilities/MethodSwizzler (~> 8.0)
|
||||||
- GoogleUtilities/Network (~> 8.0)
|
- GoogleUtilities/Network (~> 8.0)
|
||||||
- "GoogleUtilities/NSData+zlib (~> 8.0)"
|
- "GoogleUtilities/NSData+zlib (~> 8.0)"
|
||||||
- nanopb (~> 3.30910.0)
|
- nanopb (~> 3.30910.0)
|
||||||
- FirebaseCore (11.6.0):
|
- FirebaseCore (11.7.0):
|
||||||
- FirebaseCoreInternal (~> 11.6.0)
|
- FirebaseCoreInternal (~> 11.7.0)
|
||||||
- GoogleUtilities/Environment (~> 8.0)
|
- GoogleUtilities/Environment (~> 8.0)
|
||||||
- GoogleUtilities/Logger (~> 8.0)
|
- GoogleUtilities/Logger (~> 8.0)
|
||||||
- FirebaseCoreInternal (11.6.0):
|
- FirebaseCoreInternal (11.7.0):
|
||||||
- "GoogleUtilities/NSData+zlib (~> 8.0)"
|
- "GoogleUtilities/NSData+zlib (~> 8.0)"
|
||||||
- FirebaseInstallations (11.6.0):
|
- FirebaseInstallations (11.7.0):
|
||||||
- FirebaseCore (~> 11.6.0)
|
- FirebaseCore (~> 11.7.0)
|
||||||
- GoogleUtilities/Environment (~> 8.0)
|
- GoogleUtilities/Environment (~> 8.0)
|
||||||
- GoogleUtilities/UserDefaults (~> 8.0)
|
- GoogleUtilities/UserDefaults (~> 8.0)
|
||||||
- PromisesObjC (~> 2.4)
|
- PromisesObjC (~> 2.4)
|
||||||
- FirebaseMessaging (11.6.0):
|
- FirebaseMessaging (11.7.0):
|
||||||
- FirebaseCore (~> 11.6.0)
|
- FirebaseCore (~> 11.7.0)
|
||||||
- FirebaseInstallations (~> 11.0)
|
- FirebaseInstallations (~> 11.0)
|
||||||
- GoogleDataTransport (~> 10.0)
|
- GoogleDataTransport (~> 10.0)
|
||||||
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
|
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
|
||||||
@ -123,21 +122,21 @@ PODS:
|
|||||||
- gal (1.0.0):
|
- gal (1.0.0):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- GoogleAppMeasurement (11.6.0):
|
- GoogleAppMeasurement (11.7.0):
|
||||||
- GoogleAppMeasurement/AdIdSupport (= 11.6.0)
|
- GoogleAppMeasurement/AdIdSupport (= 11.7.0)
|
||||||
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
|
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
|
||||||
- GoogleUtilities/MethodSwizzler (~> 8.0)
|
- GoogleUtilities/MethodSwizzler (~> 8.0)
|
||||||
- GoogleUtilities/Network (~> 8.0)
|
- GoogleUtilities/Network (~> 8.0)
|
||||||
- "GoogleUtilities/NSData+zlib (~> 8.0)"
|
- "GoogleUtilities/NSData+zlib (~> 8.0)"
|
||||||
- nanopb (~> 3.30910.0)
|
- nanopb (~> 3.30910.0)
|
||||||
- GoogleAppMeasurement/AdIdSupport (11.6.0):
|
- GoogleAppMeasurement/AdIdSupport (11.7.0):
|
||||||
- GoogleAppMeasurement/WithoutAdIdSupport (= 11.6.0)
|
- GoogleAppMeasurement/WithoutAdIdSupport (= 11.7.0)
|
||||||
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
|
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
|
||||||
- GoogleUtilities/MethodSwizzler (~> 8.0)
|
- GoogleUtilities/MethodSwizzler (~> 8.0)
|
||||||
- GoogleUtilities/Network (~> 8.0)
|
- GoogleUtilities/Network (~> 8.0)
|
||||||
- "GoogleUtilities/NSData+zlib (~> 8.0)"
|
- "GoogleUtilities/NSData+zlib (~> 8.0)"
|
||||||
- nanopb (~> 3.30910.0)
|
- nanopb (~> 3.30910.0)
|
||||||
- GoogleAppMeasurement/WithoutAdIdSupport (11.6.0):
|
- GoogleAppMeasurement/WithoutAdIdSupport (11.7.0):
|
||||||
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
|
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
|
||||||
- GoogleUtilities/MethodSwizzler (~> 8.0)
|
- GoogleUtilities/MethodSwizzler (~> 8.0)
|
||||||
- GoogleUtilities/Network (~> 8.0)
|
- GoogleUtilities/Network (~> 8.0)
|
||||||
@ -179,8 +178,8 @@ PODS:
|
|||||||
- Flutter
|
- Flutter
|
||||||
- in_app_review (2.0.0):
|
- in_app_review (2.0.0):
|
||||||
- Flutter
|
- Flutter
|
||||||
- Kingfisher (8.1.3)
|
- Kingfisher (8.2.0)
|
||||||
- livekit_client (2.3.5):
|
- livekit_client (2.3.6):
|
||||||
- Flutter
|
- Flutter
|
||||||
- flutter_webrtc
|
- flutter_webrtc
|
||||||
- WebRTC-SDK (= 125.6422.06)
|
- WebRTC-SDK (= 125.6422.06)
|
||||||
@ -237,7 +236,7 @@ PODS:
|
|||||||
|
|
||||||
DEPENDENCIES:
|
DEPENDENCIES:
|
||||||
- Alamofire
|
- Alamofire
|
||||||
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/darwin`)
|
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
|
||||||
- croppy (from `.symlinks/plugins/croppy/ios`)
|
- croppy (from `.symlinks/plugins/croppy/ios`)
|
||||||
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
|
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
|
||||||
- file_picker (from `.symlinks/plugins/file_picker/ios`)
|
- file_picker (from `.symlinks/plugins/file_picker/ios`)
|
||||||
@ -300,7 +299,7 @@ SPEC REPOS:
|
|||||||
|
|
||||||
EXTERNAL SOURCES:
|
EXTERNAL SOURCES:
|
||||||
connectivity_plus:
|
connectivity_plus:
|
||||||
:path: ".symlinks/plugins/connectivity_plus/darwin"
|
:path: ".symlinks/plugins/connectivity_plus/ios"
|
||||||
croppy:
|
croppy:
|
||||||
:path: ".symlinks/plugins/croppy/ios"
|
:path: ".symlinks/plugins/croppy/ios"
|
||||||
device_info_plus:
|
device_info_plus:
|
||||||
@ -374,22 +373,22 @@ EXTERNAL SOURCES:
|
|||||||
|
|
||||||
SPEC CHECKSUMS:
|
SPEC CHECKSUMS:
|
||||||
Alamofire: 7193b3b92c74a07f85569e1a6c4f4237291e7496
|
Alamofire: 7193b3b92c74a07f85569e1a6c4f4237291e7496
|
||||||
connectivity_plus: 18382e7311ba19efcaee94442b23b32507b20695
|
connectivity_plus: 2a701ffec2c0ae28a48cf7540e279787e77c447d
|
||||||
croppy: b6199bc8d56bd2e03cc11609d1c47ad9875c1321
|
croppy: b6199bc8d56bd2e03cc11609d1c47ad9875c1321
|
||||||
device_info_plus: bf2e3232933866d73fe290f2942f2156cdd10342
|
device_info_plus: bf2e3232933866d73fe290f2942f2156cdd10342
|
||||||
DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
|
DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
|
||||||
DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
|
DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
|
||||||
file_picker: b159e0c068aef54932bb15dc9fd1571818edaf49
|
file_picker: b159e0c068aef54932bb15dc9fd1571818edaf49
|
||||||
file_saver: 503e386464dbe118f630e17b4c2e1190fa0cf808
|
file_saver: 503e386464dbe118f630e17b4c2e1190fa0cf808
|
||||||
Firebase: 374a441a91ead896215703a674d58cdb3e9d772b
|
Firebase: a64bf6a8546e6eab54f1c715cd6151f39d2329f4
|
||||||
firebase_analytics: 13ea4ad8a42c5060bad7e6694304dabb8b02fe7e
|
firebase_analytics: 7236e6115c1b4e62c2270faa29c052a317e31107
|
||||||
firebase_core: e2aa06dbd854d961f8ce46c2e20933bee1bf2d2b
|
firebase_core: aa979ae726f00b3ef4ccf59dfb96170af84efbd4
|
||||||
firebase_messaging: 96cf6d67121b3f39746b2a4f29a26c0eee4af70e
|
firebase_messaging: 3af84b6a90aeac4d7a67fbf4c43a91e7083bea1f
|
||||||
FirebaseAnalytics: 7114c698cac995602e3b1b96663473e50d54d6e7
|
FirebaseAnalytics: bc9e565af9044ba8d6c6e4157e4edca8e5fdf7ec
|
||||||
FirebaseCore: 48b0dd707581cf9c1a1220da68223fb0a562afaa
|
FirebaseCore: 3227e35f4197a924206fbcdc0349325baf4f5de4
|
||||||
FirebaseCoreInternal: d98ab91e2d80a56d7b246856a8885443b302c0c2
|
FirebaseCoreInternal: d6c17dafc8dc33614733a8b52df78fcb4394c881
|
||||||
FirebaseInstallations: efc0946fc756e4d22d8113f7c761948120322e8c
|
FirebaseInstallations: 9347e719c3d52d8d7b9074b2c32407dd027305e9
|
||||||
FirebaseMessaging: e1aca1fcc23e8b9eddb0e33f375ff90944623021
|
FirebaseMessaging: 00ece041b71ddb52a2862ffdee73fb6e9824bd0c
|
||||||
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
||||||
flutter_app_update: 65f61da626cb111d1b24674abc4b01728d7723bc
|
flutter_app_update: 65f61da626cb111d1b24674abc4b01728d7723bc
|
||||||
flutter_inappwebview_ios: 6f63631e2c62a7c350263b13fa5427aedefe81d4
|
flutter_inappwebview_ios: 6f63631e2c62a7c350263b13fa5427aedefe81d4
|
||||||
@ -397,14 +396,14 @@ SPEC CHECKSUMS:
|
|||||||
flutter_udid: b2417673f287ee62817a1de3d1643f47b9f508ab
|
flutter_udid: b2417673f287ee62817a1de3d1643f47b9f508ab
|
||||||
flutter_webrtc: 90260f83024b1b96d239a575ea4e3708e79344d1
|
flutter_webrtc: 90260f83024b1b96d239a575ea4e3708e79344d1
|
||||||
gal: 6a522c75909f1244732d4596d11d6a2f86ff37a5
|
gal: 6a522c75909f1244732d4596d11d6a2f86ff37a5
|
||||||
GoogleAppMeasurement: 6a9e6317b6a6d810ad03d4a66564ca6c4c5818a3
|
GoogleAppMeasurement: 0471a5b5bff51f3a91b1e76df22c952d04c63967
|
||||||
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
|
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
|
||||||
GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d
|
GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d
|
||||||
home_widget: 0434835a4c9a75704264feff6be17ea40e0f0d57
|
home_widget: 0434835a4c9a75704264feff6be17ea40e0f0d57
|
||||||
image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1
|
image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1
|
||||||
in_app_review: a31b5257259646ea78e0e35fc914979b0031d011
|
in_app_review: a31b5257259646ea78e0e35fc914979b0031d011
|
||||||
Kingfisher: f2af9028b16baf9dc6c07c570072bc41cbf009ef
|
Kingfisher: 323e5c4ec7983aaace12af655a7b51a7f88a599d
|
||||||
livekit_client: dcc5fd47ba69c98fc6baeb12e862c9d43807d976
|
livekit_client: 148b2cf67a09aaf475ba8e5bf1667fe10dc35f81
|
||||||
media_kit_libs_ios_video: a5fe24bc7875ccd6378a0978c13185e1344651c1
|
media_kit_libs_ios_video: a5fe24bc7875ccd6378a0978c13185e1344651c1
|
||||||
media_kit_native_event_loop: e6b2ab20cf0746eb1c33be961fcf79667304fa2a
|
media_kit_native_event_loop: e6b2ab20cf0746eb1c33be961fcf79667304fa2a
|
||||||
media_kit_video: 5da63f157170e5bf303bf85453b7ef6971218a2e
|
media_kit_video: 5da63f157170e5bf303bf85453b7ef6971218a2e
|
||||||
|
@ -123,48 +123,59 @@ class NotificationService: UNNotificationServiceExtension {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let imageIdentifier = metadata["image"] as? String {
|
if let imageIdentifier = metadata["image"] as? String {
|
||||||
attachMedia(to: content, withIdentifier: imageIdentifier, fileType: UTType.jpeg, doScaleDown: true)
|
attachMedia(to: content, withIdentifier: [imageIdentifier], fileType: UTType.jpeg, doScaleDown: true)
|
||||||
} else if let avatarIdentifier = metadata["avatar"] as? String {
|
} else if let avatarIdentifier = metadata["avatar"] as? String {
|
||||||
attachMedia(to: content, withIdentifier: avatarIdentifier, fileType: UTType.jpeg, doScaleDown: true)
|
attachMedia(to: content, withIdentifier: [avatarIdentifier], fileType: UTType.jpeg, doScaleDown: true)
|
||||||
|
} else if let imagesIdentifier = metadata["images"] as? Array<String> {
|
||||||
|
attachMedia(to: content, withIdentifier: imagesIdentifier, fileType: UTType.jpeg, doScaleDown: true)
|
||||||
} else {
|
} else {
|
||||||
contentHandler?(content)
|
contentHandler?(content)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func attachMedia(to content: UNMutableNotificationContent, withIdentifier identifier: String, fileType type: UTType?, doScaleDown scaleDown: Bool = false) {
|
private func attachMedia(to content: UNMutableNotificationContent, withIdentifier identifier: Array<String>, fileType type: UTType?, doScaleDown scaleDown: Bool = false) {
|
||||||
let attachmentUrl = getAttachmentUrl(for: identifier)
|
let attachmentUrls = identifier.compactMap { element in
|
||||||
|
return getAttachmentUrl(for: element)
|
||||||
guard let remoteUrl = URL(string: attachmentUrl) else {
|
}
|
||||||
print("Invalid URL for attachment: \(attachmentUrl)")
|
|
||||||
|
guard !attachmentUrls.isEmpty else {
|
||||||
|
print("Invalid URLs for attachments: \(attachmentUrls)")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let targetSize = 800
|
let targetSize = 800
|
||||||
let scaleProcessor = ResizingImageProcessor(referenceSize: CGSize(width: targetSize, height: targetSize), mode: .aspectFit)
|
let scaleProcessor = ResizingImageProcessor(referenceSize: CGSize(width: targetSize, height: targetSize), mode: .aspectFit)
|
||||||
|
|
||||||
KingfisherManager.shared.retrieveImage(with: remoteUrl, options: scaleDown ? [
|
for attachmentUrl in attachmentUrls {
|
||||||
.processor(scaleProcessor)
|
guard let remoteUrl = URL(string: attachmentUrl) else {
|
||||||
] : nil) { [weak self] result in
|
print("Invalid URL for attachment: \(attachmentUrl)")
|
||||||
guard let self = self else { return }
|
continue // Skip this URL and move to the next one
|
||||||
|
}
|
||||||
switch result {
|
|
||||||
case .success(let retrievalResult):
|
KingfisherManager.shared.retrieveImage(with: remoteUrl, options: scaleDown ? [
|
||||||
// The image is either retrieved from cache or downloaded
|
.processor(scaleProcessor)
|
||||||
let tempDirectory = FileManager.default.temporaryDirectory
|
] : nil) { [weak self] result in
|
||||||
let cachedFileUrl = tempDirectory.appendingPathComponent(identifier)
|
guard let self = self else { return }
|
||||||
|
|
||||||
do {
|
switch result {
|
||||||
// Write the image data to a temporary file for UNNotificationAttachment
|
case .success(let retrievalResult):
|
||||||
try retrievalResult.image.pngData()?.write(to: cachedFileUrl)
|
// The image is either retrieved from cache or downloaded
|
||||||
self.attachLocalMedia(to: content, fileType: type?.identifier, from: cachedFileUrl, withIdentifier: identifier)
|
let tempDirectory = FileManager.default.temporaryDirectory
|
||||||
} catch {
|
let cachedFileUrl = tempDirectory.appendingPathComponent(UUID().uuidString) // Unique identifier for each file
|
||||||
print("Failed to write media to temporary file: \(error.localizedDescription)")
|
|
||||||
|
do {
|
||||||
|
// Write the image data to a temporary file for UNNotificationAttachment
|
||||||
|
try retrievalResult.image.pngData()?.write(to: cachedFileUrl)
|
||||||
|
self.attachLocalMedia(to: content, fileType: type?.identifier, from: cachedFileUrl, withIdentifier: attachmentUrl)
|
||||||
|
} catch {
|
||||||
|
print("Failed to write media to temporary file: \(error.localizedDescription)")
|
||||||
|
self.contentHandler?(content)
|
||||||
|
}
|
||||||
|
|
||||||
|
case .failure(let error):
|
||||||
|
print("Failed to retrieve image: \(error.localizedDescription)")
|
||||||
self.contentHandler?(content)
|
self.contentHandler?(content)
|
||||||
}
|
}
|
||||||
|
|
||||||
case .failure(let error):
|
|
||||||
print("Failed to retrieve image: \(error.localizedDescription)")
|
|
||||||
self.contentHandler?(content)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,14 +15,14 @@ struct CheckInProvider: TimelineProvider {
|
|||||||
|
|
||||||
func getSnapshot(in context: Context, completion: @escaping (CheckInEntry) -> ()) {
|
func getSnapshot(in context: Context, completion: @escaping (CheckInEntry) -> ()) {
|
||||||
let prefs = UserDefaults(suiteName: "group.solsynth.solian")
|
let prefs = UserDefaults(suiteName: "group.solsynth.solian")
|
||||||
|
|
||||||
let dateFormatter = DateFormatter()
|
let dateFormatter = DateFormatter()
|
||||||
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSSSS'Z'"
|
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSSSS'Z'"
|
||||||
|
|
||||||
let jsonDecoder = JSONDecoder()
|
let jsonDecoder = JSONDecoder()
|
||||||
jsonDecoder.dateDecodingStrategy = .formatted(dateFormatter)
|
jsonDecoder.dateDecodingStrategy = .formatted(dateFormatter)
|
||||||
jsonDecoder.keyDecodingStrategy = .convertFromSnakeCase
|
jsonDecoder.keyDecodingStrategy = .convertFromSnakeCase
|
||||||
|
|
||||||
let checkInRaw = prefs?.string(forKey: "pas_check_in_record")
|
let checkInRaw = prefs?.string(forKey: "pas_check_in_record")
|
||||||
var checkIn: SolarCheckInRecord?
|
var checkIn: SolarCheckInRecord?
|
||||||
if let checkInRaw = checkInRaw {
|
if let checkInRaw = checkInRaw {
|
||||||
@ -31,7 +31,7 @@ struct CheckInProvider: TimelineProvider {
|
|||||||
checkIn = nil
|
checkIn = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let entry = CheckInEntry(
|
let entry = CheckInEntry(
|
||||||
date: Date(),
|
date: Date(),
|
||||||
checkIn: checkIn
|
checkIn: checkIn
|
||||||
@ -54,11 +54,11 @@ struct CheckInEntry: TimelineEntry {
|
|||||||
|
|
||||||
struct CheckInWidgetEntryView : View {
|
struct CheckInWidgetEntryView : View {
|
||||||
var entry: CheckInProvider.Entry
|
var entry: CheckInProvider.Entry
|
||||||
|
|
||||||
private let resultTierSymbols: [String] = ["大凶", "凶", "中平", "吉", "大吉"]
|
private let resultTierSymbols: [String] = ["Bad", "Poor", "Medium", "Good", "Great"]
|
||||||
|
|
||||||
func checkIn() -> Void {}
|
func checkIn() -> Void {}
|
||||||
|
|
||||||
func seeDetail() -> Void {}
|
func seeDetail() -> Void {}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
@ -68,9 +68,9 @@ struct CheckInWidgetEntryView : View {
|
|||||||
Text(resultTierSymbols[checkIn.resultTier]).font(.system(size: 27, weight: .bold))
|
Text(resultTierSymbols[checkIn.resultTier]).font(.system(size: 27, weight: .bold))
|
||||||
Text("+\(checkIn.resultExperience) EXP").font(.system(size: 15, design: .monospaced))
|
Text("+\(checkIn.resultExperience) EXP").font(.system(size: 15, design: .monospaced))
|
||||||
}.padding(.horizontal, 4)
|
}.padding(.horizontal, 4)
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
HStack {
|
HStack {
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
Text(
|
Text(
|
||||||
@ -82,7 +82,7 @@ struct CheckInWidgetEntryView : View {
|
|||||||
format: .dateTime.day().month()
|
format: .dateTime.day().month()
|
||||||
).font(.system(size: 13))
|
).font(.system(size: 13))
|
||||||
}.padding(.leading, 4)
|
}.padding(.leading, 4)
|
||||||
|
|
||||||
Button("See Detail", systemImage: "arrow.right", action: seeDetail)
|
Button("See Detail", systemImage: "arrow.right", action: seeDetail)
|
||||||
.labelStyle(.iconOnly)
|
.labelStyle(.iconOnly)
|
||||||
.buttonBorderShape(.circle)
|
.buttonBorderShape(.circle)
|
||||||
@ -91,11 +91,11 @@ struct CheckInWidgetEntryView : View {
|
|||||||
} else {
|
} else {
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
Text("Check In").font(.system(size: 19, weight: .bold))
|
Text("Check In").font(.system(size: 19, weight: .bold))
|
||||||
Text("You haven't check in today").font(.system(size: 15))
|
Text("You haven't divined today").font(.system(size: 15))
|
||||||
}.padding(.horizontal, 4)
|
}.padding(.horizontal, 4)
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
HStack(alignment: .bottom) {
|
HStack(alignment: .bottom) {
|
||||||
Button("Check In", systemImage: "checkmark", action: checkIn).labelStyle(.iconOnly).buttonBorderShape(.circle).frame(maxWidth: .infinity, alignment: .trailing)
|
Button("Check In", systemImage: "checkmark", action: checkIn).labelStyle(.iconOnly).buttonBorderShape(.circle).frame(maxWidth: .infinity, alignment: .trailing)
|
||||||
}
|
}
|
||||||
|
@ -71,7 +71,7 @@ class ChatMessageController extends ChangeNotifier {
|
|||||||
resp.data as Map<String, dynamic>,
|
resp.data as Map<String, dynamic>,
|
||||||
);
|
);
|
||||||
|
|
||||||
_wsSubscription = _ws.stream.stream.listen((event) {
|
_wsSubscription = _ws.pk.stream.listen((event) {
|
||||||
switch (event.method) {
|
switch (event.method) {
|
||||||
case 'events.new':
|
case 'events.new':
|
||||||
if (event.payload?['channel_id'] != channel?.id) break;
|
if (event.payload?['channel_id'] != channel?.id) break;
|
||||||
|
@ -16,6 +16,7 @@ import 'package:surface/providers/post.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/types/attachment.dart';
|
import 'package:surface/types/attachment.dart';
|
||||||
|
import 'package:surface/types/poll.dart';
|
||||||
import 'package:surface/types/post.dart';
|
import 'package:surface/types/post.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';
|
||||||
@ -144,6 +145,8 @@ class PostWriteController extends ChangeNotifier {
|
|||||||
static const Map<String, String> kTitleMap = {
|
static const Map<String, String> kTitleMap = {
|
||||||
'stories': 'writePostTypeStory',
|
'stories': 'writePostTypeStory',
|
||||||
'articles': 'writePostTypeArticle',
|
'articles': 'writePostTypeArticle',
|
||||||
|
'questions': 'writePostTypeQuestion',
|
||||||
|
'videos': 'writePostTypeVideo',
|
||||||
};
|
};
|
||||||
|
|
||||||
static const kAttachmentProgressWeight = 0.9;
|
static const kAttachmentProgressWeight = 0.9;
|
||||||
@ -153,6 +156,15 @@ class PostWriteController extends ChangeNotifier {
|
|||||||
final TextEditingController titleController = TextEditingController();
|
final TextEditingController titleController = TextEditingController();
|
||||||
final TextEditingController descriptionController = TextEditingController();
|
final TextEditingController descriptionController = TextEditingController();
|
||||||
final TextEditingController aliasController = TextEditingController();
|
final TextEditingController aliasController = TextEditingController();
|
||||||
|
final TextEditingController rewardController = TextEditingController();
|
||||||
|
|
||||||
|
ContentInsertionConfiguration get contentInsertionConfiguration => ContentInsertionConfiguration(
|
||||||
|
onContentInserted: (KeyboardInsertedContent content) {
|
||||||
|
if (content.hasData) {
|
||||||
|
addAttachments([PostWriteMedia.fromBytes(content.data!, 'attachmentInsertedImage'.tr(), SnMediaType.image)]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
bool _temporarySaveActive = false;
|
bool _temporarySaveActive = false;
|
||||||
|
|
||||||
@ -168,6 +180,7 @@ class PostWriteController extends ChangeNotifier {
|
|||||||
});
|
});
|
||||||
contentController.addListener(() {
|
contentController.addListener(() {
|
||||||
_temporaryPlanSave();
|
_temporaryPlanSave();
|
||||||
|
notifyListeners();
|
||||||
});
|
});
|
||||||
if (doLoadFromTemporary) _temporaryLoad();
|
if (doLoadFromTemporary) _temporaryLoad();
|
||||||
}
|
}
|
||||||
@ -194,6 +207,8 @@ class PostWriteController extends ChangeNotifier {
|
|||||||
PostWriteMedia? thumbnail;
|
PostWriteMedia? thumbnail;
|
||||||
List<PostWriteMedia> attachments = List.empty(growable: true);
|
List<PostWriteMedia> attachments = List.empty(growable: true);
|
||||||
DateTime? publishedAt, publishedUntil;
|
DateTime? publishedAt, publishedUntil;
|
||||||
|
SnAttachment? videoAttachment;
|
||||||
|
SnPoll? poll;
|
||||||
|
|
||||||
Future<void> fetchRelatedPost(
|
Future<void> fetchRelatedPost(
|
||||||
BuildContext context, {
|
BuildContext context, {
|
||||||
@ -214,6 +229,8 @@ class PostWriteController extends ChangeNotifier {
|
|||||||
descriptionController.text = post.body['description'] ?? '';
|
descriptionController.text = post.body['description'] ?? '';
|
||||||
contentController.text = post.body['content'] ?? '';
|
contentController.text = post.body['content'] ?? '';
|
||||||
aliasController.text = post.alias ?? '';
|
aliasController.text = post.alias ?? '';
|
||||||
|
rewardController.text = post.body['reward']?.toString() ?? '';
|
||||||
|
videoAttachment = post.preload?.video;
|
||||||
publishedAt = post.publishedAt;
|
publishedAt = post.publishedAt;
|
||||||
publishedUntil = post.publishedUntil;
|
publishedUntil = post.publishedUntil;
|
||||||
visibleUsers = List.from(post.visibleUsersList ?? [], growable: true);
|
visibleUsers = List.from(post.visibleUsersList ?? [], growable: true);
|
||||||
@ -222,6 +239,7 @@ class PostWriteController extends ChangeNotifier {
|
|||||||
tags = List.from(post.tags.map((ele) => ele.alias), growable: true);
|
tags = List.from(post.tags.map((ele) => ele.alias), growable: true);
|
||||||
categories = List.from(post.categories.map((ele) => ele.alias), growable: true);
|
categories = List.from(post.categories.map((ele) => ele.alias), growable: true);
|
||||||
attachments.addAll(post.preload?.attachments?.map((ele) => PostWriteMedia(ele)) ?? []);
|
attachments.addAll(post.preload?.attachments?.map((ele) => PostWriteMedia(ele)) ?? []);
|
||||||
|
poll = post.preload?.poll;
|
||||||
|
|
||||||
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);
|
||||||
@ -347,6 +365,7 @@ class PostWriteController extends ChangeNotifier {
|
|||||||
if (aliasController.text.isNotEmpty) 'alias': aliasController.text,
|
if (aliasController.text.isNotEmpty) 'alias': aliasController.text,
|
||||||
if (titleController.text.isNotEmpty) 'title': titleController.text,
|
if (titleController.text.isNotEmpty) 'title': titleController.text,
|
||||||
if (descriptionController.text.isNotEmpty) 'description': descriptionController.text,
|
if (descriptionController.text.isNotEmpty) 'description': descriptionController.text,
|
||||||
|
if (rewardController.text.isNotEmpty) 'reward': rewardController.text,
|
||||||
if (thumbnail != null && thumbnail!.attachment != null) 'thumbnail': thumbnail!.attachment!.toJson(),
|
if (thumbnail != null && thumbnail!.attachment != null) 'thumbnail': thumbnail!.attachment!.toJson(),
|
||||||
'attachments':
|
'attachments':
|
||||||
attachments.where((e) => e.attachment != null).map((e) => e.attachment!.toJson()).toList(growable: true),
|
attachments.where((e) => e.attachment != null).map((e) => e.attachment!.toJson()).toList(growable: true),
|
||||||
@ -359,6 +378,7 @@ class PostWriteController extends ChangeNotifier {
|
|||||||
if (publishedUntil != null) 'published_until': publishedAt!.toUtc().toIso8601String(),
|
if (publishedUntil != null) 'published_until': publishedAt!.toUtc().toIso8601String(),
|
||||||
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(),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -375,6 +395,7 @@ class PostWriteController extends ChangeNotifier {
|
|||||||
aliasController.text = data['alias'] ?? '';
|
aliasController.text = data['alias'] ?? '';
|
||||||
titleController.text = data['title'] ?? '';
|
titleController.text = data['title'] ?? '';
|
||||||
descriptionController.text = data['description'] ?? '';
|
descriptionController.text = data['description'] ?? '';
|
||||||
|
rewardController.text = data['reward']?.toString() ?? '';
|
||||||
if (data['thumbnail'] != null) thumbnail = PostWriteMedia(SnAttachment.fromJson(data['thumbnail']));
|
if (data['thumbnail'] != null) thumbnail = PostWriteMedia(SnAttachment.fromJson(data['thumbnail']));
|
||||||
attachments
|
attachments
|
||||||
.addAll(data['attachments'].map((ele) => PostWriteMedia(SnAttachment.fromJson(ele))).cast<PostWriteMedia>());
|
.addAll(data['attachments'].map((ele) => PostWriteMedia(SnAttachment.fromJson(ele))).cast<PostWriteMedia>());
|
||||||
@ -387,6 +408,7 @@ class PostWriteController extends ChangeNotifier {
|
|||||||
if (data['published_until'] != null) publishedUntil = DateTime.tryParse(data['published_until'])?.toLocal();
|
if (data['published_until'] != null) publishedUntil = DateTime.tryParse(data['published_until'])?.toLocal();
|
||||||
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;
|
||||||
temporaryRestored = true;
|
temporaryRestored = true;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
});
|
});
|
||||||
@ -473,6 +495,8 @@ class PostWriteController extends ChangeNotifier {
|
|||||||
progress = kAttachmentProgressWeight;
|
progress = kAttachmentProgressWeight;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
|
||||||
|
final reward = double.tryParse(rewardController.text);
|
||||||
|
|
||||||
// Posting the content
|
// Posting the content
|
||||||
try {
|
try {
|
||||||
final baseProgressVal = progress!;
|
final baseProgressVal = progress!;
|
||||||
@ -498,6 +522,9 @@ class PostWriteController extends ChangeNotifier {
|
|||||||
if (publishedUntil != null) 'published_until': publishedAt!.toUtc().toIso8601String(),
|
if (publishedUntil != null) 'published_until': publishedAt!.toUtc().toIso8601String(),
|
||||||
if (replyingPost != null) 'reply_to': replyingPost!.id,
|
if (replyingPost != null) 'reply_to': replyingPost!.id,
|
||||||
if (repostingPost != null) 'repost_to': repostingPost!.id,
|
if (repostingPost != null) 'repost_to': repostingPost!.id,
|
||||||
|
if (reward != null) 'reward': reward,
|
||||||
|
if (videoAttachment != null) 'video': videoAttachment!.rid,
|
||||||
|
if (poll != null) 'poll': poll!.id,
|
||||||
},
|
},
|
||||||
onSendProgress: (count, total) {
|
onSendProgress: (count, total) {
|
||||||
progress = baseProgressVal + (count / total) * (kPostingProgressWeight / 2);
|
progress = baseProgressVal + (count / total) * (kPostingProgressWeight / 2);
|
||||||
@ -624,6 +651,16 @@ class PostWriteController extends ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setVideoAttachment(SnAttachment? value) {
|
||||||
|
videoAttachment = value;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setPoll(SnPoll? value) {
|
||||||
|
poll = value;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
void reset() {
|
void reset() {
|
||||||
publishedAt = null;
|
publishedAt = null;
|
||||||
publishedUntil = null;
|
publishedUntil = null;
|
||||||
|
150
lib/main.dart
150
lib/main.dart
@ -1,6 +1,7 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:developer';
|
import 'dart:developer';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'package:bitsdojo_window/bitsdojo_window.dart';
|
import 'package:bitsdojo_window/bitsdojo_window.dart';
|
||||||
import 'package:croppy/croppy.dart';
|
import 'package:croppy/croppy.dart';
|
||||||
@ -10,8 +11,10 @@ import 'package:easy_localization_loader/easy_localization_loader.dart';
|
|||||||
import 'package:firebase_core/firebase_core.dart';
|
import 'package:firebase_core/firebase_core.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:hive_flutter/hive_flutter.dart';
|
import 'package:hive_flutter/hive_flutter.dart';
|
||||||
|
import 'package:hotkey_manager/hotkey_manager.dart';
|
||||||
import 'package:package_info_plus/package_info_plus.dart';
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:relative_time/relative_time.dart';
|
import 'package:relative_time/relative_time.dart';
|
||||||
@ -40,9 +43,12 @@ import 'package:surface/types/chat.dart';
|
|||||||
import 'package:surface/types/realm.dart';
|
import 'package:surface/types/realm.dart';
|
||||||
import 'package:flutter_web_plugins/url_strategy.dart' show usePathUrlStrategy;
|
import 'package:flutter_web_plugins/url_strategy.dart' show usePathUrlStrategy;
|
||||||
import 'package:surface/widgets/dialog.dart';
|
import 'package:surface/widgets/dialog.dart';
|
||||||
|
import 'package:tray_manager/tray_manager.dart';
|
||||||
import 'package:version/version.dart';
|
import 'package:version/version.dart';
|
||||||
import 'package:workmanager/workmanager.dart';
|
import 'package:workmanager/workmanager.dart';
|
||||||
import 'package:in_app_review/in_app_review.dart';
|
import 'package:in_app_review/in_app_review.dart';
|
||||||
|
import 'package:image_picker_android/image_picker_android.dart';
|
||||||
|
import 'package:image_picker_platform_interface/image_picker_platform_interface.dart';
|
||||||
|
|
||||||
@pragma('vm:entry-point')
|
@pragma('vm:entry-point')
|
||||||
void appBackgroundDispatcher() {
|
void appBackgroundDispatcher() {
|
||||||
@ -63,20 +69,6 @@ void appBackgroundDispatcher() {
|
|||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
await EasyLocalization.ensureInitialized();
|
|
||||||
|
|
||||||
await Hive.initFlutter();
|
|
||||||
Hive.registerAdapter(SnChannelImplAdapter());
|
|
||||||
Hive.registerAdapter(SnRealmImplAdapter());
|
|
||||||
Hive.registerAdapter(SnChannelMemberImplAdapter());
|
|
||||||
Hive.registerAdapter(SnChatMessageImplAdapter());
|
|
||||||
|
|
||||||
await Firebase.initializeApp(
|
|
||||||
options: DefaultFirebaseOptions.currentPlatform,
|
|
||||||
);
|
|
||||||
|
|
||||||
GoRouter.optionURLReflectsImperativeAPIs = true;
|
|
||||||
usePathUrlStrategy();
|
|
||||||
|
|
||||||
if (!kIsWeb && (Platform.isWindows || Platform.isLinux || Platform.isMacOS)) {
|
if (!kIsWeb && (Platform.isWindows || Platform.isLinux || Platform.isMacOS)) {
|
||||||
doWhenWindowReady(() {
|
doWhenWindowReady(() {
|
||||||
@ -87,6 +79,23 @@ void main() async {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await EasyLocalization.ensureInitialized();
|
||||||
|
|
||||||
|
await Hive.initFlutter();
|
||||||
|
Hive.registerAdapter(SnChannelImplAdapter());
|
||||||
|
Hive.registerAdapter(SnRealmImplAdapter());
|
||||||
|
Hive.registerAdapter(SnChannelMemberImplAdapter());
|
||||||
|
Hive.registerAdapter(SnChatMessageImplAdapter());
|
||||||
|
|
||||||
|
if (kIsWeb && !Platform.isLinux) {
|
||||||
|
await Firebase.initializeApp(
|
||||||
|
options: DefaultFirebaseOptions.currentPlatform,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
GoRouter.optionURLReflectsImperativeAPIs = true;
|
||||||
|
usePathUrlStrategy();
|
||||||
|
|
||||||
if (!kIsWeb && (Platform.isAndroid || Platform.isIOS)) {
|
if (!kIsWeb && (Platform.isAndroid || Platform.isIOS)) {
|
||||||
Workmanager().initialize(
|
Workmanager().initialize(
|
||||||
appBackgroundDispatcher,
|
appBackgroundDispatcher,
|
||||||
@ -103,6 +112,13 @@ void main() async {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!kIsWeb && Platform.isAndroid) {
|
||||||
|
final ImagePickerPlatform imagePickerImplementation = ImagePickerPlatform.instance;
|
||||||
|
if (imagePickerImplementation is ImagePickerAndroid) {
|
||||||
|
imagePickerImplementation.useAndroidPhotoPicker = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
runApp(const SolianApp());
|
runApp(const SolianApp());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -156,8 +172,8 @@ class SolianApp extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
breakpoints: [
|
breakpoints: [
|
||||||
const Breakpoint(start: 0, end: 450, name: MOBILE),
|
const Breakpoint(start: 0, end: 600, name: MOBILE),
|
||||||
const Breakpoint(start: 451, end: 800, name: TABLET),
|
const Breakpoint(start: 601, end: 800, name: TABLET),
|
||||||
const Breakpoint(start: 801, end: 1920, name: DESKTOP),
|
const Breakpoint(start: 801, end: 1920, name: DESKTOP),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
@ -206,7 +222,7 @@ class _AppSplashScreen extends StatefulWidget {
|
|||||||
State<_AppSplashScreen> createState() => _AppSplashScreenState();
|
State<_AppSplashScreen> createState() => _AppSplashScreenState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AppSplashScreenState extends State<_AppSplashScreen> {
|
class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener {
|
||||||
void _tryRequestRating() async {
|
void _tryRequestRating() async {
|
||||||
final prefs = await SharedPreferences.getInstance();
|
final prefs = await SharedPreferences.getInstance();
|
||||||
if (prefs.containsKey('first_boot_time')) {
|
if (prefs.containsKey('first_boot_time')) {
|
||||||
@ -281,6 +297,9 @@ class _AppSplashScreenState extends State<_AppSplashScreen> {
|
|||||||
final notify = context.read<NotificationProvider>();
|
final notify = context.read<NotificationProvider>();
|
||||||
notify.listen();
|
notify.listen();
|
||||||
await notify.registerPushNotifications();
|
await notify.registerPushNotifications();
|
||||||
|
if (!mounted) return;
|
||||||
|
final sticker = context.read<SnStickerProvider>();
|
||||||
|
await sticker.listStickerEagerly();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
await context.showErrorDialog(err);
|
await context.showErrorDialog(err);
|
||||||
@ -291,9 +310,62 @@ class _AppSplashScreenState extends State<_AppSplashScreen> {
|
|||||||
await widgetUpdateRandomPost();
|
await widgetUpdateRandomPost();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _hotkeyInitialization() async {
|
||||||
|
if (kIsWeb) return;
|
||||||
|
|
||||||
|
if (Platform.isMacOS) {
|
||||||
|
HotKey quitHotKey = HotKey(
|
||||||
|
key: PhysicalKeyboardKey.keyQ,
|
||||||
|
modifiers: [HotKeyModifier.meta],
|
||||||
|
scope: HotKeyScope.inapp,
|
||||||
|
);
|
||||||
|
await hotKeyManager.register(quitHotKey, keyUpHandler: (_) {
|
||||||
|
_appLifecycleListener?.dispose();
|
||||||
|
SystemChannels.platform.invokeMethod('SystemNavigator.pop');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _trayInitialization() async {
|
||||||
|
if (kIsWeb || Platform.isAndroid || Platform.isIOS) return;
|
||||||
|
|
||||||
|
final icon = Platform.isWindows ? 'assets/icon/tray-icon.ico' : 'assets/icon/tray-icon.png';
|
||||||
|
final appVersion = await PackageInfo.fromPlatform();
|
||||||
|
|
||||||
|
trayManager.addListener(this);
|
||||||
|
await trayManager.setIcon(icon);
|
||||||
|
|
||||||
|
Menu menu = Menu(
|
||||||
|
items: [
|
||||||
|
MenuItem(
|
||||||
|
key: 'version_label',
|
||||||
|
label: 'Solian ${appVersion.version}+${appVersion.buildNumber}',
|
||||||
|
disabled: true,
|
||||||
|
),
|
||||||
|
MenuItem.separator(),
|
||||||
|
MenuItem(
|
||||||
|
key: 'exit',
|
||||||
|
label: 'trayMenuExit'.tr(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
await trayManager.setContextMenu(menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
AppLifecycleListener? _appLifecycleListener;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
|
||||||
|
if (!kIsWeb && !(Platform.isIOS || Platform.isAndroid)) {
|
||||||
|
_appLifecycleListener = AppLifecycleListener(
|
||||||
|
onExitRequested: _onExitRequested,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
_trayInitialization();
|
||||||
|
_hotkeyInitialization();
|
||||||
_initialize().then((_) {
|
_initialize().then((_) {
|
||||||
_postInitialization();
|
_postInitialization();
|
||||||
_tryRequestRating();
|
_tryRequestRating();
|
||||||
@ -301,6 +373,50 @@ class _AppSplashScreenState extends State<_AppSplashScreen> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<AppExitResponse> _onExitRequested() async {
|
||||||
|
appWindow.hide();
|
||||||
|
return AppExitResponse.cancel;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onTrayIconMouseDown() {
|
||||||
|
if (Platform.isWindows) {
|
||||||
|
context.read<NotificationProvider>().clearTray();
|
||||||
|
appWindow.show();
|
||||||
|
} else {
|
||||||
|
trayManager.popUpContextMenu();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onTrayIconRightMouseDown() {
|
||||||
|
if (Platform.isWindows) {
|
||||||
|
trayManager.popUpContextMenu();
|
||||||
|
} else {
|
||||||
|
context.read<NotificationProvider>().clearTray();
|
||||||
|
appWindow.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onTrayMenuItemClick(MenuItem menuItem) {
|
||||||
|
switch (menuItem.key) {
|
||||||
|
case 'exit':
|
||||||
|
_appLifecycleListener?.dispose();
|
||||||
|
SystemChannels.platform.invokeMethod('SystemNavigator.pop');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
if (!kIsWeb && !(Platform.isAndroid || Platform.isIOS)) {
|
||||||
|
trayManager.removeListener(this);
|
||||||
|
hotKeyManager.unregisterAll();
|
||||||
|
}
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final cfg = context.read<ConfigProvider>();
|
final cfg = context.read<ConfigProvider>();
|
||||||
|
@ -45,8 +45,8 @@ class ConfigProvider extends ChangeNotifier {
|
|||||||
bool newDrawerIsCollapsed = false;
|
bool newDrawerIsCollapsed = false;
|
||||||
bool newDrawerIsExpanded = false;
|
bool newDrawerIsExpanded = false;
|
||||||
if (withMediaQuery) {
|
if (withMediaQuery) {
|
||||||
newDrawerIsCollapsed = MediaQuery.of(context).size.width < 450;
|
newDrawerIsCollapsed = MediaQuery.of(context).size.width < 600;
|
||||||
newDrawerIsExpanded = MediaQuery.of(context).size.width >= 451;
|
newDrawerIsExpanded = MediaQuery.of(context).size.width >= 601;
|
||||||
} else {
|
} else {
|
||||||
final rpb = ResponsiveBreakpoints.of(context);
|
final rpb = ResponsiveBreakpoints.of(context);
|
||||||
newDrawerIsCollapsed = rpb.smallerOrEqualTo(MOBILE);
|
newDrawerIsCollapsed = rpb.smallerOrEqualTo(MOBILE);
|
||||||
|
@ -12,6 +12,7 @@ import 'package:surface/providers/sn_network.dart';
|
|||||||
import 'package:surface/providers/userinfo.dart';
|
import 'package:surface/providers/userinfo.dart';
|
||||||
import 'package:surface/providers/websocket.dart';
|
import 'package:surface/providers/websocket.dart';
|
||||||
import 'package:surface/types/notification.dart';
|
import 'package:surface/types/notification.dart';
|
||||||
|
import 'package:tray_manager/tray_manager.dart';
|
||||||
|
|
||||||
class NotificationProvider extends ChangeNotifier {
|
class NotificationProvider extends ChangeNotifier {
|
||||||
late final SnNetworkProvider _sn;
|
late final SnNetworkProvider _sn;
|
||||||
@ -72,28 +73,47 @@ class NotificationProvider extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int showingCount = 0;
|
int showingCount = 0;
|
||||||
|
int showingTrayCount = 0;
|
||||||
List<SnNotification> notifications = List.empty(growable: true);
|
List<SnNotification> notifications = List.empty(growable: true);
|
||||||
|
|
||||||
void listen() {
|
void listen() {
|
||||||
_ws.stream.stream.listen((event) {
|
_ws.pk.stream.listen((event) {
|
||||||
if (event.method == 'notifications.new') {
|
if (event.method == 'notifications.new') {
|
||||||
final notification = SnNotification.fromJson(event.payload!);
|
final notification = SnNotification.fromJson(event.payload!);
|
||||||
if (showingCount < 0) showingCount = 0;
|
if (showingCount < 0) showingCount = 0;
|
||||||
showingCount++;
|
showingCount++;
|
||||||
|
showingTrayCount++;
|
||||||
notifications.add(notification);
|
notifications.add(notification);
|
||||||
Future.delayed(const Duration(seconds: 3), () {
|
Future.delayed(const Duration(seconds: 3), () {
|
||||||
if (showingCount >= 0) showingCount--;
|
if (showingCount >= 0) showingCount--;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
});
|
});
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
updateTray();
|
||||||
final doHaptic = _cfg.prefs.getBool(kAppNotifyWithHaptic) ?? true;
|
final doHaptic = _cfg.prefs.getBool(kAppNotifyWithHaptic) ?? true;
|
||||||
if (doHaptic) HapticFeedback.mediumImpact();
|
if (doHaptic) HapticFeedback.mediumImpact();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void clearTray() {
|
||||||
|
showingTrayCount = 0;
|
||||||
|
updateTray();
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateTray() {
|
||||||
|
if (kIsWeb || Platform.isAndroid || Platform.isIOS) return;
|
||||||
|
if (showingTrayCount == 0) {
|
||||||
|
trayManager.setTitle('');
|
||||||
|
} else {
|
||||||
|
trayManager.setTitle(' $showingTrayCount');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void clear() {
|
void clear() {
|
||||||
showingCount = 0;
|
showingCount = 0;
|
||||||
|
notifications.clear();
|
||||||
|
updateTray();
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ 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/user_directory.dart';
|
import 'package:surface/providers/user_directory.dart';
|
||||||
|
import 'package:surface/types/poll.dart';
|
||||||
import 'package:surface/types/post.dart';
|
import 'package:surface/types/post.dart';
|
||||||
|
|
||||||
class SnPostContentProvider {
|
class SnPostContentProvider {
|
||||||
@ -16,6 +17,11 @@ class SnPostContentProvider {
|
|||||||
_attach = context.read<SnAttachmentProvider>();
|
_attach = context.read<SnAttachmentProvider>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<SnPoll> _fetchPoll(int id) async {
|
||||||
|
final resp = await _sn.client.get('/cgi/co/polls/$id');
|
||||||
|
return SnPoll.fromJson(resp.data);
|
||||||
|
}
|
||||||
|
|
||||||
Future<List<SnPost>> _preloadRelatedDataInBatch(List<SnPost> out) async {
|
Future<List<SnPost>> _preloadRelatedDataInBatch(List<SnPost> out) async {
|
||||||
Set<String> rids = {};
|
Set<String> rids = {};
|
||||||
for (var i = 0; i < out.length; i++) {
|
for (var i = 0; i < out.length; i++) {
|
||||||
@ -23,6 +29,9 @@ class SnPostContentProvider {
|
|||||||
if (out[i].body['thumbnail'] != null) {
|
if (out[i].body['thumbnail'] != null) {
|
||||||
rids.add(out[i].body['thumbnail']);
|
rids.add(out[i].body['thumbnail']);
|
||||||
}
|
}
|
||||||
|
if (out[i].body['video'] != null) {
|
||||||
|
rids.add(out[i].body['video']);
|
||||||
|
}
|
||||||
if (out[i].repostTo != null) {
|
if (out[i].repostTo != null) {
|
||||||
out[i] = out[i].copyWith(
|
out[i] = out[i].copyWith(
|
||||||
repostTo: await _preloadRelatedDataSingle(out[i].repostTo!),
|
repostTo: await _preloadRelatedDataSingle(out[i].repostTo!),
|
||||||
@ -32,10 +41,17 @@ 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;
|
||||||
|
if (out[i].pollId != null) {
|
||||||
|
poll = await _fetchPoll(out[i].pollId!);
|
||||||
|
}
|
||||||
|
|
||||||
out[i] = out[i].copyWith(
|
out[i] = out[i].copyWith(
|
||||||
preload: SnPostPreload(
|
preload: SnPostPreload(
|
||||||
thumbnail: attachments.where((ele) => ele?.rid == out[i].body['thumbnail']).firstOrNull,
|
thumbnail: attachments.where((ele) => ele?.rid == out[i].body['thumbnail']).firstOrNull,
|
||||||
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,
|
||||||
|
poll: poll,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -53,6 +69,9 @@ class SnPostContentProvider {
|
|||||||
if (out.body['thumbnail'] != null) {
|
if (out.body['thumbnail'] != null) {
|
||||||
rids.add(out.body['thumbnail']);
|
rids.add(out.body['thumbnail']);
|
||||||
}
|
}
|
||||||
|
if (out.body['video'] != null) {
|
||||||
|
rids.add(out.body['video']);
|
||||||
|
}
|
||||||
if (out.repostTo != null) {
|
if (out.repostTo != null) {
|
||||||
out = out.copyWith(
|
out = out.copyWith(
|
||||||
repostTo: await _preloadRelatedDataSingle(out.repostTo!),
|
repostTo: await _preloadRelatedDataSingle(out.repostTo!),
|
||||||
@ -60,10 +79,18 @@ class SnPostContentProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final attachments = await _attach.getMultiple(rids.toList());
|
final attachments = await _attach.getMultiple(rids.toList());
|
||||||
|
|
||||||
|
SnPoll? poll;
|
||||||
|
if (out.pollId != null) {
|
||||||
|
poll = await _fetchPoll(out.pollId!);
|
||||||
|
}
|
||||||
|
|
||||||
out = out.copyWith(
|
out = out.copyWith(
|
||||||
preload: SnPostPreload(
|
preload: SnPostPreload(
|
||||||
thumbnail: attachments.where((ele) => ele?.rid == out.body['thumbnail']).firstOrNull,
|
thumbnail: attachments.where((ele) => ele?.rid == out.body['thumbnail']).firstOrNull,
|
||||||
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,
|
||||||
|
poll: poll,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -9,6 +9,10 @@ class SnStickerProvider {
|
|||||||
late final SnNetworkProvider _sn;
|
late final SnNetworkProvider _sn;
|
||||||
final Map<String, SnSticker?> _cache = {};
|
final Map<String, SnSticker?> _cache = {};
|
||||||
|
|
||||||
|
final Map<int, List<SnSticker>> stickersByPack = {};
|
||||||
|
|
||||||
|
List<SnSticker> get stickers => _cache.values.where((ele) => ele != null).cast<SnSticker>().toList();
|
||||||
|
|
||||||
SnStickerProvider(BuildContext context) {
|
SnStickerProvider(BuildContext context) {
|
||||||
_sn = context.read<SnNetworkProvider>();
|
_sn = context.read<SnNetworkProvider>();
|
||||||
}
|
}
|
||||||
@ -17,6 +21,12 @@ class SnStickerProvider {
|
|||||||
return _cache.containsKey(alias) && _cache[alias] == null;
|
return _cache.containsKey(alias) && _cache[alias] == null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _cacheSticker(SnSticker sticker) {
|
||||||
|
_cache['${sticker.pack.prefix}:${sticker.alias}'] = sticker;
|
||||||
|
if (stickersByPack[sticker.pack.id] == null) stickersByPack[sticker.pack.id] = List.empty(growable: true);
|
||||||
|
if (!stickersByPack[sticker.pack.id]!.contains(sticker)) stickersByPack[sticker.pack.id]!.add(sticker);
|
||||||
|
}
|
||||||
|
|
||||||
Future<SnSticker?> lookupSticker(String alias) async {
|
Future<SnSticker?> lookupSticker(String alias) async {
|
||||||
if (_cache.containsKey(alias)) {
|
if (_cache.containsKey(alias)) {
|
||||||
return _cache[alias];
|
return _cache[alias];
|
||||||
@ -25,7 +35,7 @@ class SnStickerProvider {
|
|||||||
try {
|
try {
|
||||||
final resp = await _sn.client.get('/cgi/uc/stickers/lookup/$alias');
|
final resp = await _sn.client.get('/cgi/uc/stickers/lookup/$alias');
|
||||||
final sticker = SnSticker.fromJson(resp.data);
|
final sticker = SnSticker.fromJson(resp.data);
|
||||||
_cache[alias] = sticker;
|
_cacheSticker(sticker);
|
||||||
|
|
||||||
return sticker;
|
return sticker;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -35,4 +45,30 @@ class SnStickerProvider {
|
|||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> listStickerEagerly() async {
|
||||||
|
var count = await listSticker();
|
||||||
|
for (var page = 1; count > 0; count -= 10) {
|
||||||
|
await listSticker(page: page);
|
||||||
|
page++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<int> listSticker({int page = 0}) async {
|
||||||
|
try {
|
||||||
|
final resp = await _sn.client.get('/cgi/uc/stickers', queryParameters: {
|
||||||
|
'take': 10,
|
||||||
|
'offset': page * 10,
|
||||||
|
});
|
||||||
|
final data = resp.data;
|
||||||
|
final stickers = List.from(data['data']).map((ele) => SnSticker.fromJson(ele));
|
||||||
|
for (final sticker in stickers) {
|
||||||
|
_cacheSticker(sticker);
|
||||||
|
}
|
||||||
|
return data['count'] as int;
|
||||||
|
} catch (err) {
|
||||||
|
log('[Sticker] Failed to list stickers: $err');
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,9 +14,32 @@ class UserDirectoryProvider {
|
|||||||
final Map<int, SnAccount> _cache = {};
|
final Map<int, SnAccount> _cache = {};
|
||||||
|
|
||||||
Future<List<SnAccount?>> listAccount(Iterable<dynamic> id) async {
|
Future<List<SnAccount?>> listAccount(Iterable<dynamic> id) async {
|
||||||
final out = await Future.wait(
|
final out = List<SnAccount?>.generate(id.length, (e) => null);
|
||||||
id.map((e) => getAccount(e)),
|
final plannedQuery = <int>{};
|
||||||
);
|
for (var idx = 0; idx < out.length; idx++) {
|
||||||
|
var item = id.elementAt(idx);
|
||||||
|
if (item is String && _idCache.containsKey(item)) {
|
||||||
|
item = _idCache[item];
|
||||||
|
}
|
||||||
|
if (_cache.containsKey(item)) {
|
||||||
|
out[idx] = _cache[item];
|
||||||
|
} else {
|
||||||
|
plannedQuery.add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final resp = await _sn.client.get('/cgi/id/users', queryParameters: {'id': plannedQuery.join(',')});
|
||||||
|
final respDecoded = resp.data.map((e) => SnAccount.fromJson(e)).cast<SnAccount>().toList();
|
||||||
|
var sideIdx = 0;
|
||||||
|
for (var idx = 0; idx < out.length; idx++) {
|
||||||
|
if (out[idx] != null) continue;
|
||||||
|
if (respDecoded.length <= sideIdx) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
out[idx] = respDecoded[sideIdx];
|
||||||
|
_cache[respDecoded[sideIdx].id] = out[idx]!;
|
||||||
|
_idCache[respDecoded[sideIdx].name] = respDecoded[sideIdx].id;
|
||||||
|
sideIdx++;
|
||||||
|
}
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,7 +18,8 @@ class WebSocketProvider extends ChangeNotifier {
|
|||||||
late final SnNetworkProvider _sn;
|
late final SnNetworkProvider _sn;
|
||||||
late final UserProvider _ua;
|
late final UserProvider _ua;
|
||||||
|
|
||||||
StreamController<WebSocketPackage> stream = StreamController.broadcast();
|
StreamController<WebSocketPackage> pk = StreamController.broadcast();
|
||||||
|
Stream<dynamic>? _wsStream;
|
||||||
|
|
||||||
WebSocketProvider(BuildContext context) {
|
WebSocketProvider(BuildContext context) {
|
||||||
_sn = context.read<SnNetworkProvider>();
|
_sn = context.read<SnNetworkProvider>();
|
||||||
@ -33,23 +34,33 @@ class WebSocketProvider extends ChangeNotifier {
|
|||||||
await connect();
|
await connect();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Completer<void>? _connectCompleter;
|
||||||
|
|
||||||
Future<void> connect({noRetry = false}) async {
|
Future<void> connect({noRetry = false}) async {
|
||||||
|
if (_connectCompleter != null) {
|
||||||
|
await _connectCompleter!.future;
|
||||||
|
_connectCompleter = null;
|
||||||
|
}
|
||||||
|
|
||||||
if (!_ua.isAuthorized) return;
|
if (!_ua.isAuthorized) return;
|
||||||
if (isConnected || conn != null) {
|
if (isConnected || conn != null) {
|
||||||
disconnect();
|
disconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
final atk = await _sn.getFreshAtk();
|
|
||||||
final uri = Uri.parse(
|
|
||||||
'${_sn.client.options.baseUrl.replaceFirst('http', 'ws')}/ws?tk=$atk',
|
|
||||||
);
|
|
||||||
|
|
||||||
isBusy = true;
|
|
||||||
notifyListeners();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
_connectCompleter = Completer<void>();
|
||||||
|
|
||||||
|
final atk = await _sn.getFreshAtk();
|
||||||
|
final uri = Uri.parse(
|
||||||
|
'${_sn.client.options.baseUrl.replaceFirst('http', 'ws')}/ws?tk=$atk',
|
||||||
|
);
|
||||||
|
|
||||||
|
isBusy = true;
|
||||||
|
notifyListeners();
|
||||||
|
|
||||||
conn = WebSocketChannel.connect(uri);
|
conn = WebSocketChannel.connect(uri);
|
||||||
await conn!.ready;
|
await conn!.ready;
|
||||||
|
_wsStream = conn!.stream.asBroadcastStream();
|
||||||
listen();
|
listen();
|
||||||
log('[WebSocket] Connected to server!');
|
log('[WebSocket] Connected to server!');
|
||||||
isConnected = true;
|
isConnected = true;
|
||||||
@ -70,6 +81,8 @@ class WebSocketProvider extends ChangeNotifier {
|
|||||||
} finally {
|
} finally {
|
||||||
isBusy = false;
|
isBusy = false;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
_connectCompleter!.complete();
|
||||||
|
_connectCompleter = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,11 +96,12 @@ class WebSocketProvider extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void listen() {
|
void listen() {
|
||||||
conn?.stream.listen(
|
if (_wsStream == null) return;
|
||||||
|
_wsStream!.listen(
|
||||||
(event) {
|
(event) {
|
||||||
final packet = WebSocketPackage.fromJson(jsonDecode(event));
|
final packet = WebSocketPackage.fromJson(jsonDecode(event));
|
||||||
log('Websocket incoming message: ${packet.method} ${packet.message}');
|
log('Websocket incoming message: ${packet.method} ${packet.message}');
|
||||||
stream.sink.add(packet);
|
pk.sink.add(packet);
|
||||||
},
|
},
|
||||||
onDone: () {
|
onDone: () {
|
||||||
isConnected = false;
|
isConnected = false;
|
||||||
|
@ -47,6 +47,7 @@ class HomeWidgetProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> widgetUpdateRandomPost() async {
|
Future<void> widgetUpdateRandomPost() async {
|
||||||
|
if (kIsWeb || (!Platform.isAndroid && !Platform.isIOS)) return;
|
||||||
final snc = await SnNetworkProvider.createOffContextClient();
|
final snc = await SnNetworkProvider.createOffContextClient();
|
||||||
final resp = await snc.get('/cgi/co/recommendations/shuffle?take=1');
|
final resp = await snc.get('/cgi/co/recommendations/shuffle?take=1');
|
||||||
final post = SnPost.fromJson(resp.data['data'][0]);
|
final post = SnPost.fromJson(resp.data['data'][0]);
|
||||||
|
@ -31,6 +31,7 @@ import 'package:surface/screens/post/post_search.dart';
|
|||||||
import 'package:surface/screens/realm.dart';
|
import 'package:surface/screens/realm.dart';
|
||||||
import 'package:surface/screens/realm/manage.dart';
|
import 'package:surface/screens/realm/manage.dart';
|
||||||
import 'package:surface/screens/realm/realm_detail.dart';
|
import 'package:surface/screens/realm/realm_detail.dart';
|
||||||
|
import 'package:surface/screens/realm/realm_discovery.dart';
|
||||||
import 'package:surface/screens/settings.dart';
|
import 'package:surface/screens/settings.dart';
|
||||||
import 'package:surface/screens/sharing.dart';
|
import 'package:surface/screens/sharing.dart';
|
||||||
import 'package:surface/screens/wallet.dart';
|
import 'package:surface/screens/wallet.dart';
|
||||||
@ -192,11 +193,6 @@ final _appRoutes = [
|
|||||||
child: const RealmScreen(),
|
child: const RealmScreen(),
|
||||||
),
|
),
|
||||||
routes: [
|
routes: [
|
||||||
GoRoute(
|
|
||||||
path: '/:alias',
|
|
||||||
name: 'realmDetail',
|
|
||||||
builder: (context, state) => RealmDetailScreen(alias: state.pathParameters['alias']!),
|
|
||||||
),
|
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/manage',
|
path: '/manage',
|
||||||
name: 'realmManage',
|
name: 'realmManage',
|
||||||
@ -204,6 +200,16 @@ final _appRoutes = [
|
|||||||
editingRealmAlias: state.uri.queryParameters['editing'],
|
editingRealmAlias: state.uri.queryParameters['editing'],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/discovery',
|
||||||
|
name: 'realmDiscovery',
|
||||||
|
builder: (context, state) => const RealmDiscoveryScreen(),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/:alias',
|
||||||
|
name: 'realmDetail',
|
||||||
|
builder: (context, state) => RealmDetailScreen(alias: state.pathParameters['alias']!),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
GoRoute(path: '/news', name: 'news', builder: (context, state) => const NewsScreen(), routes: [
|
GoRoute(path: '/news', name: 'news', builder: (context, state) => const NewsScreen(), routes: [
|
||||||
|
@ -74,7 +74,10 @@ class _AbuseReportScreenState extends State<AbuseReportScreen> {
|
|||||||
),
|
),
|
||||||
const Divider(height: 1),
|
const Divider(height: 1),
|
||||||
if (_isBusy)
|
if (_isBusy)
|
||||||
const CircularProgressIndicator().padding(all: 24).center()
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(24),
|
||||||
|
child: const CircularProgressIndicator(),
|
||||||
|
).center()
|
||||||
else
|
else
|
||||||
Expanded(
|
Expanded(
|
||||||
child: ListView.builder(
|
child: ListView.builder(
|
||||||
|
@ -178,6 +178,10 @@ class _AccountPublisherEditScreenState extends State<AccountPublisherEditScreen>
|
|||||||
final sn = context.read<SnNetworkProvider>();
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
|
||||||
return AppScaffold(
|
return AppScaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
leading: PageBackButton(),
|
||||||
|
title: Text('screenAccountPublisherEdit').tr(),
|
||||||
|
),
|
||||||
body: SingleChildScrollView(
|
body: SingleChildScrollView(
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
|
@ -45,6 +45,33 @@ class _PublisherScreenState extends State<PublisherScreen> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _deletePublisher(SnPublisher publisher) async {
|
||||||
|
final confirm = await context.showConfirmDialog(
|
||||||
|
'publisherDelete'.tr(args: ['#${publisher.name}']),
|
||||||
|
'publisherDeleteDescription'.tr(),
|
||||||
|
);
|
||||||
|
if (!confirm) return;
|
||||||
|
|
||||||
|
if (!mounted) return;
|
||||||
|
setState(() => _isBusy = true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await context
|
||||||
|
.read<SnNetworkProvider>()
|
||||||
|
.client
|
||||||
|
.delete('/cgi/co/publishers/${publisher.name}');
|
||||||
|
if (!mounted) return;
|
||||||
|
context.showSnackbar('publisherDeleted'.tr(args: ['#${publisher.name}']));
|
||||||
|
_publishers.remove(publisher);
|
||||||
|
_fetchPublishers();
|
||||||
|
} catch (err) {
|
||||||
|
if (!mounted) return;
|
||||||
|
context.showErrorDialog(err);
|
||||||
|
} finally {
|
||||||
|
setState(() => _isBusy = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
@ -118,6 +145,18 @@ class _PublisherScreenState extends State<PublisherScreen> {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
PopupMenuItem(
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const Icon(Symbols.delete),
|
||||||
|
const Gap(16),
|
||||||
|
Text('delete').tr(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
_deletePublisher(publisher);
|
||||||
|
},
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -123,8 +123,10 @@ class _AlbumScreenState extends State<AlbumScreen> {
|
|||||||
),
|
),
|
||||||
if (_isBusy)
|
if (_isBusy)
|
||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
child:
|
child: Padding(
|
||||||
const CircularProgressIndicator().padding(all: 24).center(),
|
padding: const EdgeInsets.all(24),
|
||||||
|
child: const CircularProgressIndicator(),
|
||||||
|
).center(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -155,12 +155,16 @@ class _CallRoomScreenState extends State<CallRoomScreen> {
|
|||||||
text: TextSpan(children: [
|
text: TextSpan(children: [
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: 'call'.tr(),
|
text: 'call'.tr(),
|
||||||
style: Theme.of(context).textTheme.titleLarge!.copyWith(color: Colors.white),
|
style: Theme.of(context).textTheme.titleLarge!.copyWith(
|
||||||
|
color: Theme.of(context).appBarTheme.foregroundColor,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const TextSpan(text: '\n'),
|
const TextSpan(text: '\n'),
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: call.lastDuration.toString(),
|
text: call.lastDuration.toString(),
|
||||||
style: Theme.of(context).textTheme.bodySmall!.copyWith(color: Colors.white),
|
style: Theme.of(context).textTheme.bodySmall!.copyWith(
|
||||||
|
color: Theme.of(context).appBarTheme.foregroundColor,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
]),
|
]),
|
||||||
),
|
),
|
||||||
|
@ -10,8 +10,10 @@ import 'package:surface/providers/channel.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/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/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';
|
||||||
@ -20,6 +22,7 @@ import 'package:very_good_infinite_list/very_good_infinite_list.dart';
|
|||||||
class ChannelDetailScreen extends StatefulWidget {
|
class ChannelDetailScreen extends StatefulWidget {
|
||||||
final String scope;
|
final String scope;
|
||||||
final String alias;
|
final String alias;
|
||||||
|
|
||||||
const ChannelDetailScreen({
|
const ChannelDetailScreen({
|
||||||
super.key,
|
super.key,
|
||||||
required this.scope,
|
required this.scope,
|
||||||
@ -55,8 +58,7 @@ class _ChannelDetailScreenState extends State<ChannelDetailScreen> {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
final sn = context.read<SnNetworkProvider>();
|
final sn = context.read<SnNetworkProvider>();
|
||||||
final resp = await sn.client
|
final resp = await sn.client.get('/cgi/im/channels/${_channel!.keyPath}/members/me');
|
||||||
.get('/cgi/im/channels/${_channel!.keyPath}/members/me');
|
|
||||||
_profile = SnChannelMember.fromJson(resp.data);
|
_profile = SnChannelMember.fromJson(resp.data);
|
||||||
_notifyLevel = _profile!.notify;
|
_notifyLevel = _profile!.notify;
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
@ -143,6 +145,25 @@ class _ChannelDetailScreenState extends State<ChannelDetailScreen> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _addMember(SnAccount related) async {
|
||||||
|
setState(() => _isBusy = true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
await sn.client.post(
|
||||||
|
'/cgi/im/channels/${_channel!.keyPath}/members',
|
||||||
|
data: {'related': related.name},
|
||||||
|
);
|
||||||
|
if (!mounted) return;
|
||||||
|
context.showSnackbar('channelMemberAdded'.tr());
|
||||||
|
} catch (err) {
|
||||||
|
if (!mounted) return;
|
||||||
|
context.showErrorDialog(err);
|
||||||
|
} finally {
|
||||||
|
setState(() => _isBusy = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void _showChannelProfileDetail() {
|
void _showChannelProfileDetail() {
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
@ -166,13 +187,16 @@ class _ChannelDetailScreenState extends State<ChannelDetailScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _showMemberAdd() {
|
void _showMemberAdd() async {
|
||||||
showModalBottomSheet(
|
final user = await showModalBottomSheet<SnAccount?>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => _NewChannelMemberWidget(
|
builder: (context) => AccountSelect(
|
||||||
channel: _channel!,
|
title: 'channelMemberAdd'.tr(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
if (!mounted) return;
|
||||||
|
if (user == null) return;
|
||||||
|
_addMember(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -221,11 +245,7 @@ class _ChannelDetailScreenState extends State<ChannelDetailScreen> {
|
|||||||
Column(
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text('channelDetailPersonalRegion')
|
Text('channelDetailPersonalRegion').bold().fontSize(17).tr().padding(horizontal: 20, bottom: 4),
|
||||||
.bold()
|
|
||||||
.fontSize(17)
|
|
||||||
.tr()
|
|
||||||
.padding(horizontal: 20, bottom: 4),
|
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Symbols.notifications),
|
leading: const Icon(Symbols.notifications),
|
||||||
trailing: DropdownButtonHideUnderline(
|
trailing: DropdownButtonHideUnderline(
|
||||||
@ -264,8 +284,7 @@ class _ChannelDetailScreenState extends State<ChannelDetailScreen> {
|
|||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: AccountImage(
|
leading: AccountImage(
|
||||||
content:
|
content: ud.getAccountFromCache(_profile!.accountId)?.avatar,
|
||||||
ud.getAccountFromCache(_profile!.accountId)?.avatar,
|
|
||||||
radius: 18,
|
radius: 18,
|
||||||
),
|
),
|
||||||
trailing: const Icon(Symbols.chevron_right),
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
@ -284,8 +303,7 @@ class _ChannelDetailScreenState extends State<ChannelDetailScreen> {
|
|||||||
trailing: const Icon(Symbols.chevron_right),
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
title: Text('channelActionLeave').tr(),
|
title: Text('channelActionLeave').tr(),
|
||||||
subtitle: Text('channelActionLeaveDescription').tr(),
|
subtitle: Text('channelActionLeaveDescription').tr(),
|
||||||
contentPadding:
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
const EdgeInsets.symmetric(horizontal: 24),
|
|
||||||
onTap: _leaveChannel,
|
onTap: _leaveChannel,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -293,11 +311,7 @@ class _ChannelDetailScreenState extends State<ChannelDetailScreen> {
|
|||||||
Column(
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text('channelDetailMemberRegion')
|
Text('channelDetailMemberRegion').bold().fontSize(17).tr().padding(horizontal: 20, bottom: 4),
|
||||||
.bold()
|
|
||||||
.fontSize(17)
|
|
||||||
.tr()
|
|
||||||
.padding(horizontal: 20, bottom: 4),
|
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Symbols.group),
|
leading: const Icon(Symbols.group),
|
||||||
trailing: const Icon(Symbols.chevron_right),
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
@ -319,11 +333,7 @@ class _ChannelDetailScreenState extends State<ChannelDetailScreen> {
|
|||||||
Column(
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text('channelDetailAdminRegion')
|
Text('channelDetailAdminRegion').bold().fontSize(17).tr().padding(horizontal: 20, bottom: 4),
|
||||||
.bold()
|
|
||||||
.fontSize(17)
|
|
||||||
.tr()
|
|
||||||
.padding(horizontal: 20, bottom: 4),
|
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Symbols.edit),
|
leading: const Icon(Symbols.edit),
|
||||||
trailing: const Icon(Symbols.chevron_right),
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
@ -362,18 +372,17 @@ class _ChannelDetailScreenState extends State<ChannelDetailScreen> {
|
|||||||
class _ChannelProfileDetailDialog extends StatefulWidget {
|
class _ChannelProfileDetailDialog extends StatefulWidget {
|
||||||
final SnChannel channel;
|
final SnChannel channel;
|
||||||
final SnChannelMember current;
|
final SnChannelMember current;
|
||||||
|
|
||||||
const _ChannelProfileDetailDialog({
|
const _ChannelProfileDetailDialog({
|
||||||
required this.channel,
|
required this.channel,
|
||||||
required this.current,
|
required this.current,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<_ChannelProfileDetailDialog> createState() =>
|
State<_ChannelProfileDetailDialog> createState() => _ChannelProfileDetailDialogState();
|
||||||
_ChannelProfileDetailDialogState();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ChannelProfileDetailDialogState
|
class _ChannelProfileDetailDialogState extends State<_ChannelProfileDetailDialog> {
|
||||||
extends State<_ChannelProfileDetailDialog> {
|
|
||||||
bool _isBusy = false;
|
bool _isBusy = false;
|
||||||
|
|
||||||
final TextEditingController _nickController = TextEditingController();
|
final TextEditingController _nickController = TextEditingController();
|
||||||
@ -444,11 +453,11 @@ class _ChannelProfileDetailDialogState
|
|||||||
|
|
||||||
class _ChannelMemberListWidget extends StatefulWidget {
|
class _ChannelMemberListWidget extends StatefulWidget {
|
||||||
final SnChannel channel;
|
final SnChannel channel;
|
||||||
|
|
||||||
const _ChannelMemberListWidget({required this.channel});
|
const _ChannelMemberListWidget({required this.channel});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<_ChannelMemberListWidget> createState() =>
|
State<_ChannelMemberListWidget> createState() => _ChannelMemberListWidgetState();
|
||||||
_ChannelMemberListWidgetState();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ChannelMemberListWidgetState extends State<_ChannelMemberListWidget> {
|
class _ChannelMemberListWidgetState extends State<_ChannelMemberListWidget> {
|
||||||
@ -463,12 +472,10 @@ class _ChannelMemberListWidgetState extends State<_ChannelMemberListWidget> {
|
|||||||
try {
|
try {
|
||||||
final ud = context.read<UserDirectoryProvider>();
|
final ud = context.read<UserDirectoryProvider>();
|
||||||
final sn = context.read<SnNetworkProvider>();
|
final sn = context.read<SnNetworkProvider>();
|
||||||
final resp = await sn.client.get(
|
final resp = await sn.client.get('/cgi/im/channels/${widget.channel.keyPath}/members', queryParameters: {
|
||||||
'/cgi/im/channels/${widget.channel.keyPath}/members',
|
'take': 10,
|
||||||
queryParameters: {
|
'offset': _members.length,
|
||||||
'take': 10,
|
});
|
||||||
'offset': 0,
|
|
||||||
});
|
|
||||||
final out = List<SnChannelMember>.from(
|
final out = List<SnChannelMember>.from(
|
||||||
resp.data['data']?.map((e) => SnChannelMember.fromJson(e)) ?? [],
|
resp.data['data']?.map((e) => SnChannelMember.fromJson(e)) ?? [],
|
||||||
);
|
);
|
||||||
@ -526,9 +533,7 @@ class _ChannelMemberListWidgetState extends State<_ChannelMemberListWidget> {
|
|||||||
children: [
|
children: [
|
||||||
const Icon(Symbols.group, size: 24),
|
const Icon(Symbols.group, size: 24),
|
||||||
const Gap(16),
|
const Gap(16),
|
||||||
Text('channelMemberManage')
|
Text('channelMemberManage').tr().textStyle(Theme.of(context).textTheme.titleLarge!),
|
||||||
.tr()
|
|
||||||
.textStyle(Theme.of(context).textTheme.titleLarge!),
|
|
||||||
],
|
],
|
||||||
).padding(horizontal: 20, top: 16, bottom: 12),
|
).padding(horizontal: 20, top: 16, bottom: 12),
|
||||||
Expanded(
|
Expanded(
|
||||||
@ -539,8 +544,7 @@ class _ChannelMemberListWidgetState extends State<_ChannelMemberListWidget> {
|
|||||||
},
|
},
|
||||||
child: InfiniteList(
|
child: InfiniteList(
|
||||||
itemCount: _members.length,
|
itemCount: _members.length,
|
||||||
hasReachedMax:
|
hasReachedMax: _totalCount != null && _members.length >= _totalCount!,
|
||||||
_totalCount != null && _members.length >= _totalCount!,
|
|
||||||
isLoading: _isBusy,
|
isLoading: _isBusy,
|
||||||
onFetchData: _fetchMembers,
|
onFetchData: _fetchMembers,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
@ -551,8 +555,7 @@ class _ChannelMemberListWidgetState extends State<_ChannelMemberListWidget> {
|
|||||||
content: ud.getAccountFromCache(member.accountId)?.avatar,
|
content: ud.getAccountFromCache(member.accountId)?.avatar,
|
||||||
),
|
),
|
||||||
title: Text(
|
title: Text(
|
||||||
ud.getAccountFromCache(member.accountId)?.name ??
|
ud.getAccountFromCache(member.accountId)?.name ?? 'unknown'.tr(),
|
||||||
'unknown'.tr(),
|
|
||||||
),
|
),
|
||||||
subtitle: Text(member.nick ?? 'unknown'.tr()),
|
subtitle: Text(member.nick ?? 'unknown'.tr()),
|
||||||
trailing: SizedBox(
|
trailing: SizedBox(
|
||||||
@ -562,8 +565,7 @@ class _ChannelMemberListWidgetState extends State<_ChannelMemberListWidget> {
|
|||||||
mainAxisAlignment: MainAxisAlignment.end,
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
children: [
|
children: [
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed:
|
onPressed: _isUpdating ? null : () => _deleteMember(member),
|
||||||
_isUpdating ? null : () => _deleteMember(member),
|
|
||||||
icon: const Icon(Symbols.person_remove),
|
icon: const Icon(Symbols.person_remove),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -578,83 +580,3 @@ class _ChannelMemberListWidgetState extends State<_ChannelMemberListWidget> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _NewChannelMemberWidget extends StatefulWidget {
|
|
||||||
final SnChannel channel;
|
|
||||||
const _NewChannelMemberWidget({required this.channel});
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<_NewChannelMemberWidget> createState() =>
|
|
||||||
_NewChannelMemberWidgetState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _NewChannelMemberWidgetState extends State<_NewChannelMemberWidget> {
|
|
||||||
bool _isBusy = false;
|
|
||||||
|
|
||||||
final TextEditingController _relatedController = TextEditingController();
|
|
||||||
|
|
||||||
Future<void> _performAction() async {
|
|
||||||
if (_relatedController.text.isEmpty) return;
|
|
||||||
|
|
||||||
setState(() => _isBusy = true);
|
|
||||||
|
|
||||||
try {
|
|
||||||
final sn = context.read<SnNetworkProvider>();
|
|
||||||
await sn.client.post(
|
|
||||||
'/cgi/im/channels/${widget.channel.keyPath}/members',
|
|
||||||
data: {
|
|
||||||
'related': _relatedController.text,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
if (!mounted) return;
|
|
||||||
Navigator.pop(context, true);
|
|
||||||
context.showSnackbar('channelMemberAdded'.tr());
|
|
||||||
} catch (err) {
|
|
||||||
if (!mounted) return;
|
|
||||||
context.showErrorDialog(err);
|
|
||||||
} finally {
|
|
||||||
setState(() => _isBusy = false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
super.dispose();
|
|
||||||
_relatedController.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return StyledWidget(Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'channelMemberAdd',
|
|
||||||
style: Theme.of(context).textTheme.titleLarge,
|
|
||||||
).tr(),
|
|
||||||
const Gap(12),
|
|
||||||
TextField(
|
|
||||||
controller: _relatedController,
|
|
||||||
readOnly: _isBusy,
|
|
||||||
autocorrect: false,
|
|
||||||
autofocus: true,
|
|
||||||
textCapitalization: TextCapitalization.none,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'fieldMemberRelatedName'.tr(),
|
|
||||||
suffix: SizedBox(
|
|
||||||
height: 24,
|
|
||||||
child: IconButton(
|
|
||||||
onPressed: _isBusy ? null : () => _performAction(),
|
|
||||||
icon: Icon(Symbols.send),
|
|
||||||
visualDensity:
|
|
||||||
const VisualDensity(horizontal: -4, vertical: -4),
|
|
||||||
padding: EdgeInsets.zero,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
)).padding(all: 24);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import 'package:collection/collection.dart';
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:dropdown_button2/dropdown_button2.dart';
|
import 'package:dropdown_button2/dropdown_button2.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
@ -17,6 +18,7 @@ import 'package:uuid/uuid.dart';
|
|||||||
|
|
||||||
class ChatManageScreen extends StatefulWidget {
|
class ChatManageScreen extends StatefulWidget {
|
||||||
final String? editingChannelAlias;
|
final String? editingChannelAlias;
|
||||||
|
|
||||||
const ChatManageScreen({super.key, this.editingChannelAlias});
|
const ChatManageScreen({super.key, this.editingChannelAlias});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -33,6 +35,11 @@ class _ChatManageScreenState extends State<ChatManageScreen> {
|
|||||||
List<SnRealm>? _realms;
|
List<SnRealm>? _realms;
|
||||||
SnRealm? _belongToRealm;
|
SnRealm? _belongToRealm;
|
||||||
|
|
||||||
|
SnChannel? _editingChannel;
|
||||||
|
|
||||||
|
bool _isPublic = false;
|
||||||
|
bool _isCommunity = false;
|
||||||
|
|
||||||
Future<void> _fetchRealms() async {
|
Future<void> _fetchRealms() async {
|
||||||
setState(() => _isBusy = true);
|
setState(() => _isBusy = true);
|
||||||
try {
|
try {
|
||||||
@ -41,6 +48,9 @@ class _ChatManageScreenState extends State<ChatManageScreen> {
|
|||||||
_realms = List<SnRealm>.from(
|
_realms = List<SnRealm>.from(
|
||||||
resp.data?.map((e) => SnRealm.fromJson(e)) ?? [],
|
resp.data?.map((e) => SnRealm.fromJson(e)) ?? [],
|
||||||
);
|
);
|
||||||
|
if (_editingChannel != null) {
|
||||||
|
_belongToRealm = _realms?.firstWhereOrNull((e) => e.id == _editingChannel!.realmId);
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (mounted) context.showErrorDialog(err);
|
if (mounted) context.showErrorDialog(err);
|
||||||
} finally {
|
} finally {
|
||||||
@ -48,8 +58,6 @@ class _ChatManageScreenState extends State<ChatManageScreen> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SnChannel? _editingChannel;
|
|
||||||
|
|
||||||
Future<void> _fetchChannel() async {
|
Future<void> _fetchChannel() async {
|
||||||
setState(() => _isBusy = true);
|
setState(() => _isBusy = true);
|
||||||
|
|
||||||
@ -62,6 +70,8 @@ class _ChatManageScreenState extends State<ChatManageScreen> {
|
|||||||
_aliasController.text = _editingChannel!.alias;
|
_aliasController.text = _editingChannel!.alias;
|
||||||
_nameController.text = _editingChannel!.name;
|
_nameController.text = _editingChannel!.name;
|
||||||
_descriptionController.text = _editingChannel!.description;
|
_descriptionController.text = _editingChannel!.description;
|
||||||
|
_isPublic = _editingChannel!.isPublic;
|
||||||
|
_isCommunity = _editingChannel!.isCommunity;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
context.showErrorDialog(err);
|
context.showErrorDialog(err);
|
||||||
@ -83,6 +93,8 @@ class _ChatManageScreenState extends State<ChatManageScreen> {
|
|||||||
: uuid.v4().replaceAll('-', '').substring(0, 12),
|
: uuid.v4().replaceAll('-', '').substring(0, 12),
|
||||||
'name': _nameController.text,
|
'name': _nameController.text,
|
||||||
'description': _descriptionController.text,
|
'description': _descriptionController.text,
|
||||||
|
'is_public': _isPublic,
|
||||||
|
'is_community': _isCommunity,
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -124,9 +136,7 @@ class _ChatManageScreenState extends State<ChatManageScreen> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return AppScaffold(
|
return AppScaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: widget.editingChannelAlias != null
|
title: widget.editingChannelAlias != null ? Text('screenChatManage').tr() : Text('screenChatNew').tr(),
|
||||||
? Text('screenChatManage').tr()
|
|
||||||
: Text('screenChatNew').tr(),
|
|
||||||
),
|
),
|
||||||
body: SingleChildScrollView(
|
body: SingleChildScrollView(
|
||||||
child: Column(
|
child: Column(
|
||||||
@ -138,8 +148,7 @@ class _ChatManageScreenState extends State<ChatManageScreen> {
|
|||||||
leadingPadding: const EdgeInsets.only(left: 10, right: 20),
|
leadingPadding: const EdgeInsets.only(left: 10, right: 20),
|
||||||
dividerColor: Colors.transparent,
|
dividerColor: Colors.transparent,
|
||||||
content: Text(
|
content: Text(
|
||||||
'channelEditingNotice'
|
'channelEditingNotice'.tr(args: ['#${_editingChannel!.alias}']),
|
||||||
.tr(args: ['#${_editingChannel!.alias}']),
|
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
@ -162,6 +171,7 @@ 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: [
|
||||||
@ -179,15 +189,12 @@ class _ChatManageScreenState extends State<ChatManageScreen> {
|
|||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(item.name).textStyle(Theme.of(context)
|
Text(item.name).textStyle(Theme.of(context).textTheme.bodyMedium!),
|
||||||
.textTheme
|
|
||||||
.bodyMedium!),
|
|
||||||
Text(
|
Text(
|
||||||
item.description,
|
item.description,
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
).textStyle(
|
).textStyle(Theme.of(context).textTheme.bodySmall!),
|
||||||
Theme.of(context).textTheme.bodySmall!),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -197,14 +204,14 @@ class _ChatManageScreenState extends State<ChatManageScreen> {
|
|||||||
) ??
|
) ??
|
||||||
[]),
|
[]),
|
||||||
DropdownMenuItem<SnRealm>(
|
DropdownMenuItem<SnRealm>(
|
||||||
|
enabled: _editingChannel == null,
|
||||||
value: null,
|
value: null,
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
CircleAvatar(
|
CircleAvatar(
|
||||||
radius: 16,
|
radius: 16,
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
foregroundColor:
|
foregroundColor: Theme.of(context).colorScheme.onSurface,
|
||||||
Theme.of(context).colorScheme.onSurface,
|
|
||||||
child: const Icon(Symbols.clear),
|
child: const Icon(Symbols.clear),
|
||||||
),
|
),
|
||||||
const Gap(12),
|
const Gap(12),
|
||||||
@ -213,9 +220,7 @@ class _ChatManageScreenState extends State<ChatManageScreen> {
|
|||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text('fieldChatBelongToRealmUnset')
|
Text('fieldChatBelongToRealmUnset').tr().textStyle(
|
||||||
.tr()
|
|
||||||
.textStyle(
|
|
||||||
Theme.of(context).textTheme.bodyMedium!,
|
Theme.of(context).textTheme.bodyMedium!,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -231,10 +236,10 @@ class _ChatManageScreenState extends State<ChatManageScreen> {
|
|||||||
},
|
},
|
||||||
buttonStyleData: const ButtonStyleData(
|
buttonStyleData: const ButtonStyleData(
|
||||||
padding: EdgeInsets.only(right: 16),
|
padding: EdgeInsets.only(right: 16),
|
||||||
height: 60,
|
height: 48,
|
||||||
),
|
),
|
||||||
menuItemStyleData: const MenuItemStyleData(
|
menuItemStyleData: const MenuItemStyleData(
|
||||||
height: 60,
|
height: 48,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -250,8 +255,7 @@ class _ChatManageScreenState extends State<ChatManageScreen> {
|
|||||||
helperText: 'fieldChatAliasHint'.tr(),
|
helperText: 'fieldChatAliasHint'.tr(),
|
||||||
helperMaxLines: 2,
|
helperMaxLines: 2,
|
||||||
),
|
),
|
||||||
onTapOutside: (_) =>
|
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
FocusManager.instance.primaryFocus?.unfocus(),
|
|
||||||
),
|
),
|
||||||
const Gap(4),
|
const Gap(4),
|
||||||
TextField(
|
TextField(
|
||||||
@ -260,8 +264,7 @@ class _ChatManageScreenState extends State<ChatManageScreen> {
|
|||||||
border: const UnderlineInputBorder(),
|
border: const UnderlineInputBorder(),
|
||||||
labelText: 'fieldChatName'.tr(),
|
labelText: 'fieldChatName'.tr(),
|
||||||
),
|
),
|
||||||
onTapOutside: (_) =>
|
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
FocusManager.instance.primaryFocus?.unfocus(),
|
|
||||||
),
|
),
|
||||||
const Gap(4),
|
const Gap(4),
|
||||||
TextField(
|
TextField(
|
||||||
@ -272,8 +275,24 @@ class _ChatManageScreenState extends State<ChatManageScreen> {
|
|||||||
border: const UnderlineInputBorder(),
|
border: const UnderlineInputBorder(),
|
||||||
labelText: 'fieldChatDescription'.tr(),
|
labelText: 'fieldChatDescription'.tr(),
|
||||||
),
|
),
|
||||||
onTapOutside: (_) =>
|
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
FocusManager.instance.primaryFocus?.unfocus(),
|
),
|
||||||
|
const Gap(12),
|
||||||
|
CheckboxListTile(
|
||||||
|
value: _isPublic,
|
||||||
|
title: Text('channelIsPublic'.tr()),
|
||||||
|
subtitle: Text('channelIsPublicDescription'.tr()),
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() => _isPublic = value ?? false);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
CheckboxListTile(
|
||||||
|
value: _isCommunity,
|
||||||
|
title: Text('channelIsCommunity'.tr()),
|
||||||
|
subtitle: Text('channelIsCommunityDescription'.tr()),
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() => _isCommunity = value ?? false);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
const Gap(12),
|
const Gap(12),
|
||||||
Row(
|
Row(
|
||||||
|
@ -206,7 +206,7 @@ class _ChatRoomScreenState extends State<ChatRoomScreen> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
final ws = context.read<WebSocketProvider>();
|
final ws = context.read<WebSocketProvider>();
|
||||||
_wsSubscription = ws.stream.stream.listen((event) {
|
_wsSubscription = ws.pk.stream.listen((event) {
|
||||||
switch (event.method) {
|
switch (event.method) {
|
||||||
case 'calls.new':
|
case 'calls.new':
|
||||||
final payload = SnChatCall.fromJson(event.payload!);
|
final payload = SnChatCall.fromJson(event.payload!);
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import 'package:animations/animations.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';
|
||||||
@ -7,10 +6,8 @@ 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/config.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/screens/post/post_detail.dart';
|
|
||||||
import 'package:surface/types/post.dart';
|
import 'package:surface/types/post.dart';
|
||||||
import 'package:surface/widgets/app_bar_leading.dart';
|
import 'package:surface/widgets/app_bar_leading.dart';
|
||||||
import 'package:surface/widgets/dialog.dart';
|
import 'package:surface/widgets/dialog.dart';
|
||||||
@ -97,8 +94,6 @@ class _ExploreScreenState extends State<ExploreScreen> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final cfg = context.read<ConfigProvider>();
|
|
||||||
|
|
||||||
return AppScaffold(
|
return AppScaffold(
|
||||||
floatingActionButtonLocation: ExpandableFab.location,
|
floatingActionButtonLocation: ExpandableFab.location,
|
||||||
floatingActionButton: ExpandableFab(
|
floatingActionButton: ExpandableFab(
|
||||||
@ -166,6 +161,48 @@ class _ExploreScreenState extends State<ExploreScreen> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Text('writePostTypeQuestion').tr(),
|
||||||
|
const Gap(20),
|
||||||
|
FloatingActionButton(
|
||||||
|
heroTag: null,
|
||||||
|
tooltip: 'writePostTypeQuestion'.tr(),
|
||||||
|
onPressed: () {
|
||||||
|
GoRouter.of(context).pushNamed('postEditor', pathParameters: {
|
||||||
|
'mode': 'questions',
|
||||||
|
}).then((value) {
|
||||||
|
if (value == true) {
|
||||||
|
_refreshPosts();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
_fabKey.currentState!.toggle();
|
||||||
|
},
|
||||||
|
child: const Icon(Symbols.question_answer),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Text('writePostTypeVideo').tr(),
|
||||||
|
const Gap(20),
|
||||||
|
FloatingActionButton(
|
||||||
|
heroTag: null,
|
||||||
|
tooltip: 'writePostTypeVideo'.tr(),
|
||||||
|
onPressed: () {
|
||||||
|
GoRouter.of(context).pushNamed('postEditor', pathParameters: {
|
||||||
|
'mode': 'videos',
|
||||||
|
}).then((value) {
|
||||||
|
if (value == true) {
|
||||||
|
_refreshPosts();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
_fabKey.currentState!.toggle();
|
||||||
|
},
|
||||||
|
child: const Icon(Symbols.video_call),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: RefreshIndicator(
|
body: RefreshIndicator(
|
||||||
@ -224,36 +261,15 @@ class _ExploreScreenState extends State<ExploreScreen> {
|
|||||||
hasReachedMax: _postCount != null && _posts.length >= _postCount!,
|
hasReachedMax: _postCount != null && _posts.length >= _postCount!,
|
||||||
onFetchData: _fetchPosts,
|
onFetchData: _fetchPosts,
|
||||||
itemBuilder: (context, idx) {
|
itemBuilder: (context, idx) {
|
||||||
return Center(
|
return OpenablePostItem(
|
||||||
child: OpenContainer(
|
data: _posts[idx],
|
||||||
closedBuilder: (_, __) => Container(
|
maxWidth: 640,
|
||||||
constraints: const BoxConstraints(maxWidth: 640),
|
onChanged: (data) {
|
||||||
child: PostItem(
|
setState(() => _posts[idx] = data);
|
||||||
data: _posts[idx],
|
},
|
||||||
maxWidth: 640,
|
onDeleted: () {
|
||||||
onChanged: (data) {
|
_refreshPosts();
|
||||||
setState(() => _posts[idx] = data);
|
},
|
||||||
},
|
|
||||||
onDeleted: () {
|
|
||||||
_refreshPosts();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
openBuilder: (_, close) => PostDetailScreen(
|
|
||||||
slug: _posts[idx].id.toString(),
|
|
||||||
preload: _posts[idx],
|
|
||||||
onBack: close,
|
|
||||||
),
|
|
||||||
openColor: Colors.transparent,
|
|
||||||
openElevation: 0,
|
|
||||||
transitionType: ContainerTransitionType.fade,
|
|
||||||
closedColor: Theme.of(context).colorScheme.surfaceContainerLow.withOpacity(
|
|
||||||
cfg.prefs.getBool(kAppBackgroundStoreKey) == true ? 0.75 : 1,
|
|
||||||
),
|
|
||||||
closedShape: const RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.all(Radius.circular(16)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
separatorBuilder: (_, __) => const Gap(8),
|
separatorBuilder: (_, __) => const Gap(8),
|
||||||
|
@ -6,15 +6,15 @@ import 'package:provider/provider.dart';
|
|||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
import 'package:surface/providers/relationship.dart';
|
import 'package:surface/providers/relationship.dart';
|
||||||
import 'package:surface/providers/sn_network.dart';
|
import 'package:surface/providers/sn_network.dart';
|
||||||
|
import 'package:surface/providers/userinfo.dart';
|
||||||
import 'package:surface/types/account.dart';
|
import 'package:surface/types/account.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/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/unauthorized_hint.dart';
|
||||||
import '../providers/userinfo.dart';
|
|
||||||
import '../widgets/unauthorized_hint.dart';
|
|
||||||
|
|
||||||
const kFriendStatus = {
|
const kFriendStatus = {
|
||||||
0: 'friendStatusPending',
|
0: 'friendStatusPending',
|
||||||
@ -168,6 +168,24 @@ class _FriendScreenState extends State<FriendScreen> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _sendRequest(SnAccount user) async {
|
||||||
|
setState(() => _isBusy = true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
await sn.client.post('/cgi/id/users/me/relations', data: {
|
||||||
|
'related': user.name,
|
||||||
|
});
|
||||||
|
if (!mounted) return;
|
||||||
|
context.showSnackbar('friendRequestSent'.tr());
|
||||||
|
} catch (err) {
|
||||||
|
if (!mounted) return;
|
||||||
|
context.showErrorDialog(err);
|
||||||
|
} finally {
|
||||||
|
setState(() => _isBusy = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
@ -199,11 +217,16 @@ class _FriendScreenState extends State<FriendScreen> {
|
|||||||
),
|
),
|
||||||
floatingActionButton: FloatingActionButton(
|
floatingActionButton: FloatingActionButton(
|
||||||
child: const Icon(Symbols.add),
|
child: const Icon(Symbols.add),
|
||||||
onPressed: () {
|
onPressed: () async {
|
||||||
showModalBottomSheet(
|
final user = await showModalBottomSheet<SnAccount?>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => _NewFriendWidget(),
|
builder: (context) => AccountSelect(
|
||||||
|
title: 'friendNew'.tr(),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
if (!mounted) return;
|
||||||
|
if (user == null) return;
|
||||||
|
_sendRequest(user);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
body: Column(
|
body: Column(
|
||||||
@ -231,8 +254,7 @@ class _FriendScreenState extends State<FriendScreen> {
|
|||||||
trailing: const Icon(Symbols.chevron_right),
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
onTap: _showBlocks,
|
onTap: _showBlocks,
|
||||||
),
|
),
|
||||||
if (_requests.isNotEmpty || _blocks.isNotEmpty)
|
if (_requests.isNotEmpty || _blocks.isNotEmpty) const Divider(height: 1),
|
||||||
const Divider(height: 1),
|
|
||||||
Expanded(
|
Expanded(
|
||||||
child: MediaQuery.removePadding(
|
child: MediaQuery.removePadding(
|
||||||
context: context,
|
context: context,
|
||||||
@ -264,16 +286,12 @@ class _FriendScreenState extends State<FriendScreen> {
|
|||||||
mainAxisAlignment: MainAxisAlignment.end,
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
children: [
|
children: [
|
||||||
InkWell(
|
InkWell(
|
||||||
onTap: _isUpdating
|
onTap: _isUpdating ? null : () => _changeRelation(relation, 2),
|
||||||
? null
|
|
||||||
: () => _changeRelation(relation, 2),
|
|
||||||
child: Text('friendBlock').tr(),
|
child: Text('friendBlock').tr(),
|
||||||
),
|
),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
InkWell(
|
InkWell(
|
||||||
onTap: _isUpdating
|
onTap: _isUpdating ? null : () => _deleteRelation(relation),
|
||||||
? null
|
|
||||||
: () => _deleteRelation(relation),
|
|
||||||
child: Text('friendDeleteAction').tr(),
|
child: Text('friendDeleteAction').tr(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -293,83 +311,9 @@ class _FriendScreenState extends State<FriendScreen> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _NewFriendWidget extends StatefulWidget {
|
|
||||||
const _NewFriendWidget();
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<_NewFriendWidget> createState() => _NewFriendWidgetState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _NewFriendWidgetState extends State<_NewFriendWidget> {
|
|
||||||
bool _isBusy = false;
|
|
||||||
|
|
||||||
final TextEditingController _relatedController = TextEditingController();
|
|
||||||
|
|
||||||
Future<void> _sendRequest() async {
|
|
||||||
if (_relatedController.text.isEmpty) return;
|
|
||||||
|
|
||||||
setState(() => _isBusy = true);
|
|
||||||
|
|
||||||
try {
|
|
||||||
final sn = context.read<SnNetworkProvider>();
|
|
||||||
await sn.client.post('/cgi/id/users/me/relations', data: {
|
|
||||||
'related': _relatedController.text,
|
|
||||||
});
|
|
||||||
if (!mounted) return;
|
|
||||||
Navigator.pop(context, true);
|
|
||||||
context.showSnackbar('friendRequestSent'.tr());
|
|
||||||
} catch (err) {
|
|
||||||
if (!mounted) return;
|
|
||||||
context.showErrorDialog(err);
|
|
||||||
} finally {
|
|
||||||
setState(() => _isBusy = false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
super.dispose();
|
|
||||||
_relatedController.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return StyledWidget(Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'friendNew',
|
|
||||||
style: Theme.of(context).textTheme.titleLarge,
|
|
||||||
).tr(),
|
|
||||||
const Gap(12),
|
|
||||||
TextField(
|
|
||||||
controller: _relatedController,
|
|
||||||
readOnly: _isBusy,
|
|
||||||
autocorrect: false,
|
|
||||||
autofocus: true,
|
|
||||||
textCapitalization: TextCapitalization.none,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'fieldFriendRelatedName'.tr(),
|
|
||||||
suffix: SizedBox(
|
|
||||||
height: 24,
|
|
||||||
child: IconButton(
|
|
||||||
onPressed: _isBusy ? null : () => _sendRequest(),
|
|
||||||
icon: Icon(Symbols.send),
|
|
||||||
visualDensity:
|
|
||||||
const VisualDensity(horizontal: -4, vertical: -4),
|
|
||||||
padding: EdgeInsets.zero,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
)).padding(all: 24);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _FriendshipListWidget extends StatefulWidget {
|
class _FriendshipListWidget extends StatefulWidget {
|
||||||
final List<SnRelationship> relations;
|
final List<SnRelationship> relations;
|
||||||
|
|
||||||
const _FriendshipListWidget({required this.relations});
|
const _FriendshipListWidget({required this.relations});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -476,9 +420,7 @@ class _FriendshipListWidgetState extends State<_FriendshipListWidget> {
|
|||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
children: [
|
children: [
|
||||||
Text(kFriendStatus[relation.status] ?? 'unknown')
|
Text(kFriendStatus[relation.status] ?? 'unknown').tr().opacity(0.75),
|
||||||
.tr()
|
|
||||||
.opacity(0.75),
|
|
||||||
if (relation.status == 0)
|
if (relation.status == 0)
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
@ -499,8 +441,7 @@ class _FriendshipListWidgetState extends State<_FriendshipListWidget> {
|
|||||||
mainAxisAlignment: MainAxisAlignment.end,
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
children: [
|
children: [
|
||||||
InkWell(
|
InkWell(
|
||||||
onTap:
|
onTap: _isBusy ? null : () => _changeRelation(relation, 1),
|
||||||
_isBusy ? null : () => _changeRelation(relation, 1),
|
|
||||||
child: Text('friendUnblock').tr(),
|
child: Text('friendUnblock').tr(),
|
||||||
),
|
),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
|
@ -131,6 +131,7 @@ class _HomeDashUpdateWidget extends StatelessWidget {
|
|||||||
return Container(
|
return Container(
|
||||||
padding: padding,
|
padding: padding,
|
||||||
child: Card(
|
child: Card(
|
||||||
|
margin: EdgeInsets.zero,
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
leading: Icon(Symbols.update),
|
leading: Icon(Symbols.update),
|
||||||
title: Text('updateAvailable').tr(),
|
title: Text('updateAvailable').tr(),
|
||||||
@ -180,6 +181,7 @@ class _HomeDashSpecialDayWidgetState extends State<_HomeDashSpecialDayWidget> {
|
|||||||
return Column(
|
return Column(
|
||||||
children: days.map((ele) {
|
children: days.map((ele) {
|
||||||
return Card(
|
return Card(
|
||||||
|
margin: EdgeInsets.zero,
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
leading: Text(kSpecialDaysSymbol[ele] ?? '🎉').fontSize(24),
|
leading: Text(kSpecialDaysSymbol[ele] ?? '🎉').fontSize(24),
|
||||||
title: Text('celebrate$ele').tr(args: [ua.user?.nick ?? 'user']),
|
title: Text('celebrate$ele').tr(args: [ua.user?.nick ?? 'user']),
|
||||||
@ -203,6 +205,7 @@ class _HomeDashSpecialDayWidgetState extends State<_HomeDashSpecialDayWidget> {
|
|||||||
final progress = dayz.getSpecialDayProgress(lastOne.$2, date);
|
final progress = dayz.getSpecialDayProgress(lastOne.$2, date);
|
||||||
final diff = nextOne.$2.difference(DateTime.now());
|
final diff = nextOne.$2.difference(DateTime.now());
|
||||||
return Card(
|
return Card(
|
||||||
|
margin: EdgeInsets.zero,
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
leading: Text(kSpecialDaysSymbol[name] ?? '🎉').fontSize(24),
|
leading: Text(kSpecialDaysSymbol[name] ?? '🎉').fontSize(24),
|
||||||
title: Text('pending$name').tr(args: [RelativeTime(context).format(date).replaceFirst('in', '').trim()]),
|
title: Text('pending$name').tr(args: [RelativeTime(context).format(date).replaceFirst('in', '').trim()]),
|
||||||
@ -270,6 +273,7 @@ class _HomeDashTodayNewsState extends State<_HomeDashTodayNews> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Card(
|
return Card(
|
||||||
|
margin: EdgeInsets.zero,
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@ -469,6 +473,7 @@ class _HomeDashCheckInWidgetState extends State<_HomeDashCheckInWidget> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Card(
|
return Card(
|
||||||
|
margin: EdgeInsets.zero,
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@ -594,6 +599,7 @@ class _HomeDashNotificationWidgetState extends State<_HomeDashNotificationWidget
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Card(
|
return Card(
|
||||||
|
margin: EdgeInsets.zero,
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@ -657,36 +663,59 @@ class _HomeDashRecommendationPostWidgetState extends State<_HomeDashRecommendati
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int _currentPage = 0;
|
||||||
|
final PageController _pageController = PageController();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_fetchRecommendationPosts();
|
_fetchRecommendationPosts();
|
||||||
|
_pageController.addListener(() {
|
||||||
|
setState(() {
|
||||||
|
_currentPage = _pageController.page?.round() ?? 0;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_pageController.dispose();
|
||||||
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (_isBusy) {
|
if (_isBusy) {
|
||||||
return Card(
|
return Card(
|
||||||
|
margin: EdgeInsets.zero,
|
||||||
child: CircularProgressIndicator().center(),
|
child: CircularProgressIndicator().center(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Card(
|
return Card(
|
||||||
|
margin: EdgeInsets.zero,
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
const Icon(Symbols.star),
|
Row(
|
||||||
const Gap(8),
|
children: [
|
||||||
Text(
|
const Icon(Symbols.star),
|
||||||
'postRecommendation',
|
const Gap(8),
|
||||||
style: Theme.of(context).textTheme.titleLarge,
|
Text(
|
||||||
).tr()
|
'postRecommendation',
|
||||||
|
style: Theme.of(context).textTheme.titleLarge,
|
||||||
|
).tr(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Text('${_currentPage + 1}/${_posts?.length ?? 0}', style: GoogleFonts.robotoMono())
|
||||||
],
|
],
|
||||||
).padding(horizontal: 18, top: 12, bottom: 8),
|
).padding(horizontal: 18, top: 12, bottom: 8),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: PageView.builder(
|
child: PageView.builder(
|
||||||
|
controller: _pageController,
|
||||||
scrollBehavior: ScrollConfiguration.of(context).copyWith(dragDevices: {
|
scrollBehavior: ScrollConfiguration.of(context).copyWith(dragDevices: {
|
||||||
PointerDeviceKind.mouse,
|
PointerDeviceKind.mouse,
|
||||||
PointerDeviceKind.touch,
|
PointerDeviceKind.touch,
|
||||||
|
@ -3,10 +3,12 @@ import 'dart:math' as math;
|
|||||||
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:gap/gap.dart';
|
import 'package:gap/gap.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:relative_time/relative_time.dart';
|
import 'package:relative_time/relative_time.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
import 'package:surface/providers/notification.dart';
|
||||||
import 'package:surface/providers/sn_network.dart';
|
import 'package:surface/providers/sn_network.dart';
|
||||||
import 'package:surface/types/notification.dart';
|
import 'package:surface/types/notification.dart';
|
||||||
import 'package:surface/types/post.dart';
|
import 'package:surface/types/post.dart';
|
||||||
@ -54,14 +56,13 @@ class _NotificationScreenState extends State<NotificationScreen> {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
final sn = context.read<SnNetworkProvider>();
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
final nty = context.read<NotificationProvider>();
|
||||||
final resp = await sn.client.get('/cgi/id/notifications?take=10');
|
final resp = await sn.client.get('/cgi/id/notifications?take=10');
|
||||||
_totalCount = resp.data['count'];
|
_totalCount = resp.data['count'];
|
||||||
_notifications.addAll(
|
_notifications.addAll(
|
||||||
resp.data['data']
|
resp.data['data']?.map((e) => SnNotification.fromJson(e)).cast<SnNotification>() ?? [],
|
||||||
?.map((e) => SnNotification.fromJson(e))
|
|
||||||
.cast<SnNotification>() ??
|
|
||||||
[],
|
|
||||||
);
|
);
|
||||||
|
nty.updateTray();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
context.showErrorDialog(err);
|
context.showErrorDialog(err);
|
||||||
@ -88,9 +89,11 @@ class _NotificationScreenState extends State<NotificationScreen> {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
final sn = context.read<SnNetworkProvider>();
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
final nty = context.read<NotificationProvider>();
|
||||||
final resp = await sn.client.put('/cgi/id/notifications/read/all');
|
final resp = await sn.client.put('/cgi/id/notifications/read/all');
|
||||||
_notifications.clear();
|
_notifications.clear();
|
||||||
_fetchNotifications();
|
_fetchNotifications();
|
||||||
|
nty.clear();
|
||||||
|
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
context.showSnackbar(
|
context.showSnackbar(
|
||||||
@ -183,8 +186,7 @@ class _NotificationScreenState extends State<NotificationScreen> {
|
|||||||
_fetchNotifications();
|
_fetchNotifications();
|
||||||
},
|
},
|
||||||
isLoading: _isBusy,
|
isLoading: _isBusy,
|
||||||
hasReachedMax: _totalCount != null &&
|
hasReachedMax: _totalCount != null && _notifications.length >= _totalCount!,
|
||||||
_notifications.length >= _totalCount!,
|
|
||||||
itemBuilder: (context, idx) {
|
itemBuilder: (context, idx) {
|
||||||
final nty = _notifications[idx];
|
final nty = _notifications[idx];
|
||||||
return Row(
|
return Row(
|
||||||
@ -216,29 +218,36 @@ class _NotificationScreenState extends State<NotificationScreen> {
|
|||||||
isAutoWarp: true,
|
isAutoWarp: true,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if ([
|
if (['interactive.reply', 'interactive.feedback', 'interactive.subscription']
|
||||||
'interactive.feedback',
|
.contains(nty.topic) &&
|
||||||
'interactive.subscription'
|
|
||||||
].contains(nty.topic) &&
|
|
||||||
nty.metadata['related_post'] != null)
|
nty.metadata['related_post'] != null)
|
||||||
StyledWidget(Container(
|
GestureDetector(
|
||||||
decoration: BoxDecoration(
|
child: Container(
|
||||||
borderRadius: const BorderRadius.all(
|
decoration: BoxDecoration(
|
||||||
Radius.circular(8)),
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: Theme.of(context).dividerColor,
|
color: Theme.of(context).dividerColor,
|
||||||
width: 1,
|
width: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: PostItem(
|
||||||
|
data: SnPost.fromJson(
|
||||||
|
nty.metadata['related_post']!,
|
||||||
|
),
|
||||||
|
showComments: false,
|
||||||
|
showReactions: false,
|
||||||
|
showMenu: false,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: PostItem(
|
onTap: () {
|
||||||
data: SnPost.fromJson(
|
GoRouter.of(context).pushNamed(
|
||||||
nty.metadata['related_post']!,
|
'postDetail',
|
||||||
),
|
pathParameters: {
|
||||||
showComments: false,
|
'slug': nty.metadata['related_post']!['id'].toString(),
|
||||||
showReactions: false,
|
},
|
||||||
showMenu: false,
|
);
|
||||||
),
|
},
|
||||||
)).padding(top: 8),
|
).padding(top: 8),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
@ -263,10 +272,8 @@ class _NotificationScreenState extends State<NotificationScreen> {
|
|||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Symbols.check),
|
icon: const Icon(Symbols.check),
|
||||||
padding: EdgeInsets.all(0),
|
padding: EdgeInsets.all(0),
|
||||||
visualDensity:
|
visualDensity: const VisualDensity(horizontal: -4, vertical: -4),
|
||||||
const VisualDensity(horizontal: -4, vertical: -4),
|
onPressed: _isSubmitting ? null : () => _markOneAsRead(nty),
|
||||||
onPressed:
|
|
||||||
_isSubmitting ? null : () => _markOneAsRead(nty),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
).padding(horizontal: 16);
|
).padding(horizontal: 16);
|
||||||
|
@ -6,7 +6,6 @@ import 'package:gap/gap.dart';
|
|||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:responsive_framework/responsive_framework.dart';
|
|
||||||
import 'package: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/userinfo.dart';
|
import 'package:surface/providers/userinfo.dart';
|
||||||
@ -17,7 +16,6 @@ import 'package:surface/widgets/navigation/app_background.dart';
|
|||||||
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
||||||
import 'package:surface/widgets/post/post_comment_list.dart';
|
import 'package:surface/widgets/post/post_comment_list.dart';
|
||||||
import 'package:surface/widgets/post/post_item.dart';
|
import 'package:surface/widgets/post/post_item.dart';
|
||||||
import 'package:surface/widgets/post/post_mini_editor.dart';
|
|
||||||
|
|
||||||
class PostDetailScreen extends StatefulWidget {
|
class PostDetailScreen extends StatefulWidget {
|
||||||
final String slug;
|
final String slug;
|
||||||
@ -64,7 +62,8 @@ class _PostDetailScreenState extends State<PostDetailScreen> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final ua = context.watch<UserProvider>();
|
final ua = context.watch<UserProvider>();
|
||||||
final devicePixelRatio = MediaQuery.of(context).devicePixelRatio;
|
|
||||||
|
final double maxWidth = _data?.type == 'video' ? double.infinity : 640;
|
||||||
|
|
||||||
return AppBackground(
|
return AppBackground(
|
||||||
isRoot: widget.onBack != null,
|
isRoot: widget.onBack != null,
|
||||||
@ -114,7 +113,7 @@ class _PostDetailScreenState extends State<PostDetailScreen> {
|
|||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
child: PostItem(
|
child: PostItem(
|
||||||
data: _data!,
|
data: _data!,
|
||||||
maxWidth: 640,
|
maxWidth: maxWidth,
|
||||||
showComments: false,
|
showComments: false,
|
||||||
showFullPost: true,
|
showFullPost: true,
|
||||||
onChanged: (data) {
|
onChanged: (data) {
|
||||||
@ -125,11 +124,11 @@ class _PostDetailScreenState extends State<PostDetailScreen> {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SliverToBoxAdapter(child: Divider(height: 1)),
|
if (_data != null && _data!.type != 'video') const SliverToBoxAdapter(child: Divider(height: 1)),
|
||||||
if (_data != null)
|
if (_data != null && _data!.type != 'video')
|
||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
child: Container(
|
child: Container(
|
||||||
constraints: const BoxConstraints(maxWidth: 640),
|
constraints: BoxConstraints(maxWidth: maxWidth),
|
||||||
child: Row(
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
@ -142,51 +141,30 @@ class _PostDetailScreenState extends State<PostDetailScreen> {
|
|||||||
).padding(horizontal: 20, vertical: 12).center(),
|
).padding(horizontal: 20, vertical: 12).center(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (_data != null && ua.isAuthorized)
|
if (_data != null && ua.isAuthorized && _data!.type != 'video')
|
||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
child: Container(
|
child: PostCommentQuickAction(
|
||||||
height: 240,
|
parentPost: _data!,
|
||||||
constraints: const BoxConstraints(maxWidth: 640),
|
maxWidth: maxWidth,
|
||||||
margin:
|
onPosted: () {
|
||||||
ResponsiveBreakpoints.of(context).largerThan(MOBILE) ? const EdgeInsets.all(8) : EdgeInsets.zero,
|
setState(() {
|
||||||
decoration: BoxDecoration(
|
_data = _data!.copyWith(
|
||||||
borderRadius: ResponsiveBreakpoints.of(context).largerThan(MOBILE)
|
metric: _data!.metric.copyWith(
|
||||||
? const BorderRadius.all(Radius.circular(8))
|
replyCount: _data!.metric.replyCount + 1,
|
||||||
: BorderRadius.zero,
|
),
|
||||||
border: ResponsiveBreakpoints.of(context).largerThan(MOBILE)
|
);
|
||||||
? Border.all(
|
});
|
||||||
color: Theme.of(context).dividerColor,
|
_childListKey.currentState!.refresh();
|
||||||
width: 1 / devicePixelRatio,
|
},
|
||||||
)
|
),
|
||||||
: Border.symmetric(
|
|
||||||
horizontal: BorderSide(
|
|
||||||
color: Theme.of(context).dividerColor,
|
|
||||||
width: 1 / devicePixelRatio,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: PostMiniEditor(
|
|
||||||
postReplyId: _data!.id,
|
|
||||||
onPost: () {
|
|
||||||
setState(() {
|
|
||||||
_data = _data!.copyWith(
|
|
||||||
metric: _data!.metric.copyWith(
|
|
||||||
replyCount: _data!.metric.replyCount + 1,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
_childListKey.currentState!.refresh();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
).center(),
|
|
||||||
),
|
),
|
||||||
if (_data != null)
|
if (_data != null && _data!.type != 'video')
|
||||||
PostCommentSliverList(
|
PostCommentSliverList(
|
||||||
key: _childListKey,
|
key: _childListKey,
|
||||||
parentPostId: _data!.id,
|
parentPost: _data!,
|
||||||
maxWidth: 640,
|
maxWidth: maxWidth,
|
||||||
),
|
),
|
||||||
SliverGap(math.max(MediaQuery.of(context).padding.bottom, 16)),
|
if (_data != null && _data!.type == 'video') SliverGap(math.max(MediaQuery.of(context).padding.bottom, 16)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,7 +1,6 @@
|
|||||||
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:gap/gap.dart';
|
import 'package:gap/gap.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:styled_widget/styled_widget.dart';
|
||||||
@ -134,7 +133,7 @@ class _PostSearchScreenState extends State<PostSearchScreen> {
|
|||||||
body: Stack(
|
body: Stack(
|
||||||
children: [
|
children: [
|
||||||
InfiniteList(
|
InfiniteList(
|
||||||
padding: const EdgeInsets.only(top: 100),
|
padding: const EdgeInsets.only(top: 100 + 8),
|
||||||
itemCount: _posts.length,
|
itemCount: _posts.length,
|
||||||
isLoading: _isBusy,
|
isLoading: _isBusy,
|
||||||
hasReachedMax: _postCount != null && _posts.length >= _postCount!,
|
hasReachedMax: _postCount != null && _posts.length >= _postCount!,
|
||||||
@ -142,27 +141,18 @@ class _PostSearchScreenState extends State<PostSearchScreen> {
|
|||||||
_fetchPosts();
|
_fetchPosts();
|
||||||
},
|
},
|
||||||
itemBuilder: (context, idx) {
|
itemBuilder: (context, idx) {
|
||||||
return GestureDetector(
|
return OpenablePostItem(
|
||||||
child: PostItem(
|
data: _posts[idx],
|
||||||
data: _posts[idx],
|
maxWidth: 640,
|
||||||
maxWidth: 640,
|
onChanged: (data) {
|
||||||
onChanged: (data) {
|
setState(() => _posts[idx] = data);
|
||||||
setState(() => _posts[idx] = data);
|
},
|
||||||
},
|
onDeleted: () {
|
||||||
onDeleted: () {
|
_refreshPosts();
|
||||||
_refreshPosts();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
onTap: () {
|
|
||||||
GoRouter.of(context).pushNamed(
|
|
||||||
'postDetail',
|
|
||||||
pathParameters: {'slug': _posts[idx].id.toString()},
|
|
||||||
extra: _posts[idx],
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
separatorBuilder: (context, index) => const Divider(height: 1),
|
separatorBuilder: (_, __) => const Gap(8),
|
||||||
),
|
),
|
||||||
Positioned(
|
Positioned(
|
||||||
top: 16,
|
top: 16,
|
||||||
|
@ -287,8 +287,8 @@ class _PostPublisherScreenState extends State<PostPublisherScreen> with SingleTi
|
|||||||
Theme(
|
Theme(
|
||||||
data: Theme.of(context).copyWith(
|
data: Theme.of(context).copyWith(
|
||||||
appBarTheme: Theme.of(context).appBarTheme.copyWith(
|
appBarTheme: Theme.of(context).appBarTheme.copyWith(
|
||||||
foregroundColor: Colors.white,
|
foregroundColor: Colors.white,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: SliverAppBar(
|
child: SliverAppBar(
|
||||||
expandedHeight: _appBarHeight,
|
expandedHeight: _appBarHeight,
|
||||||
@ -597,25 +597,16 @@ class _PublisherPostList extends StatelessWidget {
|
|||||||
hasReachedMax: postCount != null && posts.length >= postCount!,
|
hasReachedMax: postCount != null && posts.length >= postCount!,
|
||||||
onFetchData: fetchPosts,
|
onFetchData: fetchPosts,
|
||||||
itemBuilder: (context, idx) {
|
itemBuilder: (context, idx) {
|
||||||
return GestureDetector(
|
return OpenablePostItem(
|
||||||
child: PostItem(
|
data: posts[idx],
|
||||||
data: posts[idx],
|
maxWidth: 640,
|
||||||
maxWidth: 640,
|
onChanged: (data) {
|
||||||
onChanged: (data) {
|
onChanged(idx, data);
|
||||||
onChanged(idx, data);
|
|
||||||
},
|
|
||||||
onDeleted: onDeleted,
|
|
||||||
),
|
|
||||||
onTap: () {
|
|
||||||
GoRouter.of(context).pushNamed(
|
|
||||||
'postDetail',
|
|
||||||
pathParameters: {'slug': posts[idx].id.toString()},
|
|
||||||
extra: posts[idx],
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
|
onDeleted: onDeleted,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
separatorBuilder: (context, index) => const Divider(height: 1),
|
separatorBuilder: (_, __) => const Gap(8),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -100,6 +100,12 @@ class _RealmScreenState extends State<RealmScreen> {
|
|||||||
leading: AutoAppBarLeading(),
|
leading: AutoAppBarLeading(),
|
||||||
title: Text('screenRealm').tr(),
|
title: Text('screenRealm').tr(),
|
||||||
actions: [
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Symbols.globe),
|
||||||
|
onPressed: () {
|
||||||
|
GoRouter.of(context).pushNamed('realmDiscovery');
|
||||||
|
},
|
||||||
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
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: () {
|
||||||
@ -180,7 +186,11 @@ class _RealmScreenState extends State<RealmScreen> {
|
|||||||
GoRouter.of(context).pushNamed(
|
GoRouter.of(context).pushNamed(
|
||||||
'realmDetail',
|
'realmDetail',
|
||||||
pathParameters: {'alias': realm.alias},
|
pathParameters: {'alias': realm.alias},
|
||||||
);
|
).then((value) {
|
||||||
|
if (value == true) {
|
||||||
|
_fetchRealms();
|
||||||
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -238,7 +248,11 @@ class _RealmScreenState extends State<RealmScreen> {
|
|||||||
GoRouter.of(context).pushNamed(
|
GoRouter.of(context).pushNamed(
|
||||||
'realmDetail',
|
'realmDetail',
|
||||||
pathParameters: {'alias': realm.alias},
|
pathParameters: {'alias': realm.alias},
|
||||||
);
|
).then((value) {
|
||||||
|
if (value == true) {
|
||||||
|
_fetchRealms();
|
||||||
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -50,6 +50,8 @@ class _RealmManageScreenState extends State<RealmManageScreen> {
|
|||||||
_aliasController.text = out.alias;
|
_aliasController.text = out.alias;
|
||||||
_nameController.text = out.name;
|
_nameController.text = out.name;
|
||||||
_descriptionController.text = out.description;
|
_descriptionController.text = out.description;
|
||||||
|
_isPublic = out.isPublic;
|
||||||
|
_isCommunity = out.isCommunity;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// ignore: use_build_context_synchronously
|
// ignore: use_build_context_synchronously
|
||||||
if (context.mounted) context.showErrorDialog(err);
|
if (context.mounted) context.showErrorDialog(err);
|
||||||
@ -67,6 +69,9 @@ class _RealmManageScreenState extends State<RealmManageScreen> {
|
|||||||
|
|
||||||
final _imagePicker = ImagePicker();
|
final _imagePicker = ImagePicker();
|
||||||
|
|
||||||
|
bool _isPublic = false;
|
||||||
|
bool _isCommunity = false;
|
||||||
|
|
||||||
Future<void> _updateImage(String place) async {
|
Future<void> _updateImage(String place) 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;
|
||||||
@ -138,6 +143,8 @@ class _RealmManageScreenState extends State<RealmManageScreen> {
|
|||||||
'description': _descriptionController.text,
|
'description': _descriptionController.text,
|
||||||
'avatar': _avatar,
|
'avatar': _avatar,
|
||||||
'banner': _banner,
|
'banner': _banner,
|
||||||
|
'is_public': _isPublic,
|
||||||
|
'is_community': _isCommunity,
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -293,6 +300,23 @@ class _RealmManageScreenState extends State<RealmManageScreen> {
|
|||||||
FocusManager.instance.primaryFocus?.unfocus(),
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
),
|
),
|
||||||
const Gap(12),
|
const Gap(12),
|
||||||
|
CheckboxListTile(
|
||||||
|
value: _isPublic,
|
||||||
|
title: Text('realmIsPublic'.tr()),
|
||||||
|
subtitle: Text('realmIsPublicDescription'.tr()),
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() => _isPublic = value ?? false);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
CheckboxListTile(
|
||||||
|
value: _isCommunity,
|
||||||
|
title: Text('realmIsCommunity'.tr()),
|
||||||
|
subtitle: Text('realmIsCommunityDescription'.tr()),
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() => _isCommunity = value ?? false);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const Gap(12),
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
children: [
|
children: [
|
||||||
|
@ -8,9 +8,11 @@ import 'package:styled_widget/styled_widget.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/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/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:very_good_infinite_list/very_good_infinite_list.dart';
|
import 'package:very_good_infinite_list/very_good_infinite_list.dart';
|
||||||
@ -187,7 +189,7 @@ class _RealmMemberListWidgetState extends State<_RealmMemberListWidget> {
|
|||||||
final sn = context.read<SnNetworkProvider>();
|
final sn = context.read<SnNetworkProvider>();
|
||||||
final resp = await sn.client.get('/cgi/id/realms/${widget.realm!.alias}/members', queryParameters: {
|
final resp = await sn.client.get('/cgi/id/realms/${widget.realm!.alias}/members', queryParameters: {
|
||||||
'take': 10,
|
'take': 10,
|
||||||
'offset': 0,
|
'offset': _members.length,
|
||||||
});
|
});
|
||||||
|
|
||||||
final out = List<SnRealmMember>.from(
|
final out = List<SnRealmMember>.from(
|
||||||
@ -229,13 +231,35 @@ class _RealmMemberListWidgetState extends State<_RealmMemberListWidget> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _showMemberAdd() {
|
Future<void> _addMember(SnAccount related) async {
|
||||||
showModalBottomSheet(
|
setState(() => _isBusy = true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
await sn.client.post(
|
||||||
|
'/cgi/id/realms/${widget.realm!.alias}/members',
|
||||||
|
data: {'related': related.name},
|
||||||
|
);
|
||||||
|
if (!mounted) return;
|
||||||
|
context.showSnackbar('realmMemberAdded'.tr());
|
||||||
|
} catch (err) {
|
||||||
|
if (!mounted) return;
|
||||||
|
context.showErrorDialog(err);
|
||||||
|
} finally {
|
||||||
|
setState(() => _isBusy = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showMemberAdd() async {
|
||||||
|
final user = await showModalBottomSheet<SnAccount?>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => _NewRealmMemberWidget(
|
builder: (context) => AccountSelect(
|
||||||
realm: widget.realm!,
|
title: 'realmMemberAdd'.tr(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
if (!mounted) return;
|
||||||
|
if (user == null) return;
|
||||||
|
_addMember(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -293,85 +317,6 @@ class _RealmMemberListWidgetState extends State<_RealmMemberListWidget> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _NewRealmMemberWidget extends StatefulWidget {
|
|
||||||
final SnRealm realm;
|
|
||||||
|
|
||||||
const _NewRealmMemberWidget({required this.realm});
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<_NewRealmMemberWidget> createState() => _NewRealmMemberWidgetState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _NewRealmMemberWidgetState extends State<_NewRealmMemberWidget> {
|
|
||||||
bool _isBusy = false;
|
|
||||||
|
|
||||||
final TextEditingController _relatedController = TextEditingController();
|
|
||||||
|
|
||||||
Future<void> _performAction() async {
|
|
||||||
if (_relatedController.text.isEmpty) return;
|
|
||||||
|
|
||||||
setState(() => _isBusy = true);
|
|
||||||
|
|
||||||
try {
|
|
||||||
final sn = context.read<SnNetworkProvider>();
|
|
||||||
await sn.client.post(
|
|
||||||
'/cgi/id/realms/${widget.realm.alias}/members',
|
|
||||||
data: {
|
|
||||||
'related': _relatedController.text,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
if (!mounted) return;
|
|
||||||
Navigator.pop(context, true);
|
|
||||||
context.showSnackbar('channelMemberAdded'.tr());
|
|
||||||
} catch (err) {
|
|
||||||
if (!mounted) return;
|
|
||||||
context.showErrorDialog(err);
|
|
||||||
} finally {
|
|
||||||
setState(() => _isBusy = false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
super.dispose();
|
|
||||||
_relatedController.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return StyledWidget(Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'realmMemberAdd',
|
|
||||||
style: Theme.of(context).textTheme.titleLarge,
|
|
||||||
).tr(),
|
|
||||||
const Gap(12),
|
|
||||||
TextField(
|
|
||||||
controller: _relatedController,
|
|
||||||
readOnly: _isBusy,
|
|
||||||
autocorrect: false,
|
|
||||||
autofocus: true,
|
|
||||||
textCapitalization: TextCapitalization.none,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'fieldMemberRelatedName'.tr(),
|
|
||||||
suffix: SizedBox(
|
|
||||||
height: 24,
|
|
||||||
child: IconButton(
|
|
||||||
onPressed: _isBusy ? null : () => _performAction(),
|
|
||||||
icon: Icon(Symbols.send),
|
|
||||||
visualDensity: const VisualDensity(horizontal: -4, vertical: -4),
|
|
||||||
padding: EdgeInsets.zero,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
)).padding(all: 24);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _RealmSettingsWidget extends StatefulWidget {
|
class _RealmSettingsWidget extends StatefulWidget {
|
||||||
final SnRealm? realm;
|
final SnRealm? realm;
|
||||||
final Function() onUpdate;
|
final Function() onUpdate;
|
||||||
@ -398,12 +343,31 @@ class _RealmSettingsWidgetState extends State<_RealmSettingsWidget> {
|
|||||||
setState(() => _isBusy = true);
|
setState(() => _isBusy = true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await sn.client.delete('/cgi/id/realms/${widget.realm!.alias}');
|
await sn.client.delete('/cgi/id/realms/${widget.realm!.id}');
|
||||||
|
if (!mounted) return;
|
||||||
|
Navigator.pop(context, true);
|
||||||
|
} catch (err) {
|
||||||
|
if (!mounted) return;
|
||||||
|
context.showErrorDialog(err);
|
||||||
|
} finally {
|
||||||
|
setState(() => _isBusy = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _leaveRealm() async {
|
||||||
|
final confirm = await context.showConfirmDialog(
|
||||||
|
'realmLeave'.tr(),
|
||||||
|
'realmLeaveDescription'.tr(),
|
||||||
|
);
|
||||||
|
if (!confirm) return;
|
||||||
|
if (!mounted) return;
|
||||||
|
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
|
||||||
|
try {
|
||||||
|
await sn.client.delete('/cgi/id/realms/${widget.realm!.alias}/members/me');
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
Navigator.pop(context, true);
|
Navigator.pop(context, true);
|
||||||
context.showSnackbar('realmDeleted'.tr(args: [
|
|
||||||
'#${widget.realm!.alias}',
|
|
||||||
]));
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
context.showErrorDialog(err);
|
context.showErrorDialog(err);
|
||||||
@ -422,22 +386,31 @@ class _RealmSettingsWidgetState extends State<_RealmSettingsWidget> {
|
|||||||
children: [
|
children: [
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Symbols.edit),
|
leading: const Icon(Symbols.logout),
|
||||||
trailing: const Icon(Symbols.chevron_right),
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
title: Text('realmEdit').tr(),
|
title: Text('realmLeave').tr(),
|
||||||
subtitle: Text('realmEditDescription').tr(),
|
subtitle: Text('realmLeaveDescription').tr(),
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
onTap: () {
|
onTap: _isBusy ? null : () => _leaveRealm(),
|
||||||
GoRouter.of(context).pushNamed(
|
|
||||||
'realmManage',
|
|
||||||
queryParameters: {'editing': widget.realm!.alias},
|
|
||||||
).then((value) {
|
|
||||||
if (value != null) {
|
|
||||||
widget.onUpdate();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
|
if (isOwned)
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Symbols.edit),
|
||||||
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
|
title: Text('realmEdit').tr(),
|
||||||
|
subtitle: Text('realmEditDescription').tr(),
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
onTap: () {
|
||||||
|
GoRouter.of(context).pushNamed(
|
||||||
|
'realmManage',
|
||||||
|
queryParameters: {'editing': widget.realm!.alias},
|
||||||
|
).then((value) {
|
||||||
|
if (value != null) {
|
||||||
|
widget.onUpdate();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
if (isOwned)
|
if (isOwned)
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Symbols.delete),
|
leading: const Icon(Symbols.delete),
|
||||||
|
290
lib/screens/realm/realm_discovery.dart
Normal file
290
lib/screens/realm/realm_discovery.dart
Normal file
@ -0,0 +1,290 @@
|
|||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:gap/gap.dart';
|
||||||
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
import 'package:surface/providers/sn_network.dart';
|
||||||
|
import 'package:surface/providers/userinfo.dart';
|
||||||
|
import 'package:surface/types/chat.dart';
|
||||||
|
import 'package:surface/types/realm.dart';
|
||||||
|
import 'package:surface/widgets/account/account_image.dart';
|
||||||
|
import 'package:surface/widgets/dialog.dart';
|
||||||
|
import 'package:surface/widgets/loading_indicator.dart';
|
||||||
|
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
||||||
|
import 'package:surface/widgets/universal_image.dart';
|
||||||
|
|
||||||
|
class RealmDiscoveryScreen extends StatefulWidget {
|
||||||
|
const RealmDiscoveryScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<RealmDiscoveryScreen> createState() => _RealmDiscoveryScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _RealmDiscoveryScreenState extends State<RealmDiscoveryScreen> {
|
||||||
|
List<SnRealm>? _realms;
|
||||||
|
bool _isBusy = false;
|
||||||
|
|
||||||
|
Future<void> _fetchRealms() async {
|
||||||
|
try {
|
||||||
|
setState(() => _isBusy = true);
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
final resp = await sn.client.get('/cgi/id/realms');
|
||||||
|
_realms = List<SnRealm>.from(
|
||||||
|
resp.data?.map((e) => SnRealm.fromJson(e)) ?? [],
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
if (mounted) context.showErrorDialog(err);
|
||||||
|
rethrow;
|
||||||
|
} finally {
|
||||||
|
setState(() => _isBusy = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_fetchRealms();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
|
||||||
|
return AppScaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text('screenRealmDiscovery').tr(),
|
||||||
|
),
|
||||||
|
body: Column(
|
||||||
|
children: [
|
||||||
|
LoadingIndicator(isActive: _isBusy),
|
||||||
|
Expanded(
|
||||||
|
child: RefreshIndicator(
|
||||||
|
onRefresh: _fetchRealms,
|
||||||
|
child: ListView.builder(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
itemCount: _realms?.length ?? 0,
|
||||||
|
itemBuilder: (context, idx) {
|
||||||
|
final realm = _realms![idx];
|
||||||
|
return Container(
|
||||||
|
constraints: BoxConstraints(maxWidth: 640),
|
||||||
|
child: Card(
|
||||||
|
margin: const EdgeInsets.all(12),
|
||||||
|
child: InkWell(
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
AspectRatio(
|
||||||
|
aspectRatio: 16 / 7,
|
||||||
|
child: Stack(
|
||||||
|
clipBehavior: Clip.none,
|
||||||
|
fit: StackFit.expand,
|
||||||
|
children: [
|
||||||
|
ClipRRect(
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
|
child: Container(
|
||||||
|
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||||
|
child: (realm.banner?.isEmpty ?? true)
|
||||||
|
? const SizedBox.shrink()
|
||||||
|
: AutoResizeUniversalImage(
|
||||||
|
sn.getAttachmentUrl(realm.banner!),
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
bottom: -30,
|
||||||
|
left: 18,
|
||||||
|
child: AccountImage(
|
||||||
|
content: realm.avatar,
|
||||||
|
radius: 24,
|
||||||
|
fallbackWidget: const Icon(Symbols.group, size: 24),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Gap(20 + 12),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(realm.name).textStyle(Theme.of(context).textTheme.titleMedium!),
|
||||||
|
Text(realm.description).textStyle(Theme.of(context).textTheme.bodySmall!),
|
||||||
|
],
|
||||||
|
).padding(horizontal: 24, bottom: 14),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => _RealmJoinPopup(realm: realm),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
).center();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _RealmJoinPopup extends StatefulWidget {
|
||||||
|
final SnRealm realm;
|
||||||
|
|
||||||
|
const _RealmJoinPopup({required this.realm});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<_RealmJoinPopup> createState() => _RealmJoinPopupState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _RealmJoinPopupState extends State<_RealmJoinPopup> {
|
||||||
|
final List<String> _planJoinChannels = List.empty(growable: true);
|
||||||
|
|
||||||
|
List<SnChannel>? _channels;
|
||||||
|
bool _isBusy = false;
|
||||||
|
bool _isJoining = false;
|
||||||
|
|
||||||
|
Future<void> _fetchPublicChannels() async {
|
||||||
|
try {
|
||||||
|
setState(() => _isBusy = true);
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
final resp = await sn.client.get('/cgi/im/channels/${widget.realm.alias}/public');
|
||||||
|
final out = List<SnChannel>.from(
|
||||||
|
resp.data.map((e) => SnChannel.fromJson(e)).cast<SnChannel>(),
|
||||||
|
);
|
||||||
|
setState(() => _channels = out);
|
||||||
|
} catch (err) {
|
||||||
|
if (!mounted) return;
|
||||||
|
context.showErrorDialog(err);
|
||||||
|
} finally {
|
||||||
|
setState(() => _isBusy = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _joinRealm() async {
|
||||||
|
try {
|
||||||
|
setState(() => _isJoining = true);
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
final ua = context.read<UserProvider>();
|
||||||
|
await sn.client.post('/cgi/id/realms/${widget.realm.alias}/members', data: {
|
||||||
|
'related': ua.user?.name,
|
||||||
|
});
|
||||||
|
await _joinSelectedChannels();
|
||||||
|
if (!mounted) return;
|
||||||
|
context.showSnackbar('realmJoined'.tr(args: [widget.realm.name]));
|
||||||
|
Navigator.pop(context);
|
||||||
|
} catch (err) {
|
||||||
|
if (!mounted) return;
|
||||||
|
context.showErrorDialog(err);
|
||||||
|
} finally {
|
||||||
|
setState(() => _isJoining = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _joinSelectedChannels() async {
|
||||||
|
if (_planJoinChannels.isEmpty) return;
|
||||||
|
for (final channel in _planJoinChannels) {
|
||||||
|
try {
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
final ua = context.read<UserProvider>();
|
||||||
|
await sn.client.post('/cgi/im/channels/${widget.realm.alias}/$channel/members', data: {
|
||||||
|
'related': ua.user?.name,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
if (!mounted) return;
|
||||||
|
context.showErrorDialog(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_fetchPublicChannels();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const Icon(Symbols.group_add, size: 24),
|
||||||
|
const Gap(16),
|
||||||
|
Text('realmJoin', style: Theme.of(context).textTheme.titleLarge).tr(),
|
||||||
|
],
|
||||||
|
).padding(horizontal: 20, top: 16, bottom: 12),
|
||||||
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
widget.realm.name,
|
||||||
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
widget.realm.description,
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: _isJoining ? null : () => _joinRealm(),
|
||||||
|
child: Text('join'.tr()),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).padding(horizontal: 24, bottom: 12),
|
||||||
|
const Divider(height: 1),
|
||||||
|
LoadingIndicator(isActive: _isBusy),
|
||||||
|
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),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: ListView.builder(
|
||||||
|
itemCount: _channels?.length ?? 0,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final channel = _channels![index];
|
||||||
|
return CheckboxListTile(
|
||||||
|
value: _planJoinChannels.contains(channel.alias),
|
||||||
|
title: Text(channel.name),
|
||||||
|
subtitle: Text(
|
||||||
|
channel.description,
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
secondary: AccountImage(
|
||||||
|
content: null,
|
||||||
|
fallbackWidget: const Icon(Symbols.chat, size: 20),
|
||||||
|
),
|
||||||
|
onChanged: (value) {
|
||||||
|
value ??= false;
|
||||||
|
if (value) {
|
||||||
|
setState(() => _planJoinChannels.add(channel.alias));
|
||||||
|
} else {
|
||||||
|
setState(() => _planJoinChannels.remove(channel.alias));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -33,7 +33,7 @@ Future<ThemeData> createAppTheme(
|
|||||||
brightness: brightness,
|
brightness: brightness,
|
||||||
);
|
);
|
||||||
|
|
||||||
final hasAppBarBlurry = prefs.getBool(kAppbarTransparentStoreKey) ?? false;
|
final hasAppBarTransparent = prefs.getBool(kAppbarTransparentStoreKey) ?? false;
|
||||||
final useM3 = useMaterial3 ?? (prefs.getBool(kMaterialYouToggleStoreKey) ?? true);
|
final useM3 = useMaterial3 ?? (prefs.getBool(kMaterialYouToggleStoreKey) ?? true);
|
||||||
|
|
||||||
return ThemeData(
|
return ThemeData(
|
||||||
@ -51,13 +51,13 @@ Future<ThemeData> createAppTheme(
|
|||||||
),
|
),
|
||||||
appBarTheme: AppBarTheme(
|
appBarTheme: AppBarTheme(
|
||||||
centerTitle: true,
|
centerTitle: true,
|
||||||
elevation: hasAppBarBlurry ? 0 : null,
|
elevation: hasAppBarTransparent ? 0 : null,
|
||||||
backgroundColor: hasAppBarBlurry ? colorScheme.primary.withOpacity(0.3) : colorScheme.primary,
|
backgroundColor: hasAppBarTransparent ? Colors.transparent : colorScheme.primary,
|
||||||
foregroundColor: hasAppBarBlurry ? colorScheme.onSurface : colorScheme.onPrimary,
|
foregroundColor: hasAppBarTransparent ? colorScheme.onSurface : colorScheme.onPrimary,
|
||||||
),
|
),
|
||||||
pageTransitionsTheme: PageTransitionsTheme(
|
pageTransitionsTheme: PageTransitionsTheme(
|
||||||
builders: {
|
builders: {
|
||||||
TargetPlatform.android: PredictiveBackPageTransitionsBuilder(),
|
TargetPlatform.android: ZoomPageTransitionsBuilder(),
|
||||||
TargetPlatform.iOS: CupertinoPageTransitionsBuilder(),
|
TargetPlatform.iOS: CupertinoPageTransitionsBuilder(),
|
||||||
TargetPlatform.macOS: ZoomPageTransitionsBuilder(),
|
TargetPlatform.macOS: ZoomPageTransitionsBuilder(),
|
||||||
TargetPlatform.fuchsia: ZoomPageTransitionsBuilder(),
|
TargetPlatform.fuchsia: ZoomPageTransitionsBuilder(),
|
||||||
|
@ -20,7 +20,7 @@ class SnAccount with _$SnAccount {
|
|||||||
required String description,
|
required String description,
|
||||||
required String name,
|
required String name,
|
||||||
required String nick,
|
required String nick,
|
||||||
required Map<String, dynamic> permNodes,
|
@Default({}) Map<String, dynamic> permNodes,
|
||||||
required String language,
|
required String language,
|
||||||
required SnAccountProfile? profile,
|
required SnAccountProfile? profile,
|
||||||
@Default([]) List<SnAccountBadge> badges,
|
@Default([]) List<SnAccountBadge> badges,
|
||||||
|
@ -385,7 +385,7 @@ class _$SnAccountImpl extends _SnAccount {
|
|||||||
required this.description,
|
required this.description,
|
||||||
required this.name,
|
required this.name,
|
||||||
required this.nick,
|
required this.nick,
|
||||||
required final Map<String, dynamic> permNodes,
|
final Map<String, dynamic> permNodes = const {},
|
||||||
required this.language,
|
required this.language,
|
||||||
required this.profile,
|
required this.profile,
|
||||||
final List<SnAccountBadge> badges = const [],
|
final List<SnAccountBadge> badges = const [],
|
||||||
@ -437,6 +437,7 @@ class _$SnAccountImpl extends _SnAccount {
|
|||||||
final String nick;
|
final String nick;
|
||||||
final Map<String, dynamic> _permNodes;
|
final Map<String, dynamic> _permNodes;
|
||||||
@override
|
@override
|
||||||
|
@JsonKey()
|
||||||
Map<String, dynamic> get permNodes {
|
Map<String, dynamic> get permNodes {
|
||||||
if (_permNodes is EqualUnmodifiableMapView) return _permNodes;
|
if (_permNodes is EqualUnmodifiableMapView) return _permNodes;
|
||||||
// ignore: implicit_dynamic_type
|
// ignore: implicit_dynamic_type
|
||||||
@ -566,7 +567,7 @@ abstract class _SnAccount extends SnAccount {
|
|||||||
required final String description,
|
required final String description,
|
||||||
required final String name,
|
required final String name,
|
||||||
required final String nick,
|
required final String nick,
|
||||||
required final Map<String, dynamic> permNodes,
|
final Map<String, dynamic> permNodes,
|
||||||
required final String language,
|
required final String language,
|
||||||
required final SnAccountProfile? profile,
|
required final SnAccountProfile? profile,
|
||||||
final List<SnAccountBadge> badges,
|
final List<SnAccountBadge> badges,
|
||||||
|
@ -25,7 +25,7 @@ _$SnAccountImpl _$$SnAccountImplFromJson(Map<String, dynamic> json) =>
|
|||||||
description: json['description'] as String,
|
description: json['description'] as String,
|
||||||
name: json['name'] as String,
|
name: json['name'] as String,
|
||||||
nick: json['nick'] as String,
|
nick: json['nick'] as String,
|
||||||
permNodes: json['perm_nodes'] as Map<String, dynamic>,
|
permNodes: json['perm_nodes'] as Map<String, dynamic>? ?? const {},
|
||||||
language: json['language'] as String,
|
language: json['language'] as String,
|
||||||
profile: json['profile'] == null
|
profile: json['profile'] == null
|
||||||
? null
|
? null
|
||||||
|
@ -1,9 +1,17 @@
|
|||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
part 'check_in.freezed.dart';
|
part 'check_in.freezed.dart';
|
||||||
|
|
||||||
part 'check_in.g.dart';
|
part 'check_in.g.dart';
|
||||||
|
|
||||||
const List<String> kCheckInResultTierSymbols = ['大凶', '凶', '中平', '吉', '大吉'];
|
final List<String> kCheckInResultTierSymbols = [
|
||||||
|
'checkInResultTier1',
|
||||||
|
'checkInResultTier2',
|
||||||
|
'checkInResultTier3',
|
||||||
|
'checkInResultTier4',
|
||||||
|
'checkInResultTier5'
|
||||||
|
].map((e) => e.tr()).toList();
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
class SnCheckInRecord with _$SnCheckInRecord {
|
class SnCheckInRecord with _$SnCheckInRecord {
|
||||||
@ -21,8 +29,7 @@ class SnCheckInRecord with _$SnCheckInRecord {
|
|||||||
required int accountId,
|
required int accountId,
|
||||||
}) = _SnCheckInRecord;
|
}) = _SnCheckInRecord;
|
||||||
|
|
||||||
factory SnCheckInRecord.fromJson(Map<String, dynamic> json) =>
|
factory SnCheckInRecord.fromJson(Map<String, dynamic> json) => _$SnCheckInRecordFromJson(json);
|
||||||
_$SnCheckInRecordFromJson(json);
|
|
||||||
|
|
||||||
String get symbol => kCheckInResultTierSymbols[resultTier];
|
String get symbol => kCheckInResultTierSymbols[resultTier];
|
||||||
}
|
}
|
||||||
|
45
lib/types/poll.dart
Normal file
45
lib/types/poll.dart
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
|
part 'poll.freezed.dart';
|
||||||
|
part 'poll.g.dart';
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class SnPoll with _$SnPoll {
|
||||||
|
const factory SnPoll({
|
||||||
|
required int id,
|
||||||
|
required DateTime createdAt,
|
||||||
|
required DateTime updatedAt,
|
||||||
|
required dynamic deletedAt,
|
||||||
|
required dynamic expiredAt,
|
||||||
|
required List<SnPollOption> options,
|
||||||
|
required int accountId,
|
||||||
|
required SnPollMetric metric,
|
||||||
|
}) = _SnPoll;
|
||||||
|
|
||||||
|
factory SnPoll.fromJson(Map<String, Object?> json) => _$SnPollFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class SnPollMetric with _$SnPollMetric {
|
||||||
|
const factory SnPollMetric({
|
||||||
|
required int totalAnswer,
|
||||||
|
@Default({}) Map<String, int> byOptions,
|
||||||
|
@Default({}) Map<String, double> byOptionsPercentage,
|
||||||
|
}) = _SnPollMetric;
|
||||||
|
|
||||||
|
factory SnPollMetric.fromJson(Map<String, Object?> json) =>
|
||||||
|
_$SnPollMetricFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class SnPollOption with _$SnPollOption {
|
||||||
|
const factory SnPollOption({
|
||||||
|
required String id,
|
||||||
|
required String icon,
|
||||||
|
required String name,
|
||||||
|
required String description,
|
||||||
|
}) = _SnPollOption;
|
||||||
|
|
||||||
|
factory SnPollOption.fromJson(Map<String, Object?> json) =>
|
||||||
|
_$SnPollOptionFromJson(json);
|
||||||
|
}
|
761
lib/types/poll.freezed.dart
Normal file
761
lib/types/poll.freezed.dart
Normal file
@ -0,0 +1,761 @@
|
|||||||
|
// coverage:ignore-file
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||||
|
|
||||||
|
part of 'poll.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// FreezedGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
T _$identity<T>(T value) => value;
|
||||||
|
|
||||||
|
final _privateConstructorUsedError = UnsupportedError(
|
||||||
|
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
|
||||||
|
|
||||||
|
SnPoll _$SnPollFromJson(Map<String, dynamic> json) {
|
||||||
|
return _SnPoll.fromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$SnPoll {
|
||||||
|
int get id => throw _privateConstructorUsedError;
|
||||||
|
DateTime get createdAt => throw _privateConstructorUsedError;
|
||||||
|
DateTime get updatedAt => throw _privateConstructorUsedError;
|
||||||
|
dynamic get deletedAt => throw _privateConstructorUsedError;
|
||||||
|
dynamic get expiredAt => throw _privateConstructorUsedError;
|
||||||
|
List<SnPollOption> get options => throw _privateConstructorUsedError;
|
||||||
|
int get accountId => throw _privateConstructorUsedError;
|
||||||
|
SnPollMetric get metric => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
|
/// Serializes this SnPoll to a JSON map.
|
||||||
|
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
|
/// Create a copy of SnPoll
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
$SnPollCopyWith<SnPoll> get copyWith => throw _privateConstructorUsedError;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class $SnPollCopyWith<$Res> {
|
||||||
|
factory $SnPollCopyWith(SnPoll value, $Res Function(SnPoll) then) =
|
||||||
|
_$SnPollCopyWithImpl<$Res, SnPoll>;
|
||||||
|
@useResult
|
||||||
|
$Res call(
|
||||||
|
{int id,
|
||||||
|
DateTime createdAt,
|
||||||
|
DateTime updatedAt,
|
||||||
|
dynamic deletedAt,
|
||||||
|
dynamic expiredAt,
|
||||||
|
List<SnPollOption> options,
|
||||||
|
int accountId,
|
||||||
|
SnPollMetric metric});
|
||||||
|
|
||||||
|
$SnPollMetricCopyWith<$Res> get metric;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class _$SnPollCopyWithImpl<$Res, $Val extends SnPoll>
|
||||||
|
implements $SnPollCopyWith<$Res> {
|
||||||
|
_$SnPollCopyWithImpl(this._value, this._then);
|
||||||
|
|
||||||
|
// ignore: unused_field
|
||||||
|
final $Val _value;
|
||||||
|
// ignore: unused_field
|
||||||
|
final $Res Function($Val) _then;
|
||||||
|
|
||||||
|
/// Create a copy of SnPoll
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({
|
||||||
|
Object? id = null,
|
||||||
|
Object? createdAt = null,
|
||||||
|
Object? updatedAt = null,
|
||||||
|
Object? deletedAt = freezed,
|
||||||
|
Object? expiredAt = freezed,
|
||||||
|
Object? options = null,
|
||||||
|
Object? accountId = null,
|
||||||
|
Object? metric = null,
|
||||||
|
}) {
|
||||||
|
return _then(_value.copyWith(
|
||||||
|
id: null == id
|
||||||
|
? _value.id
|
||||||
|
: id // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,
|
||||||
|
createdAt: null == createdAt
|
||||||
|
? _value.createdAt
|
||||||
|
: createdAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,
|
||||||
|
updatedAt: null == updatedAt
|
||||||
|
? _value.updatedAt
|
||||||
|
: updatedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,
|
||||||
|
deletedAt: freezed == deletedAt
|
||||||
|
? _value.deletedAt
|
||||||
|
: deletedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as dynamic,
|
||||||
|
expiredAt: freezed == expiredAt
|
||||||
|
? _value.expiredAt
|
||||||
|
: expiredAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as dynamic,
|
||||||
|
options: null == options
|
||||||
|
? _value.options
|
||||||
|
: options // ignore: cast_nullable_to_non_nullable
|
||||||
|
as List<SnPollOption>,
|
||||||
|
accountId: null == accountId
|
||||||
|
? _value.accountId
|
||||||
|
: accountId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,
|
||||||
|
metric: null == metric
|
||||||
|
? _value.metric
|
||||||
|
: metric // ignore: cast_nullable_to_non_nullable
|
||||||
|
as SnPollMetric,
|
||||||
|
) as $Val);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a copy of SnPoll
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SnPollMetricCopyWith<$Res> get metric {
|
||||||
|
return $SnPollMetricCopyWith<$Res>(_value.metric, (value) {
|
||||||
|
return _then(_value.copyWith(metric: value) as $Val);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class _$$SnPollImplCopyWith<$Res> implements $SnPollCopyWith<$Res> {
|
||||||
|
factory _$$SnPollImplCopyWith(
|
||||||
|
_$SnPollImpl value, $Res Function(_$SnPollImpl) then) =
|
||||||
|
__$$SnPollImplCopyWithImpl<$Res>;
|
||||||
|
@override
|
||||||
|
@useResult
|
||||||
|
$Res call(
|
||||||
|
{int id,
|
||||||
|
DateTime createdAt,
|
||||||
|
DateTime updatedAt,
|
||||||
|
dynamic deletedAt,
|
||||||
|
dynamic expiredAt,
|
||||||
|
List<SnPollOption> options,
|
||||||
|
int accountId,
|
||||||
|
SnPollMetric metric});
|
||||||
|
|
||||||
|
@override
|
||||||
|
$SnPollMetricCopyWith<$Res> get metric;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class __$$SnPollImplCopyWithImpl<$Res>
|
||||||
|
extends _$SnPollCopyWithImpl<$Res, _$SnPollImpl>
|
||||||
|
implements _$$SnPollImplCopyWith<$Res> {
|
||||||
|
__$$SnPollImplCopyWithImpl(
|
||||||
|
_$SnPollImpl _value, $Res Function(_$SnPollImpl) _then)
|
||||||
|
: super(_value, _then);
|
||||||
|
|
||||||
|
/// Create a copy of SnPoll
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({
|
||||||
|
Object? id = null,
|
||||||
|
Object? createdAt = null,
|
||||||
|
Object? updatedAt = null,
|
||||||
|
Object? deletedAt = freezed,
|
||||||
|
Object? expiredAt = freezed,
|
||||||
|
Object? options = null,
|
||||||
|
Object? accountId = null,
|
||||||
|
Object? metric = null,
|
||||||
|
}) {
|
||||||
|
return _then(_$SnPollImpl(
|
||||||
|
id: null == id
|
||||||
|
? _value.id
|
||||||
|
: id // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,
|
||||||
|
createdAt: null == createdAt
|
||||||
|
? _value.createdAt
|
||||||
|
: createdAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,
|
||||||
|
updatedAt: null == updatedAt
|
||||||
|
? _value.updatedAt
|
||||||
|
: updatedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,
|
||||||
|
deletedAt: freezed == deletedAt
|
||||||
|
? _value.deletedAt
|
||||||
|
: deletedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as dynamic,
|
||||||
|
expiredAt: freezed == expiredAt
|
||||||
|
? _value.expiredAt
|
||||||
|
: expiredAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as dynamic,
|
||||||
|
options: null == options
|
||||||
|
? _value._options
|
||||||
|
: options // ignore: cast_nullable_to_non_nullable
|
||||||
|
as List<SnPollOption>,
|
||||||
|
accountId: null == accountId
|
||||||
|
? _value.accountId
|
||||||
|
: accountId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,
|
||||||
|
metric: null == metric
|
||||||
|
? _value.metric
|
||||||
|
: metric // ignore: cast_nullable_to_non_nullable
|
||||||
|
as SnPollMetric,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
@JsonSerializable()
|
||||||
|
class _$SnPollImpl implements _SnPoll {
|
||||||
|
const _$SnPollImpl(
|
||||||
|
{required this.id,
|
||||||
|
required this.createdAt,
|
||||||
|
required this.updatedAt,
|
||||||
|
required this.deletedAt,
|
||||||
|
required this.expiredAt,
|
||||||
|
required final List<SnPollOption> options,
|
||||||
|
required this.accountId,
|
||||||
|
required this.metric})
|
||||||
|
: _options = options;
|
||||||
|
|
||||||
|
factory _$SnPollImpl.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$$SnPollImplFromJson(json);
|
||||||
|
|
||||||
|
@override
|
||||||
|
final int id;
|
||||||
|
@override
|
||||||
|
final DateTime createdAt;
|
||||||
|
@override
|
||||||
|
final DateTime updatedAt;
|
||||||
|
@override
|
||||||
|
final dynamic deletedAt;
|
||||||
|
@override
|
||||||
|
final dynamic expiredAt;
|
||||||
|
final List<SnPollOption> _options;
|
||||||
|
@override
|
||||||
|
List<SnPollOption> get options {
|
||||||
|
if (_options is EqualUnmodifiableListView) return _options;
|
||||||
|
// ignore: implicit_dynamic_type
|
||||||
|
return EqualUnmodifiableListView(_options);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
final int accountId;
|
||||||
|
@override
|
||||||
|
final SnPollMetric metric;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'SnPoll(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, expiredAt: $expiredAt, options: $options, accountId: $accountId, metric: $metric)';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) ||
|
||||||
|
(other.runtimeType == runtimeType &&
|
||||||
|
other is _$SnPollImpl &&
|
||||||
|
(identical(other.id, id) || other.id == id) &&
|
||||||
|
(identical(other.createdAt, createdAt) ||
|
||||||
|
other.createdAt == createdAt) &&
|
||||||
|
(identical(other.updatedAt, updatedAt) ||
|
||||||
|
other.updatedAt == updatedAt) &&
|
||||||
|
const DeepCollectionEquality().equals(other.deletedAt, deletedAt) &&
|
||||||
|
const DeepCollectionEquality().equals(other.expiredAt, expiredAt) &&
|
||||||
|
const DeepCollectionEquality().equals(other._options, _options) &&
|
||||||
|
(identical(other.accountId, accountId) ||
|
||||||
|
other.accountId == accountId) &&
|
||||||
|
(identical(other.metric, metric) || other.metric == metric));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(
|
||||||
|
runtimeType,
|
||||||
|
id,
|
||||||
|
createdAt,
|
||||||
|
updatedAt,
|
||||||
|
const DeepCollectionEquality().hash(deletedAt),
|
||||||
|
const DeepCollectionEquality().hash(expiredAt),
|
||||||
|
const DeepCollectionEquality().hash(_options),
|
||||||
|
accountId,
|
||||||
|
metric);
|
||||||
|
|
||||||
|
/// Create a copy of SnPoll
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$$SnPollImplCopyWith<_$SnPollImpl> get copyWith =>
|
||||||
|
__$$SnPollImplCopyWithImpl<_$SnPollImpl>(this, _$identity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return _$$SnPollImplToJson(
|
||||||
|
this,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class _SnPoll implements SnPoll {
|
||||||
|
const factory _SnPoll(
|
||||||
|
{required final int id,
|
||||||
|
required final DateTime createdAt,
|
||||||
|
required final DateTime updatedAt,
|
||||||
|
required final dynamic deletedAt,
|
||||||
|
required final dynamic expiredAt,
|
||||||
|
required final List<SnPollOption> options,
|
||||||
|
required final int accountId,
|
||||||
|
required final SnPollMetric metric}) = _$SnPollImpl;
|
||||||
|
|
||||||
|
factory _SnPoll.fromJson(Map<String, dynamic> json) = _$SnPollImpl.fromJson;
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get id;
|
||||||
|
@override
|
||||||
|
DateTime get createdAt;
|
||||||
|
@override
|
||||||
|
DateTime get updatedAt;
|
||||||
|
@override
|
||||||
|
dynamic get deletedAt;
|
||||||
|
@override
|
||||||
|
dynamic get expiredAt;
|
||||||
|
@override
|
||||||
|
List<SnPollOption> get options;
|
||||||
|
@override
|
||||||
|
int get accountId;
|
||||||
|
@override
|
||||||
|
SnPollMetric get metric;
|
||||||
|
|
||||||
|
/// Create a copy of SnPoll
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
_$$SnPollImplCopyWith<_$SnPollImpl> get copyWith =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
}
|
||||||
|
|
||||||
|
SnPollMetric _$SnPollMetricFromJson(Map<String, dynamic> json) {
|
||||||
|
return _SnPollMetric.fromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$SnPollMetric {
|
||||||
|
int get totalAnswer => throw _privateConstructorUsedError;
|
||||||
|
Map<String, int> get byOptions => throw _privateConstructorUsedError;
|
||||||
|
Map<String, double> get byOptionsPercentage =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
|
||||||
|
/// Serializes this SnPollMetric to a JSON map.
|
||||||
|
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
|
/// Create a copy of SnPollMetric
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
$SnPollMetricCopyWith<SnPollMetric> get copyWith =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class $SnPollMetricCopyWith<$Res> {
|
||||||
|
factory $SnPollMetricCopyWith(
|
||||||
|
SnPollMetric value, $Res Function(SnPollMetric) then) =
|
||||||
|
_$SnPollMetricCopyWithImpl<$Res, SnPollMetric>;
|
||||||
|
@useResult
|
||||||
|
$Res call(
|
||||||
|
{int totalAnswer,
|
||||||
|
Map<String, int> byOptions,
|
||||||
|
Map<String, double> byOptionsPercentage});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class _$SnPollMetricCopyWithImpl<$Res, $Val extends SnPollMetric>
|
||||||
|
implements $SnPollMetricCopyWith<$Res> {
|
||||||
|
_$SnPollMetricCopyWithImpl(this._value, this._then);
|
||||||
|
|
||||||
|
// ignore: unused_field
|
||||||
|
final $Val _value;
|
||||||
|
// ignore: unused_field
|
||||||
|
final $Res Function($Val) _then;
|
||||||
|
|
||||||
|
/// Create a copy of SnPollMetric
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({
|
||||||
|
Object? totalAnswer = null,
|
||||||
|
Object? byOptions = null,
|
||||||
|
Object? byOptionsPercentage = null,
|
||||||
|
}) {
|
||||||
|
return _then(_value.copyWith(
|
||||||
|
totalAnswer: null == totalAnswer
|
||||||
|
? _value.totalAnswer
|
||||||
|
: totalAnswer // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,
|
||||||
|
byOptions: null == byOptions
|
||||||
|
? _value.byOptions
|
||||||
|
: byOptions // ignore: cast_nullable_to_non_nullable
|
||||||
|
as Map<String, int>,
|
||||||
|
byOptionsPercentage: null == byOptionsPercentage
|
||||||
|
? _value.byOptionsPercentage
|
||||||
|
: byOptionsPercentage // ignore: cast_nullable_to_non_nullable
|
||||||
|
as Map<String, double>,
|
||||||
|
) as $Val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class _$$SnPollMetricImplCopyWith<$Res>
|
||||||
|
implements $SnPollMetricCopyWith<$Res> {
|
||||||
|
factory _$$SnPollMetricImplCopyWith(
|
||||||
|
_$SnPollMetricImpl value, $Res Function(_$SnPollMetricImpl) then) =
|
||||||
|
__$$SnPollMetricImplCopyWithImpl<$Res>;
|
||||||
|
@override
|
||||||
|
@useResult
|
||||||
|
$Res call(
|
||||||
|
{int totalAnswer,
|
||||||
|
Map<String, int> byOptions,
|
||||||
|
Map<String, double> byOptionsPercentage});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class __$$SnPollMetricImplCopyWithImpl<$Res>
|
||||||
|
extends _$SnPollMetricCopyWithImpl<$Res, _$SnPollMetricImpl>
|
||||||
|
implements _$$SnPollMetricImplCopyWith<$Res> {
|
||||||
|
__$$SnPollMetricImplCopyWithImpl(
|
||||||
|
_$SnPollMetricImpl _value, $Res Function(_$SnPollMetricImpl) _then)
|
||||||
|
: super(_value, _then);
|
||||||
|
|
||||||
|
/// Create a copy of SnPollMetric
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({
|
||||||
|
Object? totalAnswer = null,
|
||||||
|
Object? byOptions = null,
|
||||||
|
Object? byOptionsPercentage = null,
|
||||||
|
}) {
|
||||||
|
return _then(_$SnPollMetricImpl(
|
||||||
|
totalAnswer: null == totalAnswer
|
||||||
|
? _value.totalAnswer
|
||||||
|
: totalAnswer // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,
|
||||||
|
byOptions: null == byOptions
|
||||||
|
? _value._byOptions
|
||||||
|
: byOptions // ignore: cast_nullable_to_non_nullable
|
||||||
|
as Map<String, int>,
|
||||||
|
byOptionsPercentage: null == byOptionsPercentage
|
||||||
|
? _value._byOptionsPercentage
|
||||||
|
: byOptionsPercentage // ignore: cast_nullable_to_non_nullable
|
||||||
|
as Map<String, double>,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
@JsonSerializable()
|
||||||
|
class _$SnPollMetricImpl implements _SnPollMetric {
|
||||||
|
const _$SnPollMetricImpl(
|
||||||
|
{required this.totalAnswer,
|
||||||
|
final Map<String, int> byOptions = const {},
|
||||||
|
final Map<String, double> byOptionsPercentage = const {}})
|
||||||
|
: _byOptions = byOptions,
|
||||||
|
_byOptionsPercentage = byOptionsPercentage;
|
||||||
|
|
||||||
|
factory _$SnPollMetricImpl.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$$SnPollMetricImplFromJson(json);
|
||||||
|
|
||||||
|
@override
|
||||||
|
final int totalAnswer;
|
||||||
|
final Map<String, int> _byOptions;
|
||||||
|
@override
|
||||||
|
@JsonKey()
|
||||||
|
Map<String, int> get byOptions {
|
||||||
|
if (_byOptions is EqualUnmodifiableMapView) return _byOptions;
|
||||||
|
// ignore: implicit_dynamic_type
|
||||||
|
return EqualUnmodifiableMapView(_byOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
final Map<String, double> _byOptionsPercentage;
|
||||||
|
@override
|
||||||
|
@JsonKey()
|
||||||
|
Map<String, double> get byOptionsPercentage {
|
||||||
|
if (_byOptionsPercentage is EqualUnmodifiableMapView)
|
||||||
|
return _byOptionsPercentage;
|
||||||
|
// ignore: implicit_dynamic_type
|
||||||
|
return EqualUnmodifiableMapView(_byOptionsPercentage);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'SnPollMetric(totalAnswer: $totalAnswer, byOptions: $byOptions, byOptionsPercentage: $byOptionsPercentage)';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) ||
|
||||||
|
(other.runtimeType == runtimeType &&
|
||||||
|
other is _$SnPollMetricImpl &&
|
||||||
|
(identical(other.totalAnswer, totalAnswer) ||
|
||||||
|
other.totalAnswer == totalAnswer) &&
|
||||||
|
const DeepCollectionEquality()
|
||||||
|
.equals(other._byOptions, _byOptions) &&
|
||||||
|
const DeepCollectionEquality()
|
||||||
|
.equals(other._byOptionsPercentage, _byOptionsPercentage));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(
|
||||||
|
runtimeType,
|
||||||
|
totalAnswer,
|
||||||
|
const DeepCollectionEquality().hash(_byOptions),
|
||||||
|
const DeepCollectionEquality().hash(_byOptionsPercentage));
|
||||||
|
|
||||||
|
/// Create a copy of SnPollMetric
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$$SnPollMetricImplCopyWith<_$SnPollMetricImpl> get copyWith =>
|
||||||
|
__$$SnPollMetricImplCopyWithImpl<_$SnPollMetricImpl>(this, _$identity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return _$$SnPollMetricImplToJson(
|
||||||
|
this,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class _SnPollMetric implements SnPollMetric {
|
||||||
|
const factory _SnPollMetric(
|
||||||
|
{required final int totalAnswer,
|
||||||
|
final Map<String, int> byOptions,
|
||||||
|
final Map<String, double> byOptionsPercentage}) = _$SnPollMetricImpl;
|
||||||
|
|
||||||
|
factory _SnPollMetric.fromJson(Map<String, dynamic> json) =
|
||||||
|
_$SnPollMetricImpl.fromJson;
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get totalAnswer;
|
||||||
|
@override
|
||||||
|
Map<String, int> get byOptions;
|
||||||
|
@override
|
||||||
|
Map<String, double> get byOptionsPercentage;
|
||||||
|
|
||||||
|
/// Create a copy of SnPollMetric
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
_$$SnPollMetricImplCopyWith<_$SnPollMetricImpl> get copyWith =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
}
|
||||||
|
|
||||||
|
SnPollOption _$SnPollOptionFromJson(Map<String, dynamic> json) {
|
||||||
|
return _SnPollOption.fromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$SnPollOption {
|
||||||
|
String get id => throw _privateConstructorUsedError;
|
||||||
|
String get icon => throw _privateConstructorUsedError;
|
||||||
|
String get name => throw _privateConstructorUsedError;
|
||||||
|
String get description => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
|
/// Serializes this SnPollOption to a JSON map.
|
||||||
|
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
|
/// Create a copy of SnPollOption
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
$SnPollOptionCopyWith<SnPollOption> get copyWith =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class $SnPollOptionCopyWith<$Res> {
|
||||||
|
factory $SnPollOptionCopyWith(
|
||||||
|
SnPollOption value, $Res Function(SnPollOption) then) =
|
||||||
|
_$SnPollOptionCopyWithImpl<$Res, SnPollOption>;
|
||||||
|
@useResult
|
||||||
|
$Res call({String id, String icon, String name, String description});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class _$SnPollOptionCopyWithImpl<$Res, $Val extends SnPollOption>
|
||||||
|
implements $SnPollOptionCopyWith<$Res> {
|
||||||
|
_$SnPollOptionCopyWithImpl(this._value, this._then);
|
||||||
|
|
||||||
|
// ignore: unused_field
|
||||||
|
final $Val _value;
|
||||||
|
// ignore: unused_field
|
||||||
|
final $Res Function($Val) _then;
|
||||||
|
|
||||||
|
/// Create a copy of SnPollOption
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({
|
||||||
|
Object? id = null,
|
||||||
|
Object? icon = null,
|
||||||
|
Object? name = null,
|
||||||
|
Object? description = null,
|
||||||
|
}) {
|
||||||
|
return _then(_value.copyWith(
|
||||||
|
id: null == id
|
||||||
|
? _value.id
|
||||||
|
: id // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
icon: null == icon
|
||||||
|
? _value.icon
|
||||||
|
: icon // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
name: null == name
|
||||||
|
? _value.name
|
||||||
|
: name // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
description: null == description
|
||||||
|
? _value.description
|
||||||
|
: description // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
) as $Val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class _$$SnPollOptionImplCopyWith<$Res>
|
||||||
|
implements $SnPollOptionCopyWith<$Res> {
|
||||||
|
factory _$$SnPollOptionImplCopyWith(
|
||||||
|
_$SnPollOptionImpl value, $Res Function(_$SnPollOptionImpl) then) =
|
||||||
|
__$$SnPollOptionImplCopyWithImpl<$Res>;
|
||||||
|
@override
|
||||||
|
@useResult
|
||||||
|
$Res call({String id, String icon, String name, String description});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class __$$SnPollOptionImplCopyWithImpl<$Res>
|
||||||
|
extends _$SnPollOptionCopyWithImpl<$Res, _$SnPollOptionImpl>
|
||||||
|
implements _$$SnPollOptionImplCopyWith<$Res> {
|
||||||
|
__$$SnPollOptionImplCopyWithImpl(
|
||||||
|
_$SnPollOptionImpl _value, $Res Function(_$SnPollOptionImpl) _then)
|
||||||
|
: super(_value, _then);
|
||||||
|
|
||||||
|
/// Create a copy of SnPollOption
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({
|
||||||
|
Object? id = null,
|
||||||
|
Object? icon = null,
|
||||||
|
Object? name = null,
|
||||||
|
Object? description = null,
|
||||||
|
}) {
|
||||||
|
return _then(_$SnPollOptionImpl(
|
||||||
|
id: null == id
|
||||||
|
? _value.id
|
||||||
|
: id // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
icon: null == icon
|
||||||
|
? _value.icon
|
||||||
|
: icon // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
name: null == name
|
||||||
|
? _value.name
|
||||||
|
: name // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
description: null == description
|
||||||
|
? _value.description
|
||||||
|
: description // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
@JsonSerializable()
|
||||||
|
class _$SnPollOptionImpl implements _SnPollOption {
|
||||||
|
const _$SnPollOptionImpl(
|
||||||
|
{required this.id,
|
||||||
|
required this.icon,
|
||||||
|
required this.name,
|
||||||
|
required this.description});
|
||||||
|
|
||||||
|
factory _$SnPollOptionImpl.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$$SnPollOptionImplFromJson(json);
|
||||||
|
|
||||||
|
@override
|
||||||
|
final String id;
|
||||||
|
@override
|
||||||
|
final String icon;
|
||||||
|
@override
|
||||||
|
final String name;
|
||||||
|
@override
|
||||||
|
final String description;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'SnPollOption(id: $id, icon: $icon, name: $name, description: $description)';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) ||
|
||||||
|
(other.runtimeType == runtimeType &&
|
||||||
|
other is _$SnPollOptionImpl &&
|
||||||
|
(identical(other.id, id) || other.id == id) &&
|
||||||
|
(identical(other.icon, icon) || other.icon == icon) &&
|
||||||
|
(identical(other.name, name) || other.name == name) &&
|
||||||
|
(identical(other.description, description) ||
|
||||||
|
other.description == description));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType, id, icon, name, description);
|
||||||
|
|
||||||
|
/// Create a copy of SnPollOption
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$$SnPollOptionImplCopyWith<_$SnPollOptionImpl> get copyWith =>
|
||||||
|
__$$SnPollOptionImplCopyWithImpl<_$SnPollOptionImpl>(this, _$identity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return _$$SnPollOptionImplToJson(
|
||||||
|
this,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class _SnPollOption implements SnPollOption {
|
||||||
|
const factory _SnPollOption(
|
||||||
|
{required final String id,
|
||||||
|
required final String icon,
|
||||||
|
required final String name,
|
||||||
|
required final String description}) = _$SnPollOptionImpl;
|
||||||
|
|
||||||
|
factory _SnPollOption.fromJson(Map<String, dynamic> json) =
|
||||||
|
_$SnPollOptionImpl.fromJson;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get id;
|
||||||
|
@override
|
||||||
|
String get icon;
|
||||||
|
@override
|
||||||
|
String get name;
|
||||||
|
@override
|
||||||
|
String get description;
|
||||||
|
|
||||||
|
/// Create a copy of SnPollOption
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
_$$SnPollOptionImplCopyWith<_$SnPollOptionImpl> get copyWith =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
}
|
69
lib/types/poll.g.dart
Normal file
69
lib/types/poll.g.dart
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'poll.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// JsonSerializableGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
_$SnPollImpl _$$SnPollImplFromJson(Map<String, dynamic> json) => _$SnPollImpl(
|
||||||
|
id: (json['id'] as num).toInt(),
|
||||||
|
createdAt: DateTime.parse(json['created_at'] as String),
|
||||||
|
updatedAt: DateTime.parse(json['updated_at'] as String),
|
||||||
|
deletedAt: json['deleted_at'],
|
||||||
|
expiredAt: json['expired_at'],
|
||||||
|
options: (json['options'] as List<dynamic>)
|
||||||
|
.map((e) => SnPollOption.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList(),
|
||||||
|
accountId: (json['account_id'] as num).toInt(),
|
||||||
|
metric: SnPollMetric.fromJson(json['metric'] as Map<String, dynamic>),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$$SnPollImplToJson(_$SnPollImpl instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'id': instance.id,
|
||||||
|
'created_at': instance.createdAt.toIso8601String(),
|
||||||
|
'updated_at': instance.updatedAt.toIso8601String(),
|
||||||
|
'deleted_at': instance.deletedAt,
|
||||||
|
'expired_at': instance.expiredAt,
|
||||||
|
'options': instance.options.map((e) => e.toJson()).toList(),
|
||||||
|
'account_id': instance.accountId,
|
||||||
|
'metric': instance.metric.toJson(),
|
||||||
|
};
|
||||||
|
|
||||||
|
_$SnPollMetricImpl _$$SnPollMetricImplFromJson(Map<String, dynamic> json) =>
|
||||||
|
_$SnPollMetricImpl(
|
||||||
|
totalAnswer: (json['total_answer'] as num).toInt(),
|
||||||
|
byOptions: (json['by_options'] as Map<String, dynamic>?)?.map(
|
||||||
|
(k, e) => MapEntry(k, (e as num).toInt()),
|
||||||
|
) ??
|
||||||
|
const {},
|
||||||
|
byOptionsPercentage:
|
||||||
|
(json['by_options_percentage'] as Map<String, dynamic>?)?.map(
|
||||||
|
(k, e) => MapEntry(k, (e as num).toDouble()),
|
||||||
|
) ??
|
||||||
|
const {},
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$$SnPollMetricImplToJson(_$SnPollMetricImpl instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'total_answer': instance.totalAnswer,
|
||||||
|
'by_options': instance.byOptions,
|
||||||
|
'by_options_percentage': instance.byOptionsPercentage,
|
||||||
|
};
|
||||||
|
|
||||||
|
_$SnPollOptionImpl _$$SnPollOptionImplFromJson(Map<String, dynamic> json) =>
|
||||||
|
_$SnPollOptionImpl(
|
||||||
|
id: json['id'] as String,
|
||||||
|
icon: json['icon'] as String,
|
||||||
|
name: json['name'] as String,
|
||||||
|
description: json['description'] as String,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$$SnPollOptionImplToJson(_$SnPollOptionImpl instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'id': instance.id,
|
||||||
|
'icon': instance.icon,
|
||||||
|
'name': instance.name,
|
||||||
|
'description': instance.description,
|
||||||
|
};
|
@ -1,5 +1,6 @@
|
|||||||
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';
|
||||||
|
|
||||||
part 'post.freezed.dart';
|
part 'post.freezed.dart';
|
||||||
part 'post.g.dart';
|
part 'post.g.dart';
|
||||||
@ -36,7 +37,10 @@ class SnPost with _$SnPost {
|
|||||||
required DateTime? publishedUntil,
|
required DateTime? publishedUntil,
|
||||||
required int totalUpvote,
|
required int totalUpvote,
|
||||||
required int totalDownvote,
|
required int totalDownvote,
|
||||||
|
@Default(0) int totalViews,
|
||||||
|
@Default(0) int totalAggregatedViews,
|
||||||
required int publisherId,
|
required int publisherId,
|
||||||
|
required int? pollId,
|
||||||
required SnPublisher publisher,
|
required SnPublisher publisher,
|
||||||
required SnMetric metric,
|
required SnMetric metric,
|
||||||
SnPostPreload? preload,
|
SnPostPreload? preload,
|
||||||
@ -89,6 +93,8 @@ class SnPostPreload with _$SnPostPreload {
|
|||||||
const factory SnPostPreload({
|
const factory SnPostPreload({
|
||||||
required SnAttachment? thumbnail,
|
required SnAttachment? thumbnail,
|
||||||
required List<SnAttachment?>? attachments,
|
required List<SnAttachment?>? attachments,
|
||||||
|
required SnAttachment? video,
|
||||||
|
required SnPoll? poll,
|
||||||
}) = _SnPostPreload;
|
}) = _SnPostPreload;
|
||||||
|
|
||||||
factory SnPostPreload.fromJson(Map<String, Object?> json) =>
|
factory SnPostPreload.fromJson(Map<String, Object?> json) =>
|
||||||
|
@ -47,7 +47,10 @@ mixin _$SnPost {
|
|||||||
DateTime? get publishedUntil => throw _privateConstructorUsedError;
|
DateTime? get publishedUntil => throw _privateConstructorUsedError;
|
||||||
int get totalUpvote => throw _privateConstructorUsedError;
|
int get totalUpvote => throw _privateConstructorUsedError;
|
||||||
int get totalDownvote => throw _privateConstructorUsedError;
|
int get totalDownvote => throw _privateConstructorUsedError;
|
||||||
|
int get totalViews => throw _privateConstructorUsedError;
|
||||||
|
int get totalAggregatedViews => throw _privateConstructorUsedError;
|
||||||
int get publisherId => throw _privateConstructorUsedError;
|
int get publisherId => throw _privateConstructorUsedError;
|
||||||
|
int? get pollId => throw _privateConstructorUsedError;
|
||||||
SnPublisher get publisher => throw _privateConstructorUsedError;
|
SnPublisher get publisher => throw _privateConstructorUsedError;
|
||||||
SnMetric get metric => throw _privateConstructorUsedError;
|
SnMetric get metric => throw _privateConstructorUsedError;
|
||||||
SnPostPreload? get preload => throw _privateConstructorUsedError;
|
SnPostPreload? get preload => throw _privateConstructorUsedError;
|
||||||
@ -94,7 +97,10 @@ abstract class $SnPostCopyWith<$Res> {
|
|||||||
DateTime? publishedUntil,
|
DateTime? publishedUntil,
|
||||||
int totalUpvote,
|
int totalUpvote,
|
||||||
int totalDownvote,
|
int totalDownvote,
|
||||||
|
int totalViews,
|
||||||
|
int totalAggregatedViews,
|
||||||
int publisherId,
|
int publisherId,
|
||||||
|
int? pollId,
|
||||||
SnPublisher publisher,
|
SnPublisher publisher,
|
||||||
SnMetric metric,
|
SnMetric metric,
|
||||||
SnPostPreload? preload});
|
SnPostPreload? preload});
|
||||||
@ -148,7 +154,10 @@ class _$SnPostCopyWithImpl<$Res, $Val extends SnPost>
|
|||||||
Object? publishedUntil = freezed,
|
Object? publishedUntil = freezed,
|
||||||
Object? totalUpvote = null,
|
Object? totalUpvote = null,
|
||||||
Object? totalDownvote = null,
|
Object? totalDownvote = null,
|
||||||
|
Object? totalViews = null,
|
||||||
|
Object? totalAggregatedViews = null,
|
||||||
Object? publisherId = null,
|
Object? publisherId = null,
|
||||||
|
Object? pollId = freezed,
|
||||||
Object? publisher = null,
|
Object? publisher = null,
|
||||||
Object? metric = null,
|
Object? metric = null,
|
||||||
Object? preload = freezed,
|
Object? preload = freezed,
|
||||||
@ -262,10 +271,22 @@ class _$SnPostCopyWithImpl<$Res, $Val extends SnPost>
|
|||||||
? _value.totalDownvote
|
? _value.totalDownvote
|
||||||
: totalDownvote // ignore: cast_nullable_to_non_nullable
|
: totalDownvote // ignore: cast_nullable_to_non_nullable
|
||||||
as int,
|
as int,
|
||||||
|
totalViews: null == totalViews
|
||||||
|
? _value.totalViews
|
||||||
|
: totalViews // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,
|
||||||
|
totalAggregatedViews: null == totalAggregatedViews
|
||||||
|
? _value.totalAggregatedViews
|
||||||
|
: totalAggregatedViews // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,
|
||||||
publisherId: null == publisherId
|
publisherId: null == publisherId
|
||||||
? _value.publisherId
|
? _value.publisherId
|
||||||
: publisherId // ignore: cast_nullable_to_non_nullable
|
: publisherId // ignore: cast_nullable_to_non_nullable
|
||||||
as int,
|
as int,
|
||||||
|
pollId: freezed == pollId
|
||||||
|
? _value.pollId
|
||||||
|
: pollId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int?,
|
||||||
publisher: null == publisher
|
publisher: null == publisher
|
||||||
? _value.publisher
|
? _value.publisher
|
||||||
: publisher // ignore: cast_nullable_to_non_nullable
|
: publisher // ignore: cast_nullable_to_non_nullable
|
||||||
@ -379,7 +400,10 @@ abstract class _$$SnPostImplCopyWith<$Res> implements $SnPostCopyWith<$Res> {
|
|||||||
DateTime? publishedUntil,
|
DateTime? publishedUntil,
|
||||||
int totalUpvote,
|
int totalUpvote,
|
||||||
int totalDownvote,
|
int totalDownvote,
|
||||||
|
int totalViews,
|
||||||
|
int totalAggregatedViews,
|
||||||
int publisherId,
|
int publisherId,
|
||||||
|
int? pollId,
|
||||||
SnPublisher publisher,
|
SnPublisher publisher,
|
||||||
SnMetric metric,
|
SnMetric metric,
|
||||||
SnPostPreload? preload});
|
SnPostPreload? preload});
|
||||||
@ -436,7 +460,10 @@ class __$$SnPostImplCopyWithImpl<$Res>
|
|||||||
Object? publishedUntil = freezed,
|
Object? publishedUntil = freezed,
|
||||||
Object? totalUpvote = null,
|
Object? totalUpvote = null,
|
||||||
Object? totalDownvote = null,
|
Object? totalDownvote = null,
|
||||||
|
Object? totalViews = null,
|
||||||
|
Object? totalAggregatedViews = null,
|
||||||
Object? publisherId = null,
|
Object? publisherId = null,
|
||||||
|
Object? pollId = freezed,
|
||||||
Object? publisher = null,
|
Object? publisher = null,
|
||||||
Object? metric = null,
|
Object? metric = null,
|
||||||
Object? preload = freezed,
|
Object? preload = freezed,
|
||||||
@ -550,10 +577,22 @@ class __$$SnPostImplCopyWithImpl<$Res>
|
|||||||
? _value.totalDownvote
|
? _value.totalDownvote
|
||||||
: totalDownvote // ignore: cast_nullable_to_non_nullable
|
: totalDownvote // ignore: cast_nullable_to_non_nullable
|
||||||
as int,
|
as int,
|
||||||
|
totalViews: null == totalViews
|
||||||
|
? _value.totalViews
|
||||||
|
: totalViews // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,
|
||||||
|
totalAggregatedViews: null == totalAggregatedViews
|
||||||
|
? _value.totalAggregatedViews
|
||||||
|
: totalAggregatedViews // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,
|
||||||
publisherId: null == publisherId
|
publisherId: null == publisherId
|
||||||
? _value.publisherId
|
? _value.publisherId
|
||||||
: publisherId // ignore: cast_nullable_to_non_nullable
|
: publisherId // ignore: cast_nullable_to_non_nullable
|
||||||
as int,
|
as int,
|
||||||
|
pollId: freezed == pollId
|
||||||
|
? _value.pollId
|
||||||
|
: pollId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int?,
|
||||||
publisher: null == publisher
|
publisher: null == publisher
|
||||||
? _value.publisher
|
? _value.publisher
|
||||||
: publisher // ignore: cast_nullable_to_non_nullable
|
: publisher // ignore: cast_nullable_to_non_nullable
|
||||||
@ -601,7 +640,10 @@ class _$SnPostImpl extends _SnPost {
|
|||||||
required this.publishedUntil,
|
required this.publishedUntil,
|
||||||
required this.totalUpvote,
|
required this.totalUpvote,
|
||||||
required this.totalDownvote,
|
required this.totalDownvote,
|
||||||
|
this.totalViews = 0,
|
||||||
|
this.totalAggregatedViews = 0,
|
||||||
required this.publisherId,
|
required this.publisherId,
|
||||||
|
required this.pollId,
|
||||||
required this.publisher,
|
required this.publisher,
|
||||||
required this.metric,
|
required this.metric,
|
||||||
this.preload})
|
this.preload})
|
||||||
@ -717,8 +759,16 @@ class _$SnPostImpl extends _SnPost {
|
|||||||
@override
|
@override
|
||||||
final int totalDownvote;
|
final int totalDownvote;
|
||||||
@override
|
@override
|
||||||
|
@JsonKey()
|
||||||
|
final int totalViews;
|
||||||
|
@override
|
||||||
|
@JsonKey()
|
||||||
|
final int totalAggregatedViews;
|
||||||
|
@override
|
||||||
final int publisherId;
|
final int publisherId;
|
||||||
@override
|
@override
|
||||||
|
final int? pollId;
|
||||||
|
@override
|
||||||
final SnPublisher publisher;
|
final SnPublisher publisher;
|
||||||
@override
|
@override
|
||||||
final SnMetric metric;
|
final SnMetric metric;
|
||||||
@ -727,7 +777,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, publisherId: $publisherId, 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, 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
|
||||||
@ -780,8 +830,13 @@ class _$SnPostImpl extends _SnPost {
|
|||||||
other.totalUpvote == totalUpvote) &&
|
other.totalUpvote == totalUpvote) &&
|
||||||
(identical(other.totalDownvote, totalDownvote) ||
|
(identical(other.totalDownvote, totalDownvote) ||
|
||||||
other.totalDownvote == totalDownvote) &&
|
other.totalDownvote == totalDownvote) &&
|
||||||
|
(identical(other.totalViews, totalViews) ||
|
||||||
|
other.totalViews == totalViews) &&
|
||||||
|
(identical(other.totalAggregatedViews, totalAggregatedViews) ||
|
||||||
|
other.totalAggregatedViews == totalAggregatedViews) &&
|
||||||
(identical(other.publisherId, publisherId) ||
|
(identical(other.publisherId, publisherId) ||
|
||||||
other.publisherId == publisherId) &&
|
other.publisherId == publisherId) &&
|
||||||
|
(identical(other.pollId, pollId) || other.pollId == pollId) &&
|
||||||
(identical(other.publisher, publisher) ||
|
(identical(other.publisher, publisher) ||
|
||||||
other.publisher == publisher) &&
|
other.publisher == publisher) &&
|
||||||
(identical(other.metric, metric) || other.metric == metric) &&
|
(identical(other.metric, metric) || other.metric == metric) &&
|
||||||
@ -819,7 +874,10 @@ class _$SnPostImpl extends _SnPost {
|
|||||||
publishedUntil,
|
publishedUntil,
|
||||||
totalUpvote,
|
totalUpvote,
|
||||||
totalDownvote,
|
totalDownvote,
|
||||||
|
totalViews,
|
||||||
|
totalAggregatedViews,
|
||||||
publisherId,
|
publisherId,
|
||||||
|
pollId,
|
||||||
publisher,
|
publisher,
|
||||||
metric,
|
metric,
|
||||||
preload
|
preload
|
||||||
@ -870,7 +928,10 @@ abstract class _SnPost extends SnPost {
|
|||||||
required final DateTime? publishedUntil,
|
required final DateTime? publishedUntil,
|
||||||
required final int totalUpvote,
|
required final int totalUpvote,
|
||||||
required final int totalDownvote,
|
required final int totalDownvote,
|
||||||
|
final int totalViews,
|
||||||
|
final int totalAggregatedViews,
|
||||||
required final int publisherId,
|
required final int publisherId,
|
||||||
|
required final int? pollId,
|
||||||
required final SnPublisher publisher,
|
required final SnPublisher publisher,
|
||||||
required final SnMetric metric,
|
required final SnMetric metric,
|
||||||
final SnPostPreload? preload}) = _$SnPostImpl;
|
final SnPostPreload? preload}) = _$SnPostImpl;
|
||||||
@ -933,8 +994,14 @@ abstract class _SnPost extends SnPost {
|
|||||||
@override
|
@override
|
||||||
int get totalDownvote;
|
int get totalDownvote;
|
||||||
@override
|
@override
|
||||||
|
int get totalViews;
|
||||||
|
@override
|
||||||
|
int get totalAggregatedViews;
|
||||||
|
@override
|
||||||
int get publisherId;
|
int get publisherId;
|
||||||
@override
|
@override
|
||||||
|
int? get pollId;
|
||||||
|
@override
|
||||||
SnPublisher get publisher;
|
SnPublisher get publisher;
|
||||||
@override
|
@override
|
||||||
SnMetric get metric;
|
SnMetric get metric;
|
||||||
@ -1567,6 +1634,8 @@ SnPostPreload _$SnPostPreloadFromJson(Map<String, dynamic> json) {
|
|||||||
mixin _$SnPostPreload {
|
mixin _$SnPostPreload {
|
||||||
SnAttachment? get thumbnail => throw _privateConstructorUsedError;
|
SnAttachment? get thumbnail => throw _privateConstructorUsedError;
|
||||||
List<SnAttachment?>? get attachments => throw _privateConstructorUsedError;
|
List<SnAttachment?>? get attachments => throw _privateConstructorUsedError;
|
||||||
|
SnAttachment? get video => throw _privateConstructorUsedError;
|
||||||
|
SnPoll? get poll => 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;
|
||||||
@ -1584,9 +1653,15 @@ abstract class $SnPostPreloadCopyWith<$Res> {
|
|||||||
SnPostPreload value, $Res Function(SnPostPreload) then) =
|
SnPostPreload value, $Res Function(SnPostPreload) then) =
|
||||||
_$SnPostPreloadCopyWithImpl<$Res, SnPostPreload>;
|
_$SnPostPreloadCopyWithImpl<$Res, SnPostPreload>;
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({SnAttachment? thumbnail, List<SnAttachment?>? attachments});
|
$Res call(
|
||||||
|
{SnAttachment? thumbnail,
|
||||||
|
List<SnAttachment?>? attachments,
|
||||||
|
SnAttachment? video,
|
||||||
|
SnPoll? poll});
|
||||||
|
|
||||||
$SnAttachmentCopyWith<$Res>? get thumbnail;
|
$SnAttachmentCopyWith<$Res>? get thumbnail;
|
||||||
|
$SnAttachmentCopyWith<$Res>? get video;
|
||||||
|
$SnPollCopyWith<$Res>? get poll;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
@ -1606,6 +1681,8 @@ class _$SnPostPreloadCopyWithImpl<$Res, $Val extends SnPostPreload>
|
|||||||
$Res call({
|
$Res call({
|
||||||
Object? thumbnail = freezed,
|
Object? thumbnail = freezed,
|
||||||
Object? attachments = freezed,
|
Object? attachments = freezed,
|
||||||
|
Object? video = freezed,
|
||||||
|
Object? poll = freezed,
|
||||||
}) {
|
}) {
|
||||||
return _then(_value.copyWith(
|
return _then(_value.copyWith(
|
||||||
thumbnail: freezed == thumbnail
|
thumbnail: freezed == thumbnail
|
||||||
@ -1616,6 +1693,14 @@ class _$SnPostPreloadCopyWithImpl<$Res, $Val extends SnPostPreload>
|
|||||||
? _value.attachments
|
? _value.attachments
|
||||||
: attachments // ignore: cast_nullable_to_non_nullable
|
: attachments // ignore: cast_nullable_to_non_nullable
|
||||||
as List<SnAttachment?>?,
|
as List<SnAttachment?>?,
|
||||||
|
video: freezed == video
|
||||||
|
? _value.video
|
||||||
|
: video // ignore: cast_nullable_to_non_nullable
|
||||||
|
as SnAttachment?,
|
||||||
|
poll: freezed == poll
|
||||||
|
? _value.poll
|
||||||
|
: poll // ignore: cast_nullable_to_non_nullable
|
||||||
|
as SnPoll?,
|
||||||
) as $Val);
|
) as $Val);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1632,6 +1717,34 @@ class _$SnPostPreloadCopyWithImpl<$Res, $Val extends SnPostPreload>
|
|||||||
return _then(_value.copyWith(thumbnail: value) as $Val);
|
return _then(_value.copyWith(thumbnail: value) as $Val);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a copy of SnPostPreload
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SnAttachmentCopyWith<$Res>? get video {
|
||||||
|
if (_value.video == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $SnAttachmentCopyWith<$Res>(_value.video!, (value) {
|
||||||
|
return _then(_value.copyWith(video: value) as $Val);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a copy of SnPostPreload
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SnPollCopyWith<$Res>? get poll {
|
||||||
|
if (_value.poll == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $SnPollCopyWith<$Res>(_value.poll!, (value) {
|
||||||
|
return _then(_value.copyWith(poll: value) as $Val);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
@ -1642,10 +1755,18 @@ abstract class _$$SnPostPreloadImplCopyWith<$Res>
|
|||||||
__$$SnPostPreloadImplCopyWithImpl<$Res>;
|
__$$SnPostPreloadImplCopyWithImpl<$Res>;
|
||||||
@override
|
@override
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({SnAttachment? thumbnail, List<SnAttachment?>? attachments});
|
$Res call(
|
||||||
|
{SnAttachment? thumbnail,
|
||||||
|
List<SnAttachment?>? attachments,
|
||||||
|
SnAttachment? video,
|
||||||
|
SnPoll? poll});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
$SnAttachmentCopyWith<$Res>? get thumbnail;
|
$SnAttachmentCopyWith<$Res>? get thumbnail;
|
||||||
|
@override
|
||||||
|
$SnAttachmentCopyWith<$Res>? get video;
|
||||||
|
@override
|
||||||
|
$SnPollCopyWith<$Res>? get poll;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
@ -1663,6 +1784,8 @@ class __$$SnPostPreloadImplCopyWithImpl<$Res>
|
|||||||
$Res call({
|
$Res call({
|
||||||
Object? thumbnail = freezed,
|
Object? thumbnail = freezed,
|
||||||
Object? attachments = freezed,
|
Object? attachments = freezed,
|
||||||
|
Object? video = freezed,
|
||||||
|
Object? poll = freezed,
|
||||||
}) {
|
}) {
|
||||||
return _then(_$SnPostPreloadImpl(
|
return _then(_$SnPostPreloadImpl(
|
||||||
thumbnail: freezed == thumbnail
|
thumbnail: freezed == thumbnail
|
||||||
@ -1673,6 +1796,14 @@ class __$$SnPostPreloadImplCopyWithImpl<$Res>
|
|||||||
? _value._attachments
|
? _value._attachments
|
||||||
: attachments // ignore: cast_nullable_to_non_nullable
|
: attachments // ignore: cast_nullable_to_non_nullable
|
||||||
as List<SnAttachment?>?,
|
as List<SnAttachment?>?,
|
||||||
|
video: freezed == video
|
||||||
|
? _value.video
|
||||||
|
: video // ignore: cast_nullable_to_non_nullable
|
||||||
|
as SnAttachment?,
|
||||||
|
poll: freezed == poll
|
||||||
|
? _value.poll
|
||||||
|
: poll // ignore: cast_nullable_to_non_nullable
|
||||||
|
as SnPoll?,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1682,7 +1813,9 @@ class __$$SnPostPreloadImplCopyWithImpl<$Res>
|
|||||||
class _$SnPostPreloadImpl implements _SnPostPreload {
|
class _$SnPostPreloadImpl implements _SnPostPreload {
|
||||||
const _$SnPostPreloadImpl(
|
const _$SnPostPreloadImpl(
|
||||||
{required this.thumbnail,
|
{required this.thumbnail,
|
||||||
required final List<SnAttachment?>? attachments})
|
required final List<SnAttachment?>? attachments,
|
||||||
|
required this.video,
|
||||||
|
required this.poll})
|
||||||
: _attachments = attachments;
|
: _attachments = attachments;
|
||||||
|
|
||||||
factory _$SnPostPreloadImpl.fromJson(Map<String, dynamic> json) =>
|
factory _$SnPostPreloadImpl.fromJson(Map<String, dynamic> json) =>
|
||||||
@ -1700,9 +1833,14 @@ class _$SnPostPreloadImpl implements _SnPostPreload {
|
|||||||
return EqualUnmodifiableListView(value);
|
return EqualUnmodifiableListView(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
final SnAttachment? video;
|
||||||
|
@override
|
||||||
|
final SnPoll? poll;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'SnPostPreload(thumbnail: $thumbnail, attachments: $attachments)';
|
return 'SnPostPreload(thumbnail: $thumbnail, attachments: $attachments, video: $video, poll: $poll)';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -1713,13 +1851,15 @@ class _$SnPostPreloadImpl implements _SnPostPreload {
|
|||||||
(identical(other.thumbnail, thumbnail) ||
|
(identical(other.thumbnail, thumbnail) ||
|
||||||
other.thumbnail == thumbnail) &&
|
other.thumbnail == thumbnail) &&
|
||||||
const DeepCollectionEquality()
|
const DeepCollectionEquality()
|
||||||
.equals(other._attachments, _attachments));
|
.equals(other._attachments, _attachments) &&
|
||||||
|
(identical(other.video, video) || other.video == video) &&
|
||||||
|
(identical(other.poll, poll) || other.poll == poll));
|
||||||
}
|
}
|
||||||
|
|
||||||
@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));
|
const DeepCollectionEquality().hash(_attachments), video, poll);
|
||||||
|
|
||||||
/// 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.
|
||||||
@ -1740,7 +1880,9 @@ class _$SnPostPreloadImpl implements _SnPostPreload {
|
|||||||
abstract class _SnPostPreload implements SnPostPreload {
|
abstract class _SnPostPreload implements SnPostPreload {
|
||||||
const factory _SnPostPreload(
|
const factory _SnPostPreload(
|
||||||
{required final SnAttachment? thumbnail,
|
{required final SnAttachment? thumbnail,
|
||||||
required final List<SnAttachment?>? attachments}) = _$SnPostPreloadImpl;
|
required final List<SnAttachment?>? attachments,
|
||||||
|
required final SnAttachment? video,
|
||||||
|
required final SnPoll? poll}) = _$SnPostPreloadImpl;
|
||||||
|
|
||||||
factory _SnPostPreload.fromJson(Map<String, dynamic> json) =
|
factory _SnPostPreload.fromJson(Map<String, dynamic> json) =
|
||||||
_$SnPostPreloadImpl.fromJson;
|
_$SnPostPreloadImpl.fromJson;
|
||||||
@ -1749,6 +1891,10 @@ abstract class _SnPostPreload implements SnPostPreload {
|
|||||||
SnAttachment? get thumbnail;
|
SnAttachment? get thumbnail;
|
||||||
@override
|
@override
|
||||||
List<SnAttachment?>? get attachments;
|
List<SnAttachment?>? get attachments;
|
||||||
|
@override
|
||||||
|
SnAttachment? get video;
|
||||||
|
@override
|
||||||
|
SnPoll? get poll;
|
||||||
|
|
||||||
/// 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.
|
||||||
|
@ -62,7 +62,11 @@ _$SnPostImpl _$$SnPostImplFromJson(Map<String, dynamic> json) => _$SnPostImpl(
|
|||||||
: DateTime.parse(json['published_until'] as String),
|
: DateTime.parse(json['published_until'] as String),
|
||||||
totalUpvote: (json['total_upvote'] as num).toInt(),
|
totalUpvote: (json['total_upvote'] as num).toInt(),
|
||||||
totalDownvote: (json['total_downvote'] as num).toInt(),
|
totalDownvote: (json['total_downvote'] as num).toInt(),
|
||||||
|
totalViews: (json['total_views'] as num?)?.toInt() ?? 0,
|
||||||
|
totalAggregatedViews:
|
||||||
|
(json['total_aggregated_views'] as num?)?.toInt() ?? 0,
|
||||||
publisherId: (json['publisher_id'] as num).toInt(),
|
publisherId: (json['publisher_id'] as num).toInt(),
|
||||||
|
pollId: (json['poll_id'] as num?)?.toInt(),
|
||||||
publisher:
|
publisher:
|
||||||
SnPublisher.fromJson(json['publisher'] as Map<String, dynamic>),
|
SnPublisher.fromJson(json['publisher'] as Map<String, dynamic>),
|
||||||
metric: SnMetric.fromJson(json['metric'] as Map<String, dynamic>),
|
metric: SnMetric.fromJson(json['metric'] as Map<String, dynamic>),
|
||||||
@ -100,7 +104,10 @@ Map<String, dynamic> _$$SnPostImplToJson(_$SnPostImpl instance) =>
|
|||||||
'published_until': instance.publishedUntil?.toIso8601String(),
|
'published_until': instance.publishedUntil?.toIso8601String(),
|
||||||
'total_upvote': instance.totalUpvote,
|
'total_upvote': instance.totalUpvote,
|
||||||
'total_downvote': instance.totalDownvote,
|
'total_downvote': instance.totalDownvote,
|
||||||
|
'total_views': instance.totalViews,
|
||||||
|
'total_aggregated_views': instance.totalAggregatedViews,
|
||||||
'publisher_id': instance.publisherId,
|
'publisher_id': instance.publisherId,
|
||||||
|
'poll_id': instance.pollId,
|
||||||
'publisher': instance.publisher.toJson(),
|
'publisher': instance.publisher.toJson(),
|
||||||
'metric': instance.metric.toJson(),
|
'metric': instance.metric.toJson(),
|
||||||
'preload': instance.preload?.toJson(),
|
'preload': instance.preload?.toJson(),
|
||||||
@ -165,12 +172,20 @@ _$SnPostPreloadImpl _$$SnPostPreloadImplFromJson(Map<String, dynamic> json) =>
|
|||||||
? null
|
? null
|
||||||
: SnAttachment.fromJson(e as Map<String, dynamic>))
|
: SnAttachment.fromJson(e as Map<String, dynamic>))
|
||||||
.toList(),
|
.toList(),
|
||||||
|
video: json['video'] == null
|
||||||
|
? null
|
||||||
|
: SnAttachment.fromJson(json['video'] as Map<String, dynamic>),
|
||||||
|
poll: json['poll'] == null
|
||||||
|
? null
|
||||||
|
: SnPoll.fromJson(json['poll'] as Map<String, dynamic>),
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$$SnPostPreloadImplToJson(_$SnPostPreloadImpl instance) =>
|
Map<String, dynamic> _$$SnPostPreloadImplToJson(_$SnPostPreloadImpl instance) =>
|
||||||
<String, dynamic>{
|
<String, dynamic>{
|
||||||
'thumbnail': instance.thumbnail?.toJson(),
|
'thumbnail': instance.thumbnail?.toJson(),
|
||||||
'attachments': instance.attachments?.map((e) => e?.toJson()).toList(),
|
'attachments': instance.attachments?.map((e) => e?.toJson()).toList(),
|
||||||
|
'video': instance.video?.toJson(),
|
||||||
|
'poll': instance.poll?.toJson(),
|
||||||
};
|
};
|
||||||
|
|
||||||
_$SnBodyImpl _$$SnBodyImplFromJson(Map<String, dynamic> json) => _$SnBodyImpl(
|
_$SnBodyImpl _$$SnBodyImplFromJson(Map<String, dynamic> json) => _$SnBodyImpl(
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
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:gap/gap.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';
|
||||||
import 'package:surface/providers/user_directory.dart';
|
import 'package:surface/providers/user_directory.dart';
|
||||||
|
import 'package:surface/providers/userinfo.dart';
|
||||||
import 'package:surface/types/account.dart';
|
import 'package:surface/types/account.dart';
|
||||||
import 'package:surface/widgets/account/account_image.dart';
|
import 'package:surface/widgets/account/account_image.dart';
|
||||||
|
|
||||||
@ -47,10 +50,19 @@ class _AccountSelectState extends State<AccountSelect> {
|
|||||||
Future<void> _getFriends() async {
|
Future<void> _getFriends() async {
|
||||||
final sn = context.read<SnNetworkProvider>();
|
final sn = context.read<SnNetworkProvider>();
|
||||||
final resp = await sn.client.get('/cgi/id/users/me/relations?status=1');
|
final resp = await sn.client.get('/cgi/id/users/me/relations?status=1');
|
||||||
|
if (!mounted) return;
|
||||||
|
final ua = context.read<UserProvider>();
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_relativeUsers.addAll(
|
_relativeUsers.addAll(
|
||||||
resp.data?.map((e) => SnRelationship.fromJson(e)) ?? [],
|
resp.data?.map((e) {
|
||||||
|
final rel = SnRelationship.fromJson(e);
|
||||||
|
if (rel.relatedId == ua.user?.id) {
|
||||||
|
return rel.account!;
|
||||||
|
} else {
|
||||||
|
return rel.related!;
|
||||||
|
}
|
||||||
|
}).cast<SnAccount>(),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -96,10 +108,14 @@ class _AccountSelectState extends State<AccountSelect> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Row(
|
||||||
widget.title,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
style: Theme.of(context).textTheme.headlineSmall,
|
children: [
|
||||||
).padding(left: 24, right: 24, top: 16, bottom: 16),
|
const Icon(Symbols.group, size: 24),
|
||||||
|
const Gap(16),
|
||||||
|
Text(widget.title, style: Theme.of(context).textTheme.titleLarge),
|
||||||
|
],
|
||||||
|
).padding(horizontal: 20, top: 16, bottom: 12),
|
||||||
Container(
|
Container(
|
||||||
color: Theme.of(context).colorScheme.secondaryContainer,
|
color: Theme.of(context).colorScheme.secondaryContainer,
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
|
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
|
||||||
@ -117,13 +133,9 @@ class _AccountSelectState extends State<AccountSelect> {
|
|||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: ListView.builder(
|
child: ListView.builder(
|
||||||
itemCount: _pendingUsers.isEmpty
|
itemCount: _pendingUsers.isEmpty ? _relativeUsers.length : _pendingUsers.length,
|
||||||
? _relativeUsers.length
|
|
||||||
: _pendingUsers.length,
|
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
var user = _pendingUsers.isEmpty
|
var user = _pendingUsers.isEmpty ? _relativeUsers[index] : _pendingUsers[index];
|
||||||
? _relativeUsers[index]
|
|
||||||
: _pendingUsers[index];
|
|
||||||
return ListTile(
|
return ListTile(
|
||||||
title: Text(user.nick),
|
title: Text(user.nick),
|
||||||
subtitle: Text(user.name),
|
subtitle: Text(user.name),
|
||||||
@ -142,8 +154,7 @@ class _AccountSelectState extends State<AccountSelect> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
final idx = _selectedUsers
|
final idx = _selectedUsers.indexWhere((x) => x.id == user.id);
|
||||||
.indexWhere((x) => x.id == user.id);
|
|
||||||
if (idx != -1) {
|
if (idx != -1) {
|
||||||
_selectedUsers.removeAt(idx);
|
_selectedUsers.removeAt(idx);
|
||||||
} else {
|
} else {
|
||||||
|
@ -6,12 +6,22 @@ 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_attachment.dart';
|
import 'package:surface/providers/sn_attachment.dart';
|
||||||
|
import 'package:surface/types/attachment.dart';
|
||||||
import 'package:surface/widgets/dialog.dart';
|
import 'package:surface/widgets/dialog.dart';
|
||||||
|
|
||||||
class AttachmentInputDialog extends StatefulWidget {
|
class AttachmentInputDialog extends StatefulWidget {
|
||||||
final String? title;
|
final String? title;
|
||||||
final bool? analyzeNow;
|
final bool? analyzeNow;
|
||||||
const AttachmentInputDialog({super.key, required this.title, this.analyzeNow = false});
|
final SnMediaType? mediaType;
|
||||||
|
final String pool;
|
||||||
|
|
||||||
|
const AttachmentInputDialog({
|
||||||
|
super.key,
|
||||||
|
required this.title,
|
||||||
|
required this.pool,
|
||||||
|
this.analyzeNow = false,
|
||||||
|
this.mediaType = SnMediaType.image,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<AttachmentInputDialog> createState() => _AttachmentInputDialogState();
|
State<AttachmentInputDialog> createState() => _AttachmentInputDialogState();
|
||||||
@ -20,13 +30,18 @@ final bool? analyzeNow;
|
|||||||
class _AttachmentInputDialogState extends State<AttachmentInputDialog> {
|
class _AttachmentInputDialogState extends State<AttachmentInputDialog> {
|
||||||
final _randomIdController = TextEditingController();
|
final _randomIdController = TextEditingController();
|
||||||
|
|
||||||
XFile? _thumbnailFile;
|
XFile? _file;
|
||||||
|
double? _progress;
|
||||||
|
|
||||||
void _pickImage() async {
|
void _pickMedia() async {
|
||||||
final picker = ImagePicker();
|
final picker = ImagePicker();
|
||||||
final result = await picker.pickImage(source: ImageSource.gallery);
|
final result = switch (widget.mediaType) {
|
||||||
|
SnMediaType.image => await picker.pickImage(source: ImageSource.gallery),
|
||||||
|
SnMediaType.video => await picker.pickVideo(source: ImageSource.gallery),
|
||||||
|
_ => await picker.pickMedia(),
|
||||||
|
};
|
||||||
if (result == null) return;
|
if (result == null) return;
|
||||||
setState(() => _thumbnailFile = result);
|
setState(() => _file = result);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _isBusy = false;
|
bool _isBusy = false;
|
||||||
@ -46,15 +61,20 @@ class _AttachmentInputDialogState extends State<AttachmentInputDialog> {
|
|||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
context.showErrorDialog(err);
|
context.showErrorDialog(err);
|
||||||
}
|
}
|
||||||
} else if (_thumbnailFile != null) {
|
} else if (_file != null) {
|
||||||
try {
|
try {
|
||||||
final attachment = await attach.directUploadOne(
|
final place = await attach.chunkedUploadInitialize(await _file!.length(), _file!.name, widget.pool, null);
|
||||||
(await _thumbnailFile!.readAsBytes()).buffer.asUint8List(),
|
|
||||||
_thumbnailFile!.path,
|
final attachment = await attach.chunkedUploadParts(
|
||||||
'interactive',
|
_file!,
|
||||||
null,
|
place.$1,
|
||||||
|
place.$2,
|
||||||
analyzeNow: widget.analyzeNow ?? false,
|
analyzeNow: widget.analyzeNow ?? false,
|
||||||
|
onProgress: (value) {
|
||||||
|
setState(() => _progress = value);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
Navigator.pop(context, attachment);
|
Navigator.pop(context, attachment);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -67,7 +87,7 @@ class _AttachmentInputDialogState extends State<AttachmentInputDialog> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
title: Text(widget.title ?? 'attachmentInputDialog').tr(),
|
title: Text(widget.title ?? 'attachmentInputDialog'.tr()),
|
||||||
content: Column(
|
content: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
@ -86,24 +106,35 @@ class _AttachmentInputDialogState extends State<AttachmentInputDialog> {
|
|||||||
const Gap(24),
|
const Gap(24),
|
||||||
Text('attachmentInputNew').tr().fontSize(14),
|
Text('attachmentInputNew').tr().fontSize(14),
|
||||||
Card(
|
Card(
|
||||||
child: ListTile(
|
child: Column(
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
children: [
|
||||||
leading: const Icon(Symbols.add_photo_alternate),
|
ListTile(
|
||||||
trailing: const Icon(Symbols.chevron_right),
|
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||||
title: Text('addAttachmentFromAlbum').tr(),
|
leading: const Icon(Symbols.add_photo_alternate),
|
||||||
subtitle: _thumbnailFile == null ? Text('unset').tr() : Text('waitingForUpload').tr(),
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
onTap: () {
|
title: Text('addAttachmentFromAlbum').tr(),
|
||||||
_pickImage();
|
subtitle: _file == null ? Text('unset').tr() : Text('waitingForUpload').tr(),
|
||||||
},
|
onTap: () {
|
||||||
|
_pickMedia();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
if (_isBusy)
|
||||||
|
LinearProgressIndicator(
|
||||||
|
value: _progress,
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(8)),
|
||||||
|
).padding(top: 16),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: _isBusy ? null : () {
|
onPressed: _isBusy
|
||||||
Navigator.pop(context);
|
? null
|
||||||
},
|
: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
child: Text('dialogDismiss').tr(),
|
child: Text('dialogDismiss').tr(),
|
||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
|
@ -1,17 +1,28 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
import 'dart:math' show min;
|
||||||
|
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
|
import 'package:hotkey_manager/hotkey_manager.dart';
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
|
import 'package:pasteboard/pasteboard.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/controllers/chat_message_controller.dart';
|
import 'package:surface/controllers/chat_message_controller.dart';
|
||||||
import 'package:surface/controllers/post_write_controller.dart';
|
import 'package:surface/controllers/post_write_controller.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_sticker.dart';
|
||||||
import 'package:surface/providers/user_directory.dart';
|
import 'package:surface/providers/user_directory.dart';
|
||||||
import 'package:surface/types/attachment.dart';
|
import 'package:surface/types/attachment.dart';
|
||||||
import 'package:surface/types/chat.dart';
|
import 'package:surface/types/chat.dart';
|
||||||
import 'package:surface/widgets/dialog.dart';
|
import 'package:surface/widgets/dialog.dart';
|
||||||
import 'package:surface/widgets/post/post_media_pending_list.dart';
|
import 'package:surface/widgets/post/post_media_pending_list.dart';
|
||||||
|
import 'package:surface/widgets/universal_image.dart';
|
||||||
|
|
||||||
class ChatMessageInput extends StatefulWidget {
|
class ChatMessageInput extends StatefulWidget {
|
||||||
final ChatMessageController controller;
|
final ChatMessageController controller;
|
||||||
@ -32,9 +43,39 @@ class ChatMessageInputState extends State<ChatMessageInput> {
|
|||||||
final TextEditingController _contentController = TextEditingController();
|
final TextEditingController _contentController = TextEditingController();
|
||||||
final FocusNode _focusNode = FocusNode();
|
final FocusNode _focusNode = FocusNode();
|
||||||
|
|
||||||
|
final HotKey _pasteHotKey = HotKey(
|
||||||
|
key: PhysicalKeyboardKey.keyV,
|
||||||
|
modifiers: [(!kIsWeb && Platform.isMacOS) ? HotKeyModifier.meta : HotKeyModifier.control],
|
||||||
|
scope: HotKeyScope.inapp,
|
||||||
|
);
|
||||||
|
final HotKey _newLineHotKey = HotKey(
|
||||||
|
key: PhysicalKeyboardKey.enter,
|
||||||
|
modifiers: [(!kIsWeb && Platform.isMacOS) ? HotKeyModifier.meta : HotKeyModifier.control],
|
||||||
|
scope: HotKeyScope.inapp,
|
||||||
|
);
|
||||||
|
|
||||||
|
void _registerHotKey() {
|
||||||
|
if (kIsWeb || Platform.isAndroid || Platform.isIOS) return;
|
||||||
|
hotKeyManager.register(_pasteHotKey, keyDownHandler: (_) async {
|
||||||
|
final imageBytes = await Pasteboard.image;
|
||||||
|
if (imageBytes == null) return;
|
||||||
|
_attachments.add(PostWriteMedia.fromBytes(
|
||||||
|
imageBytes,
|
||||||
|
'attachmentPastedImage'.tr(),
|
||||||
|
SnMediaType.image,
|
||||||
|
));
|
||||||
|
setState(() {});
|
||||||
|
});
|
||||||
|
hotKeyManager.register(_newLineHotKey, keyDownHandler: (_) async {
|
||||||
|
if (_contentController.text.isEmpty) return;
|
||||||
|
_contentController.text += '\n';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
_registerHotKey();
|
||||||
_contentController.addListener(() {
|
_contentController.addListener(() {
|
||||||
if (_contentController.text.isNotEmpty) {
|
if (_contentController.text.isNotEmpty) {
|
||||||
widget.controller.pingTypingStatus();
|
widget.controller.pingTypingStatus();
|
||||||
@ -80,6 +121,7 @@ class ChatMessageInputState extends State<ChatMessageInput> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _sendMessage() async {
|
Future<void> _sendMessage() async {
|
||||||
|
if (_contentController.text.isEmpty && _attachments.isEmpty) return;
|
||||||
if (_isBusy) return;
|
if (_isBusy) return;
|
||||||
|
|
||||||
final attach = context.read<SnAttachmentProvider>();
|
final attach = context.read<SnAttachmentProvider>();
|
||||||
@ -144,10 +186,38 @@ class ChatMessageInputState extends State<ChatMessageInput> {
|
|||||||
|
|
||||||
final List<PostWriteMedia> _attachments = List.empty(growable: true);
|
final List<PostWriteMedia> _attachments = List.empty(growable: true);
|
||||||
|
|
||||||
|
OverlayEntry? _overlayEntry;
|
||||||
|
|
||||||
|
void _showEmojiPicker(BuildContext context) {
|
||||||
|
final overlay = Overlay.of(context);
|
||||||
|
_overlayEntry = OverlayEntry(
|
||||||
|
builder: (context) => Positioned(
|
||||||
|
bottom: 16 + MediaQuery.of(context).padding.bottom,
|
||||||
|
right: 16,
|
||||||
|
child: _StickerPicker(
|
||||||
|
originalText: _contentController.text,
|
||||||
|
onDismiss: () => _dismissEmojiPicker(),
|
||||||
|
onInsert: (str) => _contentController.text = str,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
overlay.insert(_overlayEntry!);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _dismissEmojiPicker() {
|
||||||
|
_overlayEntry?.remove();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_contentController.dispose();
|
_contentController.dispose();
|
||||||
_focusNode.dispose();
|
_focusNode.dispose();
|
||||||
|
_dismissEmojiPicker();
|
||||||
|
if (!kIsWeb && !(Platform.isAndroid || Platform.isIOS)) {
|
||||||
|
hotKeyManager.unregister(_pasteHotKey);
|
||||||
|
hotKeyManager.unregister(_newLineHotKey);
|
||||||
|
}
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -280,15 +350,29 @@ class ChatMessageInputState extends State<ChatMessageInput> {
|
|||||||
: 'fieldChatMessage'.tr(args: [widget.controller.channel?.name ?? 'loading'.tr()]),
|
: 'fieldChatMessage'.tr(args: [widget.controller.channel?.name ?? 'loading'.tr()]),
|
||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
),
|
),
|
||||||
|
textInputAction: TextInputAction.send,
|
||||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
onSubmitted: (_) {
|
onSubmitted: (_) {
|
||||||
if (_isBusy) return;
|
if (_isBusy) return;
|
||||||
_sendMessage();
|
_sendMessage();
|
||||||
_focusNode.requestFocus();
|
_focusNode.requestFocus();
|
||||||
},
|
},
|
||||||
|
maxLines: null,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(
|
||||||
|
Symbols.mood,
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
),
|
||||||
|
visualDensity: const VisualDensity(horizontal: -4, vertical: -4),
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
constraints: const BoxConstraints(),
|
||||||
|
onPressed: () {
|
||||||
|
_showEmojiPicker(context);
|
||||||
|
},
|
||||||
|
),
|
||||||
AddPostMediaButton(
|
AddPostMediaButton(
|
||||||
onAdd: (items) {
|
onAdd: (items) {
|
||||||
setState(() {
|
setState(() {
|
||||||
@ -302,10 +386,9 @@ class ChatMessageInputState extends State<ChatMessageInput> {
|
|||||||
Symbols.send,
|
Symbols.send,
|
||||||
color: Theme.of(context).colorScheme.primary,
|
color: Theme.of(context).colorScheme.primary,
|
||||||
),
|
),
|
||||||
visualDensity: const VisualDensity(
|
visualDensity: const VisualDensity(horizontal: -4, vertical: -4),
|
||||||
horizontal: -4,
|
padding: EdgeInsets.zero,
|
||||||
vertical: -4,
|
constraints: const BoxConstraints(),
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -314,3 +397,107 @@ class ChatMessageInputState extends State<ChatMessageInput> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _StickerPicker extends StatelessWidget {
|
||||||
|
final String originalText;
|
||||||
|
final Function? onDismiss;
|
||||||
|
final Function(String)? onInsert;
|
||||||
|
|
||||||
|
const _StickerPicker({this.onDismiss, required this.originalText, this.onInsert});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final sticker = context.read<SnStickerProvider>();
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
onDismiss?.call();
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
constraints: BoxConstraints(maxWidth: min(360, MediaQuery.of(context).size.width), maxHeight: 240),
|
||||||
|
child: Material(
|
||||||
|
elevation: 8,
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
|
child: ListView(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
children: sticker.stickersByPack.entries
|
||||||
|
.map((e) {
|
||||||
|
return <Widget>[
|
||||||
|
Container(
|
||||||
|
margin: EdgeInsets.only(bottom: 8),
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||||
|
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(e.value.first.pack.name).bold(),
|
||||||
|
Text(e.value.first.pack.description),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
GridView.builder(
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
padding: const EdgeInsets.only(left: 8, right: 8, bottom: 8),
|
||||||
|
shrinkWrap: true,
|
||||||
|
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
|
||||||
|
maxCrossAxisExtent: 48,
|
||||||
|
childAspectRatio: 1.0,
|
||||||
|
mainAxisSpacing: 8,
|
||||||
|
crossAxisSpacing: 8,
|
||||||
|
),
|
||||||
|
itemCount: e.value.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
final element = e.value[index];
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
final withSpace = originalText.isNotEmpty;
|
||||||
|
onInsert?.call(
|
||||||
|
'$originalText${withSpace ? ' ' : ''}:${element.pack.prefix}${element.alias}:');
|
||||||
|
onDismiss?.call();
|
||||||
|
},
|
||||||
|
child: Tooltip(
|
||||||
|
richMessage: TextSpan(
|
||||||
|
children: [
|
||||||
|
TextSpan(
|
||||||
|
text: ':${element.pack.prefix}${element.alias}:\n',
|
||||||
|
style: GoogleFonts.robotoMono()),
|
||||||
|
TextSpan(text: element.name).bold(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Container(
|
||||||
|
width: 48,
|
||||||
|
height: 48,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
|
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||||
|
),
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
|
child: UniversalImage(
|
||||||
|
sn.getAttachmentUrl(element.attachment.rid),
|
||||||
|
width: 48,
|
||||||
|
height: 48,
|
||||||
|
cacheHeight: 48,
|
||||||
|
cacheWidth: 48,
|
||||||
|
fit: BoxFit.contain,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
];
|
||||||
|
})
|
||||||
|
.expand((ele) => ele)
|
||||||
|
.toList(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -129,14 +129,27 @@ class MarkdownTextContent extends StatelessWidget {
|
|||||||
future: st.lookupSticker(alias),
|
future: st.lookupSticker(alias),
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (snapshot.hasData) {
|
if (snapshot.hasData) {
|
||||||
return UniversalImage(
|
return GestureDetector(
|
||||||
sn.getAttachmentUrl(snapshot.data!.attachment.rid),
|
child: UniversalImage(
|
||||||
fit: BoxFit.cover,
|
sn.getAttachmentUrl(snapshot.data!.attachment.rid),
|
||||||
width: size,
|
fit: BoxFit.contain,
|
||||||
height: size,
|
width: size,
|
||||||
cacheHeight: size,
|
height: size,
|
||||||
cacheWidth: size,
|
cacheHeight: size,
|
||||||
);
|
cacheWidth: size,
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
if (snapshot.data == null) return;
|
||||||
|
context.pushTransparentRoute(
|
||||||
|
AttachmentZoomView(
|
||||||
|
data: [snapshot.data!.attachment],
|
||||||
|
initialIndex: 0,
|
||||||
|
heroTags: [const Uuid().v4()],
|
||||||
|
),
|
||||||
|
backgroundColor: Colors.black.withOpacity(0.7),
|
||||||
|
rootNavigator: true,
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
},
|
},
|
||||||
@ -145,7 +158,7 @@ class MarkdownTextContent extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
case 'attachments':
|
case 'attachments':
|
||||||
final attachment = attachments?.firstWhere(
|
final attachment = attachments?.firstWhere(
|
||||||
(ele) => ele?.rid == segments[1],
|
(ele) => ele?.rid == segments[1],
|
||||||
orElse: () => null,
|
orElse: () => null,
|
||||||
);
|
);
|
||||||
if (attachment != null) {
|
if (attachment != null) {
|
||||||
|
@ -58,6 +58,7 @@ class AppScaffold extends StatelessWidget {
|
|||||||
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||||
body: SizedBox.expand(
|
body: SizedBox.expand(
|
||||||
child: AppBackground(
|
child: AppBackground(
|
||||||
|
isRoot: true,
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
IgnorePointer(child: SizedBox(height: appBar != null ? appBarHeight + safeTop : 0)),
|
IgnorePointer(child: SizedBox(height: appBar != null ? appBarHeight + safeTop : 0)),
|
||||||
@ -165,10 +166,12 @@ class AppRootScaffold extends StatelessWidget {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
mainAxisAlignment: Platform.isMacOS ? MainAxisAlignment.center : MainAxisAlignment.start,
|
mainAxisAlignment: Platform.isMacOS ? MainAxisAlignment.center : MainAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Expanded(
|
||||||
'Solar Network',
|
child: Text(
|
||||||
style: GoogleFonts.spaceGrotesk(),
|
'Solar Network',
|
||||||
).padding(horizontal: 12, vertical: 5),
|
style: GoogleFonts.spaceGrotesk(),
|
||||||
|
).padding(horizontal: 12, vertical: 5),
|
||||||
|
),
|
||||||
if (!Platform.isMacOS)
|
if (!Platform.isMacOS)
|
||||||
Row(
|
Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
@ -4,21 +4,69 @@ import 'package:gap/gap.dart';
|
|||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:responsive_framework/responsive_framework.dart';
|
||||||
import 'package: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/userinfo.dart';
|
import 'package:surface/providers/userinfo.dart';
|
||||||
import 'package:surface/types/post.dart';
|
import 'package:surface/types/post.dart';
|
||||||
|
import 'package:surface/widgets/dialog.dart';
|
||||||
import 'package:surface/widgets/post/post_item.dart';
|
import 'package:surface/widgets/post/post_item.dart';
|
||||||
import 'package:surface/widgets/post/post_mini_editor.dart';
|
import 'package:surface/widgets/post/post_mini_editor.dart';
|
||||||
import 'package:very_good_infinite_list/very_good_infinite_list.dart';
|
import 'package:very_good_infinite_list/very_good_infinite_list.dart';
|
||||||
|
|
||||||
class PostCommentSliverList extends StatefulWidget {
|
import '../../providers/sn_network.dart';
|
||||||
final int parentPostId;
|
|
||||||
|
class PostCommentQuickAction extends StatelessWidget {
|
||||||
final double? maxWidth;
|
final double? maxWidth;
|
||||||
|
final SnPost parentPost;
|
||||||
|
final Function? onPosted;
|
||||||
|
|
||||||
|
const PostCommentQuickAction({super.key, this.maxWidth, required this.parentPost, this.onPosted});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final devicePixelRatio = MediaQuery.of(context).devicePixelRatio;
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
height: 240,
|
||||||
|
constraints: BoxConstraints(maxWidth: maxWidth ?? double.infinity),
|
||||||
|
margin: ResponsiveBreakpoints.of(context).largerThan(MOBILE) ? const EdgeInsets.symmetric(vertical: 8) : EdgeInsets.zero,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: ResponsiveBreakpoints.of(context).largerThan(MOBILE)
|
||||||
|
? const BorderRadius.all(Radius.circular(8))
|
||||||
|
: BorderRadius.zero,
|
||||||
|
border: ResponsiveBreakpoints.of(context).largerThan(MOBILE)
|
||||||
|
? Border.all(
|
||||||
|
color: Theme.of(context).dividerColor,
|
||||||
|
width: 1 / devicePixelRatio,
|
||||||
|
)
|
||||||
|
: Border.symmetric(
|
||||||
|
horizontal: BorderSide(
|
||||||
|
color: Theme.of(context).dividerColor,
|
||||||
|
width: 1 / devicePixelRatio,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: PostMiniEditor(
|
||||||
|
postReplyId: parentPost.id,
|
||||||
|
onPost: () {
|
||||||
|
onPosted?.call();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PostCommentSliverList extends StatefulWidget {
|
||||||
|
final SnPost parentPost;
|
||||||
|
final double? maxWidth;
|
||||||
|
final Function(SnPost)? onSelectAnswer;
|
||||||
|
|
||||||
const PostCommentSliverList({
|
const PostCommentSliverList({
|
||||||
super.key,
|
super.key,
|
||||||
required this.parentPostId,
|
required this.parentPost,
|
||||||
this.maxWidth,
|
this.maxWidth,
|
||||||
|
this.onSelectAnswer,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -37,7 +85,7 @@ class PostCommentSliverListState extends State<PostCommentSliverList> {
|
|||||||
setState(() => _isBusy = true);
|
setState(() => _isBusy = true);
|
||||||
|
|
||||||
final pt = context.read<SnPostContentProvider>();
|
final pt = context.read<SnPostContentProvider>();
|
||||||
final result = await pt.listPostReplies(widget.parentPostId);
|
final result = await pt.listPostReplies(widget.parentPost.id);
|
||||||
final List<SnPost> out = result.$1;
|
final List<SnPost> out = result.$1;
|
||||||
|
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
@ -48,8 +96,24 @@ class PostCommentSliverListState extends State<PostCommentSliverList> {
|
|||||||
if (mounted) setState(() => _isBusy = false);
|
if (mounted) setState(() => _isBusy = false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _selectAnswer(SnPost answer) async {
|
||||||
|
try {
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
await sn.client.put('/cgi/co/questions/${widget.parentPost.id}/answer', data: {
|
||||||
|
'publisher': answer.publisherId,
|
||||||
|
'answer_id': answer.id,
|
||||||
|
});
|
||||||
|
if (!mounted) return;
|
||||||
|
await refresh();
|
||||||
|
} catch (err) {
|
||||||
|
if (!mounted) return;
|
||||||
|
context.showErrorDialog(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> refresh() async {
|
Future<void> refresh() async {
|
||||||
_posts.clear();
|
_posts.clear();
|
||||||
|
_postCount = null;
|
||||||
_fetchPosts();
|
_fetchPosts();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,6 +135,7 @@ class PostCommentSliverListState extends State<PostCommentSliverList> {
|
|||||||
child: PostItem(
|
child: PostItem(
|
||||||
data: _posts[idx],
|
data: _posts[idx],
|
||||||
maxWidth: widget.maxWidth,
|
maxWidth: widget.maxWidth,
|
||||||
|
onSelectAnswer: widget.parentPost.type == 'question' ? () => _selectAnswer(_posts[idx]) : null,
|
||||||
onChanged: (data) {
|
onChanged: (data) {
|
||||||
setState(() => _posts[idx] = data);
|
setState(() => _posts[idx] = data);
|
||||||
},
|
},
|
||||||
@ -94,11 +159,12 @@ class PostCommentSliverListState extends State<PostCommentSliverList> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class PostCommentListPopup extends StatefulWidget {
|
class PostCommentListPopup extends StatefulWidget {
|
||||||
final int postId;
|
final SnPost post;
|
||||||
final int commentCount;
|
final int commentCount;
|
||||||
|
|
||||||
const PostCommentListPopup({
|
const PostCommentListPopup({
|
||||||
super.key,
|
super.key,
|
||||||
required this.postId,
|
required this.post,
|
||||||
this.commentCount = 0,
|
this.commentCount = 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -122,9 +188,7 @@ class _PostCommentListPopupState extends State<PostCommentListPopup> {
|
|||||||
children: [
|
children: [
|
||||||
const Icon(Symbols.comment, size: 24),
|
const Icon(Symbols.comment, size: 24),
|
||||||
const Gap(16),
|
const Gap(16),
|
||||||
Text('postCommentsDetailed')
|
Text('postCommentsDetailed').plural(widget.commentCount).textStyle(Theme.of(context).textTheme.titleLarge!),
|
||||||
.plural(widget.commentCount)
|
|
||||||
.textStyle(Theme.of(context).textTheme.titleLarge!),
|
|
||||||
],
|
],
|
||||||
).padding(horizontal: 20, top: 16, bottom: 12),
|
).padding(horizontal: 20, top: 16, bottom: 12),
|
||||||
Expanded(
|
Expanded(
|
||||||
@ -143,7 +207,7 @@ class _PostCommentListPopupState extends State<PostCommentListPopup> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: PostMiniEditor(
|
child: PostMiniEditor(
|
||||||
postReplyId: widget.postId,
|
postReplyId: widget.post.id,
|
||||||
onPost: () {
|
onPost: () {
|
||||||
_childListKey.currentState!.refresh();
|
_childListKey.currentState!.refresh();
|
||||||
},
|
},
|
||||||
@ -151,8 +215,8 @@ class _PostCommentListPopupState extends State<PostCommentListPopup> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
PostCommentSliverList(
|
PostCommentSliverList(
|
||||||
|
parentPost: widget.post,
|
||||||
key: _childListKey,
|
key: _childListKey,
|
||||||
parentPostId: widget.postId,
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:math' as math;
|
import 'dart:math' as math;
|
||||||
|
|
||||||
|
import 'package:animations/animations.dart';
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:file_saver/file_saver.dart';
|
import 'package:file_saver/file_saver.dart';
|
||||||
@ -22,10 +23,12 @@ import 'package:styled_widget/styled_widget.dart';
|
|||||||
import 'package:surface/providers/config.dart';
|
import 'package:surface/providers/config.dart';
|
||||||
import 'package:surface/providers/sn_network.dart';
|
import 'package:surface/providers/sn_network.dart';
|
||||||
import 'package:surface/providers/userinfo.dart';
|
import 'package:surface/providers/userinfo.dart';
|
||||||
|
import 'package:surface/screens/post/post_detail.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/reaction.dart';
|
import 'package:surface/types/reaction.dart';
|
||||||
import 'package:surface/widgets/account/account_image.dart';
|
import 'package:surface/widgets/account/account_image.dart';
|
||||||
|
import 'package:surface/widgets/attachment/attachment_item.dart';
|
||||||
import 'package:surface/widgets/attachment/attachment_list.dart';
|
import 'package:surface/widgets/attachment/attachment_list.dart';
|
||||||
import 'package:surface/widgets/dialog.dart';
|
import 'package:surface/widgets/dialog.dart';
|
||||||
import 'package:surface/widgets/link_preview.dart';
|
import 'package:surface/widgets/link_preview.dart';
|
||||||
@ -33,11 +36,73 @@ import 'package:surface/widgets/markdown_content.dart';
|
|||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
import 'package:surface/widgets/post/post_comment_list.dart';
|
import 'package:surface/widgets/post/post_comment_list.dart';
|
||||||
import 'package:surface/widgets/post/post_meta_editor.dart';
|
import 'package:surface/widgets/post/post_meta_editor.dart';
|
||||||
|
import 'package:surface/widgets/post/post_poll.dart';
|
||||||
import 'package:surface/widgets/post/post_reaction.dart';
|
import 'package:surface/widgets/post/post_reaction.dart';
|
||||||
import 'package:surface/widgets/post/publisher_popover.dart';
|
import 'package:surface/widgets/post/publisher_popover.dart';
|
||||||
import 'package:surface/widgets/universal_image.dart';
|
import 'package:surface/widgets/universal_image.dart';
|
||||||
import 'package:xml/xml.dart';
|
import 'package:xml/xml.dart';
|
||||||
|
|
||||||
|
class OpenablePostItem extends StatelessWidget {
|
||||||
|
final SnPost data;
|
||||||
|
final bool showReactions;
|
||||||
|
final bool showComments;
|
||||||
|
final bool showMenu;
|
||||||
|
final bool showFullPost;
|
||||||
|
final double? maxWidth;
|
||||||
|
final Function(SnPost data)? onChanged;
|
||||||
|
final Function()? onDeleted;
|
||||||
|
final Function()? onSelectAnswer;
|
||||||
|
|
||||||
|
const OpenablePostItem({
|
||||||
|
super.key,
|
||||||
|
required this.data,
|
||||||
|
this.showReactions = true,
|
||||||
|
this.showComments = true,
|
||||||
|
this.showMenu = true,
|
||||||
|
this.showFullPost = false,
|
||||||
|
this.maxWidth,
|
||||||
|
this.onChanged,
|
||||||
|
this.onDeleted,
|
||||||
|
this.onSelectAnswer,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final cfg = context.read<ConfigProvider>();
|
||||||
|
|
||||||
|
return Center(
|
||||||
|
child: OpenContainer(
|
||||||
|
closedBuilder: (_, __) => Container(
|
||||||
|
constraints: BoxConstraints(maxWidth: maxWidth ?? double.infinity),
|
||||||
|
child: PostItem(
|
||||||
|
data: data,
|
||||||
|
maxWidth: maxWidth,
|
||||||
|
showComments: showComments,
|
||||||
|
showFullPost: showFullPost,
|
||||||
|
onChanged: onChanged,
|
||||||
|
onDeleted: onDeleted,
|
||||||
|
onSelectAnswer: onSelectAnswer,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
openBuilder: (_, close) => PostDetailScreen(
|
||||||
|
slug: data.id.toString(),
|
||||||
|
preload: data,
|
||||||
|
onBack: close,
|
||||||
|
),
|
||||||
|
openColor: Colors.transparent,
|
||||||
|
openElevation: 0,
|
||||||
|
transitionType: ContainerTransitionType.fade,
|
||||||
|
closedColor: Theme.of(context).colorScheme.surfaceContainerLow.withOpacity(
|
||||||
|
cfg.prefs.getBool(kAppBackgroundStoreKey) == true ? 0.75 : 1,
|
||||||
|
),
|
||||||
|
closedShape: const RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(16)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class PostItem extends StatelessWidget {
|
class PostItem extends StatelessWidget {
|
||||||
final SnPost data;
|
final SnPost data;
|
||||||
final bool showReactions;
|
final bool showReactions;
|
||||||
@ -47,6 +112,7 @@ class PostItem extends StatelessWidget {
|
|||||||
final double? maxWidth;
|
final double? maxWidth;
|
||||||
final Function(SnPost data)? onChanged;
|
final Function(SnPost data)? onChanged;
|
||||||
final Function()? onDeleted;
|
final Function()? onDeleted;
|
||||||
|
final Function()? onSelectAnswer;
|
||||||
|
|
||||||
const PostItem({
|
const PostItem({
|
||||||
super.key,
|
super.key,
|
||||||
@ -58,6 +124,7 @@ class PostItem extends StatelessWidget {
|
|||||||
this.maxWidth,
|
this.maxWidth,
|
||||||
this.onChanged,
|
this.onChanged,
|
||||||
this.onDeleted,
|
this.onDeleted,
|
||||||
|
this.onSelectAnswer,
|
||||||
});
|
});
|
||||||
|
|
||||||
void _onChanged(SnPost data) {
|
void _onChanged(SnPost data) {
|
||||||
@ -129,6 +196,57 @@ class PostItem extends StatelessWidget {
|
|||||||
final ua = context.read<UserProvider>();
|
final ua = context.read<UserProvider>();
|
||||||
final isAuthor = ua.isAuthorized && data.publisher.accountId == ua.user?.id;
|
final isAuthor = ua.isAuthorized && data.publisher.accountId == ua.user?.id;
|
||||||
|
|
||||||
|
// Video full view
|
||||||
|
if (showFullPost && data.type == 'video' && ResponsiveBreakpoints.of(context).largerThan(TABLET)) {
|
||||||
|
return Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const Gap(16),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
_PostContentHeader(
|
||||||
|
data: data,
|
||||||
|
isAuthor: isAuthor,
|
||||||
|
isRelativeDate: !showFullPost,
|
||||||
|
onShare: () => _doShare(context),
|
||||||
|
onShareImage: () => _doShareViaPicture(context),
|
||||||
|
onSelectAnswer: onSelectAnswer,
|
||||||
|
onDeleted: () {
|
||||||
|
if (onDeleted != null) {}
|
||||||
|
},
|
||||||
|
).padding(bottom: 8),
|
||||||
|
if (data.preload?.video != null) _PostVideoPlayer(data: data).padding(bottom: 8),
|
||||||
|
_PostHeadline(data: data).padding(horizontal: 4, bottom: 8),
|
||||||
|
_PostFeaturedComment(data: data),
|
||||||
|
_PostBottomAction(
|
||||||
|
data: data,
|
||||||
|
showComments: true,
|
||||||
|
showReactions: showReactions,
|
||||||
|
onShare: () => _doShare(context),
|
||||||
|
onShareImage: () => _doShareViaPicture(context),
|
||||||
|
onChanged: _onChanged,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Gap(4),
|
||||||
|
SizedBox(
|
||||||
|
width: 340,
|
||||||
|
child: CustomScrollView(
|
||||||
|
shrinkWrap: true,
|
||||||
|
slivers: [
|
||||||
|
PostCommentSliverList(
|
||||||
|
parentPost: data,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Article headline preview
|
// Article headline preview
|
||||||
if (!showFullPost && data.type == 'article') {
|
if (!showFullPost && data.type == 'article') {
|
||||||
return Container(
|
return Container(
|
||||||
@ -142,10 +260,12 @@ class PostItem extends StatelessWidget {
|
|||||||
isRelativeDate: !showFullPost,
|
isRelativeDate: !showFullPost,
|
||||||
onShare: () => _doShare(context),
|
onShare: () => _doShare(context),
|
||||||
onShareImage: () => _doShareViaPicture(context),
|
onShareImage: () => _doShareViaPicture(context),
|
||||||
|
onSelectAnswer: onSelectAnswer,
|
||||||
onDeleted: () {
|
onDeleted: () {
|
||||||
if (onDeleted != null) {}
|
if (onDeleted != null) {}
|
||||||
},
|
},
|
||||||
).padding(horizontal: 12, top: 8, bottom: 8),
|
).padding(horizontal: 12, top: 8, bottom: 8),
|
||||||
|
if (data.preload?.video != null) _PostVideoPlayer(data: data).padding(horizontal: 12, bottom: 8),
|
||||||
Container(
|
Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
margin: const EdgeInsets.only(bottom: 4, left: 12, right: 12),
|
margin: const EdgeInsets.only(bottom: 4, left: 12, right: 12),
|
||||||
@ -224,10 +344,13 @@ class PostItem extends StatelessWidget {
|
|||||||
showMenu: showMenu,
|
showMenu: showMenu,
|
||||||
onShare: () => _doShare(context),
|
onShare: () => _doShare(context),
|
||||||
onShareImage: () => _doShareViaPicture(context),
|
onShareImage: () => _doShareViaPicture(context),
|
||||||
|
onSelectAnswer: onSelectAnswer,
|
||||||
onDeleted: () {
|
onDeleted: () {
|
||||||
if (onDeleted != null) onDeleted!();
|
if (onDeleted != null) onDeleted!();
|
||||||
},
|
},
|
||||||
).padding(horizontal: 12, vertical: 8),
|
).padding(horizontal: 12, vertical: 8),
|
||||||
|
if (data.preload?.video != null) _PostVideoPlayer(data: data).padding(horizontal: 12, bottom: 8),
|
||||||
|
if (data.type == 'question') _PostQuestionHint(data: data).padding(horizontal: 16, bottom: 8),
|
||||||
if (data.body['title'] != null || data.body['description'] != null)
|
if (data.body['title'] != null || data.body['description'] != null)
|
||||||
_PostHeadline(
|
_PostHeadline(
|
||||||
data: data,
|
data: data,
|
||||||
@ -267,6 +390,7 @@ class PostItem extends StatelessWidget {
|
|||||||
fit: showFullPost ? BoxFit.cover : BoxFit.contain,
|
fit: showFullPost ? BoxFit.cover : BoxFit.contain,
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||||
),
|
),
|
||||||
|
if (data.preload?.poll != null) PostPoll(poll: data.preload!.poll!).padding(horizontal: 12, vertical: 4),
|
||||||
if (data.body['content'] != null && (cfg.prefs.getBool(kAppExpandPostLink) ?? true))
|
if (data.body['content'] != null && (cfg.prefs.getBool(kAppExpandPostLink) ?? true))
|
||||||
LinkPreviewWidget(
|
LinkPreviewWidget(
|
||||||
text: data.body['content'],
|
text: data.body['content'],
|
||||||
@ -333,6 +457,7 @@ class PostShareImageWidget extends StatelessWidget {
|
|||||||
showMenu: false,
|
showMenu: false,
|
||||||
isRelativeDate: false,
|
isRelativeDate: false,
|
||||||
).padding(horizontal: 16, bottom: 8),
|
).padding(horizontal: 16, bottom: 8),
|
||||||
|
if (data.type == 'question') _PostQuestionHint(data: data).padding(horizontal: 16, bottom: 8),
|
||||||
_PostHeadline(
|
_PostHeadline(
|
||||||
data: data,
|
data: data,
|
||||||
isEnlarge: data.type == 'article',
|
isEnlarge: data.type == 'article',
|
||||||
@ -438,6 +563,30 @@ class PostShareImageWidget extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _PostQuestionHint extends StatelessWidget {
|
||||||
|
final SnPost data;
|
||||||
|
|
||||||
|
const _PostQuestionHint({required this.data});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
Icon(data.body['answer'] == null ? Symbols.help : Symbols.check_circle, size: 20),
|
||||||
|
const Gap(4),
|
||||||
|
if (data.body['answer'] == null && data.body['reward']?.toDouble() != null)
|
||||||
|
Text('postQuestionUnansweredWithReward'.tr(args: [
|
||||||
|
'${data.body['reward']}',
|
||||||
|
])).opacity(0.75)
|
||||||
|
else if (data.body['answer'] == null)
|
||||||
|
Text('postQuestionUnanswered'.tr()).opacity(0.75)
|
||||||
|
else
|
||||||
|
Text('postQuestionAnswered'.tr()).opacity(0.75),
|
||||||
|
],
|
||||||
|
).opacity(0.75);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class _PostBottomAction extends StatelessWidget {
|
class _PostBottomAction extends StatelessWidget {
|
||||||
final SnPost data;
|
final SnPost data;
|
||||||
final bool showComments;
|
final bool showComments;
|
||||||
@ -529,12 +678,21 @@ class _PostBottomAction extends StatelessWidget {
|
|||||||
context: context,
|
context: context,
|
||||||
useRootNavigator: true,
|
useRootNavigator: true,
|
||||||
builder: (context) => PostCommentListPopup(
|
builder: (context) => PostCommentListPopup(
|
||||||
postId: data.id,
|
post: data,
|
||||||
commentCount: data.metric.replyCount,
|
commentCount: data.metric.replyCount,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
InkWell(
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Icon(Symbols.play_circle, size: 20, color: iconColor),
|
||||||
|
const Gap(8),
|
||||||
|
Text('postViews').plural(data.totalViews),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
InkWell(
|
InkWell(
|
||||||
@ -652,6 +810,7 @@ class _PostContentHeader extends StatelessWidget {
|
|||||||
final bool showMenu;
|
final bool showMenu;
|
||||||
final Function onDeleted;
|
final Function onDeleted;
|
||||||
final Function() onShare, onShareImage;
|
final Function() onShare, onShareImage;
|
||||||
|
final Function()? onSelectAnswer;
|
||||||
|
|
||||||
const _PostContentHeader({
|
const _PostContentHeader({
|
||||||
required this.data,
|
required this.data,
|
||||||
@ -662,6 +821,7 @@ class _PostContentHeader extends StatelessWidget {
|
|||||||
required this.onDeleted,
|
required this.onDeleted,
|
||||||
required this.onShare,
|
required this.onShare,
|
||||||
required this.onShareImage,
|
required this.onShareImage,
|
||||||
|
this.onSelectAnswer,
|
||||||
});
|
});
|
||||||
|
|
||||||
Future<void> _deletePost(BuildContext context) async {
|
Future<void> _deletePost(BuildContext context) async {
|
||||||
@ -678,7 +838,6 @@ class _PostContentHeader extends StatelessWidget {
|
|||||||
await sn.client.delete('/cgi/co/posts/${data.id}', queryParameters: {
|
await sn.client.delete('/cgi/co/posts/${data.id}', queryParameters: {
|
||||||
'publisherId': data.publisherId,
|
'publisherId': data.publisherId,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
context.showSnackbar('postDeleted'.tr(args: ['#${data.id}']));
|
context.showSnackbar('postDeleted'.tr(args: ['#${data.id}']));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -687,6 +846,25 @@ class _PostContentHeader extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _flagPost(BuildContext context) async {
|
||||||
|
final confirm = await context.showConfirmDialog(
|
||||||
|
'flagPost'.tr(),
|
||||||
|
'flagPostDescription'.tr(),
|
||||||
|
);
|
||||||
|
if (!confirm) return;
|
||||||
|
if (!context.mounted) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
await sn.client.post('/cgi/co/posts/${data.id}/flag');
|
||||||
|
if (!context.mounted) return;
|
||||||
|
context.showSnackbar('postFlagged'.tr());
|
||||||
|
} catch (err) {
|
||||||
|
if (!context.mounted) return;
|
||||||
|
context.showErrorDialog(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Row(
|
return Row(
|
||||||
@ -760,6 +938,20 @@ class _PostContentHeader extends StatelessWidget {
|
|||||||
visualDensity: VisualDensity(horizontal: -4, vertical: -4),
|
visualDensity: VisualDensity(horizontal: -4, vertical: -4),
|
||||||
),
|
),
|
||||||
itemBuilder: (BuildContext context) => <PopupMenuEntry>[
|
itemBuilder: (BuildContext context) => <PopupMenuEntry>[
|
||||||
|
if (isAuthor && onSelectAnswer != null)
|
||||||
|
PopupMenuItem(
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const Icon(Symbols.check_circle),
|
||||||
|
const Gap(16),
|
||||||
|
Text('postQuestionAnswerSelect').tr(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
onSelectAnswer?.call();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
if (isAuthor && onSelectAnswer != null) PopupMenuDivider(),
|
||||||
if (isAuthor)
|
if (isAuthor)
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
child: Row(
|
child: Row(
|
||||||
@ -800,7 +992,7 @@ class _PostContentHeader extends StatelessWidget {
|
|||||||
onTap: () {
|
onTap: () {
|
||||||
GoRouter.of(context).pushNamed(
|
GoRouter.of(context).pushNamed(
|
||||||
'postEditor',
|
'postEditor',
|
||||||
pathParameters: {'mode': data.typePlural},
|
pathParameters: {'mode': 'stories'},
|
||||||
queryParameters: {'replying': data.id.toString()},
|
queryParameters: {'replying': data.id.toString()},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -816,7 +1008,7 @@ class _PostContentHeader extends StatelessWidget {
|
|||||||
onTap: () {
|
onTap: () {
|
||||||
GoRouter.of(context).pushNamed(
|
GoRouter.of(context).pushNamed(
|
||||||
'postEditor',
|
'postEditor',
|
||||||
pathParameters: {'mode': data.typePlural},
|
pathParameters: {'mode': 'stories'},
|
||||||
queryParameters: {'reposting': data.id.toString()},
|
queryParameters: {'reposting': data.id.toString()},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -833,7 +1025,7 @@ class _PostContentHeader extends StatelessWidget {
|
|||||||
onTap: () {
|
onTap: () {
|
||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => _PostGetInsightSheet(postId: data.id),
|
builder: (context) => _PostGetInsightPopup(postId: data.id),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -864,6 +1056,18 @@ class _PostContentHeader extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
const Icon(Symbols.flag),
|
const Icon(Symbols.flag),
|
||||||
const Gap(16),
|
const Gap(16),
|
||||||
|
Text('flagPostAction').tr(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
_flagPost(context);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
PopupMenuItem(
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const Icon(Symbols.report),
|
||||||
|
const Gap(16),
|
||||||
Text('report').tr(),
|
Text('report').tr(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -1139,8 +1343,18 @@ class _PostFeaturedComment extends StatefulWidget {
|
|||||||
|
|
||||||
class _PostFeaturedCommentState extends State<_PostFeaturedComment> {
|
class _PostFeaturedCommentState extends State<_PostFeaturedComment> {
|
||||||
SnPost? _featuredComment;
|
SnPost? _featuredComment;
|
||||||
|
bool _isAnswer = false;
|
||||||
|
|
||||||
Future<void> _fetchComments() async {
|
Future<void> _fetchComments() async {
|
||||||
|
// If this is a answered question, fetch the answer instead
|
||||||
|
if (widget.data.type == 'question' && widget.data.body['answer'] != null) {
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
final resp = await sn.client.get('/cgi/co/posts/${widget.data.body['answer']}');
|
||||||
|
_isAnswer = true;
|
||||||
|
setState(() => _featuredComment = SnPost.fromJson(resp.data));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final sn = context.read<SnNetworkProvider>();
|
final sn = context.read<SnNetworkProvider>();
|
||||||
final resp = await sn.client.get('/cgi/co/posts/${widget.data.id}/replies/featured', queryParameters: {
|
final resp = await sn.client.get('/cgi/co/posts/${widget.data.id}/replies/featured', queryParameters: {
|
||||||
@ -1166,13 +1380,15 @@ class _PostFeaturedCommentState extends State<_PostFeaturedComment> {
|
|||||||
if (widget.data.metric.replyCount == 0) return const SizedBox.shrink();
|
if (widget.data.metric.replyCount == 0) return const SizedBox.shrink();
|
||||||
if (_featuredComment == null) return const SizedBox.shrink();
|
if (_featuredComment == null) return const SizedBox.shrink();
|
||||||
|
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
|
||||||
return AnimateWidgetExtensions(Container(
|
return AnimateWidgetExtensions(Container(
|
||||||
constraints: BoxConstraints(maxWidth: widget.maxWidth ?? double.infinity),
|
constraints: BoxConstraints(maxWidth: widget.maxWidth ?? double.infinity),
|
||||||
margin: const EdgeInsets.only(top: 8),
|
margin: const EdgeInsets.only(top: 8),
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
child: Material(
|
child: Material(
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
color: _isAnswer ? Colors.green.withOpacity(0.5) : Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
@ -1180,7 +1396,7 @@ class _PostFeaturedCommentState extends State<_PostFeaturedComment> {
|
|||||||
context: context,
|
context: context,
|
||||||
useRootNavigator: true,
|
useRootNavigator: true,
|
||||||
builder: (context) => PostCommentListPopup(
|
builder: (context) => PostCommentListPopup(
|
||||||
postId: widget.data.id,
|
post: widget.data,
|
||||||
commentCount: widget.data.metric.replyCount,
|
commentCount: widget.data.metric.replyCount,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -1188,7 +1404,18 @@ class _PostFeaturedCommentState extends State<_PostFeaturedComment> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text('postFeaturedComment', style: Theme.of(context).textTheme.titleMedium!.copyWith(fontSize: 16)).tr(),
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const Gap(2),
|
||||||
|
Icon(_isAnswer ? Symbols.task_alt : Symbols.prompt_suggestion, size: 20),
|
||||||
|
const Gap(10),
|
||||||
|
Text(
|
||||||
|
_isAnswer ? 'postQuestionAnswerTitle' : 'postFeaturedComment',
|
||||||
|
style: Theme.of(context).textTheme.titleMedium!.copyWith(fontSize: 15),
|
||||||
|
).tr(),
|
||||||
|
],
|
||||||
|
),
|
||||||
const Gap(4),
|
const Gap(4),
|
||||||
Row(
|
Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
@ -1196,7 +1423,7 @@ class _PostFeaturedCommentState extends State<_PostFeaturedComment> {
|
|||||||
CircleAvatar(
|
CircleAvatar(
|
||||||
radius: 12,
|
radius: 12,
|
||||||
backgroundImage: UniversalImage.provider(
|
backgroundImage: UniversalImage.provider(
|
||||||
_featuredComment!.publisher.avatar,
|
sn.getAttachmentUrl(_featuredComment!.publisher.avatar),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
@ -1292,16 +1519,16 @@ class _PostAbuseReportDialogState extends State<_PostAbuseReportDialog> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _PostGetInsightSheet extends StatefulWidget {
|
class _PostGetInsightPopup extends StatefulWidget {
|
||||||
final int postId;
|
final int postId;
|
||||||
|
|
||||||
const _PostGetInsightSheet({required this.postId});
|
const _PostGetInsightPopup({required this.postId});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<_PostGetInsightSheet> createState() => _PostGetInsightSheetState();
|
State<_PostGetInsightPopup> createState() => _PostGetInsightPopupState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _PostGetInsightSheetState extends State<_PostGetInsightSheet> {
|
class _PostGetInsightPopupState extends State<_PostGetInsightPopup> {
|
||||||
String? _response;
|
String? _response;
|
||||||
String? _thinkingProcess;
|
String? _thinkingProcess;
|
||||||
|
|
||||||
@ -1314,8 +1541,14 @@ class _PostGetInsightSheetState extends State<_PostGetInsightSheet> {
|
|||||||
receiveTimeout: const Duration(minutes: 10),
|
receiveTimeout: const Duration(minutes: 10),
|
||||||
));
|
));
|
||||||
final out = resp.data['response'] as String;
|
final out = resp.data['response'] as String;
|
||||||
final document = XmlDocument.parse(out);
|
|
||||||
_thinkingProcess = document.getElement('think')?.innerText.trim();
|
try {
|
||||||
|
final document = XmlDocument.parse(out);
|
||||||
|
_thinkingProcess = document.getElement('think')?.innerText.trim();
|
||||||
|
} catch (_) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
|
||||||
RegExp cleanThinkingRegExp = RegExp(r'<think>[\s\S]*?</think>');
|
RegExp cleanThinkingRegExp = RegExp(r'<think>[\s\S]*?</think>');
|
||||||
setState(() => _response = out.replaceAll(cleanThinkingRegExp, '').trim());
|
setState(() => _response = out.replaceAll(cleanThinkingRegExp, '').trim());
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -1384,3 +1617,29 @@ class _PostGetInsightSheetState extends State<_PostGetInsightSheet> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _PostVideoPlayer extends StatelessWidget {
|
||||||
|
final SnPost data;
|
||||||
|
|
||||||
|
const _PostVideoPlayer({required this.data});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
|
border: Border.all(
|
||||||
|
color: Theme.of(context).dividerColor,
|
||||||
|
width: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: AspectRatio(
|
||||||
|
aspectRatio: 16 / 9,
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
|
child: AttachmentItem(data: data.preload!.video!, heroTag: 'post-video-${data.id}'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -95,6 +95,7 @@ class PostMediaPendingList extends StatelessWidget {
|
|||||||
context: context,
|
context: context,
|
||||||
builder: (context) => AttachmentInputDialog(
|
builder: (context) => AttachmentInputDialog(
|
||||||
title: 'attachmentSetThumbnail'.tr(),
|
title: 'attachmentSetThumbnail'.tr(),
|
||||||
|
pool: 'interactive',
|
||||||
analyzeNow: true,
|
analyzeNow: true,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -292,7 +293,7 @@ class PostMediaPendingList extends StatelessWidget {
|
|||||||
constraints: const BoxConstraints(maxHeight: 120),
|
constraints: const BoxConstraints(maxHeight: 120),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
const Gap(8),
|
const Gap(16),
|
||||||
if (thumbnail != null)
|
if (thumbnail != null)
|
||||||
ContextMenuArea(
|
ContextMenuArea(
|
||||||
contextMenu: _createContextMenu(context, -1, thumbnail!),
|
contextMenu: _createContextMenu(context, -1, thumbnail!),
|
||||||
@ -337,15 +338,10 @@ class _PostMediaPendingItem extends StatelessWidget {
|
|||||||
|
|
||||||
final sn = context.read<SnNetworkProvider>();
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
|
||||||
return Container(
|
return Material(
|
||||||
decoration: BoxDecoration(
|
elevation: 4,
|
||||||
border: Border.all(
|
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||||
color: Theme.of(context).dividerColor,
|
borderRadius: BorderRadius.circular(8),
|
||||||
width: 1,
|
|
||||||
),
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
|
||||||
),
|
|
||||||
child: ClipRRect(
|
child: ClipRRect(
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
child: Row(
|
child: Row(
|
||||||
|
@ -19,6 +19,7 @@ const Map<int, String> kPostVisibilityLevel = {
|
|||||||
|
|
||||||
class PostMetaEditor extends StatelessWidget {
|
class PostMetaEditor extends StatelessWidget {
|
||||||
final PostWriteController controller;
|
final PostWriteController controller;
|
||||||
|
|
||||||
const PostMetaEditor({super.key, required this.controller});
|
const PostMetaEditor({super.key, required this.controller});
|
||||||
|
|
||||||
Future<DateTime?> _selectDate(
|
Future<DateTime?> _selectDate(
|
||||||
@ -87,28 +88,6 @@ class PostMetaEditor extends StatelessWidget {
|
|||||||
padding: EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom + 8),
|
padding: EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom + 8),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
TextField(
|
|
||||||
controller: controller.titleController,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'fieldPostTitle'.tr(),
|
|
||||||
border: UnderlineInputBorder(),
|
|
||||||
),
|
|
||||||
onTapOutside: (_) =>
|
|
||||||
FocusManager.instance.primaryFocus?.unfocus(),
|
|
||||||
).padding(horizontal: 24),
|
|
||||||
if (controller.mode == 'articles') const Gap(4),
|
|
||||||
if (controller.mode == 'articles')
|
|
||||||
TextField(
|
|
||||||
controller: controller.descriptionController,
|
|
||||||
maxLines: null,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'fieldPostDescription'.tr(),
|
|
||||||
border: UnderlineInputBorder(),
|
|
||||||
),
|
|
||||||
onTapOutside: (_) =>
|
|
||||||
FocusManager.instance.primaryFocus?.unfocus(),
|
|
||||||
).padding(horizontal: 24),
|
|
||||||
const Gap(4),
|
|
||||||
PostTagsField(
|
PostTagsField(
|
||||||
initialTags: controller.tags,
|
initialTags: controller.tags,
|
||||||
labelText: 'fieldPostTags'.tr(),
|
labelText: 'fieldPostTags'.tr(),
|
||||||
@ -133,8 +112,7 @@ class PostMetaEditor extends StatelessWidget {
|
|||||||
helperMaxLines: 2,
|
helperMaxLines: 2,
|
||||||
border: UnderlineInputBorder(),
|
border: UnderlineInputBorder(),
|
||||||
),
|
),
|
||||||
onTapOutside: (_) =>
|
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
FocusManager.instance.primaryFocus?.unfocus(),
|
|
||||||
).padding(horizontal: 24),
|
).padding(horizontal: 24),
|
||||||
const Gap(12),
|
const Gap(12),
|
||||||
ListTile(
|
ListTile(
|
||||||
@ -182,8 +160,7 @@ class PostMetaEditor extends StatelessWidget {
|
|||||||
leading: Icon(Symbols.person),
|
leading: Icon(Symbols.person),
|
||||||
trailing: Icon(Symbols.chevron_right),
|
trailing: Icon(Symbols.chevron_right),
|
||||||
title: Text('postVisibleUsers').tr(),
|
title: Text('postVisibleUsers').tr(),
|
||||||
subtitle: Text('postSelectedUsers')
|
subtitle: Text('postSelectedUsers').plural(controller.visibleUsers.length),
|
||||||
.plural(controller.visibleUsers.length),
|
|
||||||
onTap: () {
|
onTap: () {
|
||||||
_selectVisibleUser(context);
|
_selectVisibleUser(context);
|
||||||
},
|
},
|
||||||
@ -194,8 +171,7 @@ class PostMetaEditor extends StatelessWidget {
|
|||||||
leading: Icon(Symbols.person),
|
leading: Icon(Symbols.person),
|
||||||
trailing: Icon(Symbols.chevron_right),
|
trailing: Icon(Symbols.chevron_right),
|
||||||
title: Text('postInvisibleUsers').tr(),
|
title: Text('postInvisibleUsers').tr(),
|
||||||
subtitle: Text('postSelectedUsers')
|
subtitle: Text('postSelectedUsers').plural(controller.invisibleUsers.length),
|
||||||
.plural(controller.invisibleUsers.length),
|
|
||||||
onTap: () {
|
onTap: () {
|
||||||
_selectInvisibleUser(context);
|
_selectInvisibleUser(context);
|
||||||
},
|
},
|
||||||
@ -204,9 +180,7 @@ class PostMetaEditor extends StatelessWidget {
|
|||||||
leading: const Icon(Symbols.event_available),
|
leading: const Icon(Symbols.event_available),
|
||||||
title: Text('postPublishedAt').tr(),
|
title: Text('postPublishedAt').tr(),
|
||||||
subtitle: Text(
|
subtitle: Text(
|
||||||
controller.publishedAt != null
|
controller.publishedAt != null ? dateFormatter.format(controller.publishedAt!) : 'unset'.tr(),
|
||||||
? dateFormatter.format(controller.publishedAt!)
|
|
||||||
: 'unset'.tr(),
|
|
||||||
),
|
),
|
||||||
trailing: controller.publishedAt != null
|
trailing: controller.publishedAt != null
|
||||||
? IconButton(
|
? IconButton(
|
||||||
@ -230,9 +204,7 @@ class PostMetaEditor extends StatelessWidget {
|
|||||||
leading: const Icon(Symbols.event_busy),
|
leading: const Icon(Symbols.event_busy),
|
||||||
title: Text('postPublishedUntil').tr(),
|
title: Text('postPublishedUntil').tr(),
|
||||||
subtitle: Text(
|
subtitle: Text(
|
||||||
controller.publishedUntil != null
|
controller.publishedUntil != null ? dateFormatter.format(controller.publishedUntil!) : 'unset'.tr(),
|
||||||
? dateFormatter.format(controller.publishedUntil!)
|
|
||||||
: 'unset'.tr(),
|
|
||||||
),
|
),
|
||||||
trailing: controller.publishedUntil != null
|
trailing: controller.publishedUntil != null
|
||||||
? IconButton(
|
? IconButton(
|
||||||
|
138
lib/widgets/post/post_poll.dart
Normal file
138
lib/widgets/post/post_poll.dart
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
import 'package:surface/providers/sn_network.dart';
|
||||||
|
import 'package:surface/providers/userinfo.dart';
|
||||||
|
import 'package:surface/types/poll.dart';
|
||||||
|
import 'package:surface/widgets/dialog.dart';
|
||||||
|
|
||||||
|
class PostPoll extends StatefulWidget {
|
||||||
|
final SnPoll poll;
|
||||||
|
|
||||||
|
const PostPoll({super.key, required this.poll});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<PostPoll> createState() => _PostPollState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PostPollState extends State<PostPoll> {
|
||||||
|
bool _isBusy = false;
|
||||||
|
late SnPoll _poll;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
_poll = widget.poll;
|
||||||
|
_fetchAnswer();
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
String? _answeredChoice;
|
||||||
|
|
||||||
|
Future<void> _refreshPoll() async {
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
final resp = await sn.client.get('/cgi/co/polls/${widget.poll.id}');
|
||||||
|
if (!mounted) return;
|
||||||
|
setState(() => _poll = SnPoll.fromJson(resp.data!));
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _fetchAnswer() async {
|
||||||
|
final ua = context.read<UserProvider>();
|
||||||
|
if (!ua.isAuthorized) return;
|
||||||
|
try {
|
||||||
|
setState(() => _isBusy = true);
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
final resp =
|
||||||
|
await sn.client.get('/cgi/co/polls/${widget.poll.id}/answer');
|
||||||
|
_answeredChoice = resp.data?['answer'];
|
||||||
|
if (!mounted) return;
|
||||||
|
setState(() {});
|
||||||
|
} catch (err) {
|
||||||
|
if (!mounted) return;
|
||||||
|
// ignore because it may not found
|
||||||
|
} finally {
|
||||||
|
setState(() => _isBusy = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _voteForOption(SnPollOption option) async {
|
||||||
|
final ua = context.read<UserProvider>();
|
||||||
|
if (!ua.isAuthorized) return;
|
||||||
|
try {
|
||||||
|
setState(() => _isBusy = true);
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
await sn.client.post('/cgi/co/polls/${widget.poll.id}/answer', data: {
|
||||||
|
'answer': option.id,
|
||||||
|
});
|
||||||
|
if (!mounted) return;
|
||||||
|
HapticFeedback.heavyImpact();
|
||||||
|
_answeredChoice = option.id;
|
||||||
|
_refreshPoll();
|
||||||
|
} catch (err) {
|
||||||
|
if (!mounted) return;
|
||||||
|
context.showErrorDialog(err);
|
||||||
|
} finally {
|
||||||
|
setState(() => _isBusy = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Card(
|
||||||
|
margin: EdgeInsets.zero,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
for (final option in _poll.options)
|
||||||
|
Stack(
|
||||||
|
children: [
|
||||||
|
ClipRRect(
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
|
child: Container(
|
||||||
|
height: 60,
|
||||||
|
width: MediaQuery.of(context).size.width *
|
||||||
|
(_poll.metric.byOptionsPercentage[option.id] ?? 0)
|
||||||
|
.toDouble(),
|
||||||
|
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
minTileHeight: 60,
|
||||||
|
leading: _answeredChoice == option.id
|
||||||
|
? const Icon(Symbols.circle, fill: 1)
|
||||||
|
: const Icon(Symbols.circle),
|
||||||
|
title: Text(option.name),
|
||||||
|
subtitle: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'pollVotes'
|
||||||
|
.plural(_poll.metric.byOptions[option.id] ?? 0),
|
||||||
|
),
|
||||||
|
Text(' · ').padding(horizontal: 4),
|
||||||
|
Text(
|
||||||
|
'${((_poll.metric.byOptionsPercentage[option.id] ?? 0).toDouble() * 100).toStringAsFixed(2)}%',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (option.description.isNotEmpty)
|
||||||
|
Text(option.description),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
onTap: _isBusy ? null : () => _voteForOption(option),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
201
lib/widgets/post/post_poll_editor.dart
Normal file
201
lib/widgets/post/post_poll_editor.dart
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:surface/providers/sn_network.dart';
|
||||||
|
import 'package:surface/types/poll.dart';
|
||||||
|
import 'package:surface/widgets/dialog.dart';
|
||||||
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
|
class PollEditorDialog extends StatefulWidget {
|
||||||
|
final SnPoll? poll;
|
||||||
|
|
||||||
|
const PollEditorDialog({super.key, this.poll});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<PollEditorDialog> createState() => _PollEditorDialogState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PollEditorDialogState extends State<PollEditorDialog> {
|
||||||
|
final TextEditingController _linkController = TextEditingController();
|
||||||
|
final List<SnPollOption> _pollOptions = List.empty(growable: true);
|
||||||
|
|
||||||
|
bool _isBusy = false;
|
||||||
|
|
||||||
|
Future<void> _fetchPoll() async {
|
||||||
|
if (_linkController.text.isEmpty) return;
|
||||||
|
try {
|
||||||
|
setState(() => _isBusy = true);
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
final resp = await sn.client.get('/cgi/co/polls/${_linkController.text}');
|
||||||
|
final out = SnPoll.fromJson(resp.data);
|
||||||
|
if (!mounted) return;
|
||||||
|
Navigator.pop(context, out);
|
||||||
|
} catch (err) {
|
||||||
|
if (!mounted) return;
|
||||||
|
context.showErrorDialog(err);
|
||||||
|
} finally {
|
||||||
|
setState(() => _isBusy = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _applyPost() async {
|
||||||
|
try {
|
||||||
|
setState(() => _isBusy = true);
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
final resp = widget.poll == null
|
||||||
|
? await sn.client.post('/cgi/co/polls', data: {
|
||||||
|
'options': _pollOptions.where((ele) => ele.name.isNotEmpty).toList(),
|
||||||
|
})
|
||||||
|
: await sn.client.put('/cgi/co/polls/${widget.poll!.id}', data: {
|
||||||
|
'options': _pollOptions.where((ele) => ele.name.isNotEmpty).toList(),
|
||||||
|
});
|
||||||
|
final out = SnPoll.fromJson(resp.data);
|
||||||
|
if (!mounted) return;
|
||||||
|
Navigator.pop(context, out);
|
||||||
|
} catch (err) {
|
||||||
|
if (!mounted) return;
|
||||||
|
context.showErrorDialog(err);
|
||||||
|
} finally {
|
||||||
|
setState(() => _isBusy = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _deletePoll() async {
|
||||||
|
final confirm = await context.showConfirmDialog(
|
||||||
|
'pollEditorDelete'.tr(),
|
||||||
|
'pollEditorDeleteDescription'.tr(),
|
||||||
|
);
|
||||||
|
if (!confirm) return;
|
||||||
|
if (!mounted) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
setState(() => _isBusy = true);
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
await sn.client.delete('/cgi/co/polls/${widget.poll!.id}');
|
||||||
|
if (!mounted) return;
|
||||||
|
Navigator.pop(context, false);
|
||||||
|
} catch (err) {
|
||||||
|
if (!mounted) return;
|
||||||
|
context.showErrorDialog(err);
|
||||||
|
} finally {
|
||||||
|
setState(() => _isBusy = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_pollOptions.addAll(widget.poll?.options ?? []);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_linkController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: Text(widget.poll == null ? 'pollEditorNew' : 'pollEditorEdit').tr(),
|
||||||
|
content: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
spacing: 16,
|
||||||
|
children: [
|
||||||
|
if (widget.poll == null)
|
||||||
|
TextField(
|
||||||
|
controller: _linkController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
isDense: true,
|
||||||
|
labelText: 'pollLinkExisting'.tr(),
|
||||||
|
prefixText: '#',
|
||||||
|
suffixIcon: IconButton(
|
||||||
|
visualDensity: const VisualDensity(horizontal: -4, vertical: -4),
|
||||||
|
constraints: const BoxConstraints(),
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
onPressed: _isBusy ? null : () => _fetchPoll(),
|
||||||
|
icon: const Icon(Icons.keyboard_arrow_right),
|
||||||
|
),
|
||||||
|
border: const OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
|
),
|
||||||
|
Card(
|
||||||
|
margin: EdgeInsets.zero,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
for (int i = 0; i < _pollOptions.length; i++)
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Symbols.circle),
|
||||||
|
title: TextFormField(
|
||||||
|
decoration: InputDecoration.collapsed(
|
||||||
|
hintText: 'pollOptionName'.tr(),
|
||||||
|
),
|
||||||
|
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
|
initialValue: _pollOptions[i].name,
|
||||||
|
onChanged: (value) {
|
||||||
|
// Looks like we don't need set state here cuz it got internal updated.
|
||||||
|
_pollOptions[i] = _pollOptions[i].copyWith(name: value);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
trailing: IconButton(
|
||||||
|
visualDensity: const VisualDensity(horizontal: -4, vertical: -4),
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
constraints: const BoxConstraints(),
|
||||||
|
onPressed: () {
|
||||||
|
setState(() => _pollOptions.removeAt(i));
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.close),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Symbols.add),
|
||||||
|
title: Text('pollOptionAdd').tr(),
|
||||||
|
onTap: () {
|
||||||
|
setState(
|
||||||
|
() => _pollOptions.add(
|
||||||
|
SnPollOption(id: const Uuid().v4(), icon: '', name: '', description: ''),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (widget.poll != null)
|
||||||
|
Card(
|
||||||
|
margin: EdgeInsets.zero,
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Symbols.delete),
|
||||||
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
|
title: Text('pollEditorDelete').tr(),
|
||||||
|
onTap: _isBusy ? null : () => _deletePoll(),
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Symbols.link_off),
|
||||||
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
|
title: Text('pollEditorUnlink').tr(),
|
||||||
|
onTap: _isBusy ? null : () => Navigator.pop(context, false),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: _isBusy ? null : () => Navigator.pop(context),
|
||||||
|
child: Text('cancel'.tr()),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: _isBusy ? null : () => _applyPost(),
|
||||||
|
child: Text('dialogConfirm'.tr()),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -11,9 +11,11 @@
|
|||||||
#include <file_selector_linux/file_selector_plugin.h>
|
#include <file_selector_linux/file_selector_plugin.h>
|
||||||
#include <flutter_udid/flutter_udid_plugin.h>
|
#include <flutter_udid/flutter_udid_plugin.h>
|
||||||
#include <flutter_webrtc/flutter_web_r_t_c_plugin.h>
|
#include <flutter_webrtc/flutter_web_r_t_c_plugin.h>
|
||||||
|
#include <hotkey_manager_linux/hotkey_manager_linux_plugin.h>
|
||||||
#include <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 <tray_manager/tray_manager_plugin.h>
|
||||||
#include <url_launcher_linux/url_launcher_plugin.h>
|
#include <url_launcher_linux/url_launcher_plugin.h>
|
||||||
|
|
||||||
void fl_register_plugins(FlPluginRegistry* registry) {
|
void fl_register_plugins(FlPluginRegistry* registry) {
|
||||||
@ -32,6 +34,9 @@ void fl_register_plugins(FlPluginRegistry* registry) {
|
|||||||
g_autoptr(FlPluginRegistrar) flutter_webrtc_registrar =
|
g_autoptr(FlPluginRegistrar) flutter_webrtc_registrar =
|
||||||
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterWebRTCPlugin");
|
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterWebRTCPlugin");
|
||||||
flutter_web_r_t_c_plugin_register_with_registrar(flutter_webrtc_registrar);
|
flutter_web_r_t_c_plugin_register_with_registrar(flutter_webrtc_registrar);
|
||||||
|
g_autoptr(FlPluginRegistrar) hotkey_manager_linux_registrar =
|
||||||
|
fl_plugin_registry_get_registrar_for_plugin(registry, "HotkeyManagerLinuxPlugin");
|
||||||
|
hotkey_manager_linux_plugin_register_with_registrar(hotkey_manager_linux_registrar);
|
||||||
g_autoptr(FlPluginRegistrar) media_kit_libs_linux_registrar =
|
g_autoptr(FlPluginRegistrar) media_kit_libs_linux_registrar =
|
||||||
fl_plugin_registry_get_registrar_for_plugin(registry, "MediaKitLibsLinuxPlugin");
|
fl_plugin_registry_get_registrar_for_plugin(registry, "MediaKitLibsLinuxPlugin");
|
||||||
media_kit_libs_linux_plugin_register_with_registrar(media_kit_libs_linux_registrar);
|
media_kit_libs_linux_plugin_register_with_registrar(media_kit_libs_linux_registrar);
|
||||||
@ -41,6 +46,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) tray_manager_registrar =
|
||||||
|
fl_plugin_registry_get_registrar_for_plugin(registry, "TrayManagerPlugin");
|
||||||
|
tray_manager_plugin_register_with_registrar(tray_manager_registrar);
|
||||||
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
|
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
|
||||||
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
|
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
|
||||||
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
|
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
|
||||||
|
@ -8,9 +8,11 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
|||||||
file_selector_linux
|
file_selector_linux
|
||||||
flutter_udid
|
flutter_udid
|
||||||
flutter_webrtc
|
flutter_webrtc
|
||||||
|
hotkey_manager_linux
|
||||||
media_kit_libs_linux
|
media_kit_libs_linux
|
||||||
media_kit_video
|
media_kit_video
|
||||||
pasteboard
|
pasteboard
|
||||||
|
tray_manager
|
||||||
url_launcher_linux
|
url_launcher_linux
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
#include "my_application.h"
|
|
||||||
|
|
||||||
#include <bitsdojo_window_linux/bitsdojo_window_plugin.h>
|
#include <bitsdojo_window_linux/bitsdojo_window_plugin.h>
|
||||||
|
#include "my_application.h"
|
||||||
|
|
||||||
#include <flutter_linux/flutter_linux.h>
|
#include <flutter_linux/flutter_linux.h>
|
||||||
#ifdef GDK_WINDOWING_X11
|
#ifdef GDK_WINDOWING_X11
|
||||||
@ -42,15 +41,16 @@ static void my_application_activate(GApplication* application) {
|
|||||||
if (use_header_bar) {
|
if (use_header_bar) {
|
||||||
GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new());
|
GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new());
|
||||||
gtk_widget_show(GTK_WIDGET(header_bar));
|
gtk_widget_show(GTK_WIDGET(header_bar));
|
||||||
gtk_header_bar_set_title(header_bar, "Surface");
|
gtk_header_bar_set_title(header_bar, "bitsdojo_window_example");
|
||||||
gtk_header_bar_set_show_close_button(header_bar, TRUE);
|
gtk_header_bar_set_show_close_button(header_bar, TRUE);
|
||||||
gtk_window_set_titlebar(window, GTK_WIDGET(header_bar));
|
gtk_window_set_titlebar(window, GTK_WIDGET(header_bar));
|
||||||
} else {
|
} else {
|
||||||
gtk_window_set_title(window, "Surface");
|
gtk_window_set_title(window, "bitsdojo_window_example");
|
||||||
}
|
}
|
||||||
|
|
||||||
auto bdw = bitsdojo_window_from(window);
|
auto bdw = bitsdojo_window_from(window);
|
||||||
bdw->setCustomFrame(true);
|
bdw->setCustomFrame(true);
|
||||||
|
//gtk_window_set_default_size(window, 1280, 720);
|
||||||
gtk_widget_show(GTK_WIDGET(window));
|
gtk_widget_show(GTK_WIDGET(window));
|
||||||
|
|
||||||
g_autoptr(FlDartProject) project = fl_dart_project_new();
|
g_autoptr(FlDartProject) project = fl_dart_project_new();
|
||||||
@ -84,24 +84,6 @@ static gboolean my_application_local_command_line(GApplication* application, gch
|
|||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implements GApplication::startup.
|
|
||||||
static void my_application_startup(GApplication* application) {
|
|
||||||
//MyApplication* self = MY_APPLICATION(object);
|
|
||||||
|
|
||||||
// Perform any actions required at application startup.
|
|
||||||
|
|
||||||
G_APPLICATION_CLASS(my_application_parent_class)->startup(application);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Implements GApplication::shutdown.
|
|
||||||
static void my_application_shutdown(GApplication* application) {
|
|
||||||
//MyApplication* self = MY_APPLICATION(object);
|
|
||||||
|
|
||||||
// Perform any actions required at application shutdown.
|
|
||||||
|
|
||||||
G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Implements GObject::dispose.
|
// Implements GObject::dispose.
|
||||||
static void my_application_dispose(GObject* object) {
|
static void my_application_dispose(GObject* object) {
|
||||||
MyApplication* self = MY_APPLICATION(object);
|
MyApplication* self = MY_APPLICATION(object);
|
||||||
@ -112,8 +94,6 @@ static void my_application_dispose(GObject* object) {
|
|||||||
static void my_application_class_init(MyApplicationClass* klass) {
|
static void my_application_class_init(MyApplicationClass* klass) {
|
||||||
G_APPLICATION_CLASS(klass)->activate = my_application_activate;
|
G_APPLICATION_CLASS(klass)->activate = my_application_activate;
|
||||||
G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line;
|
G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line;
|
||||||
G_APPLICATION_CLASS(klass)->startup = my_application_startup;
|
|
||||||
G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown;
|
|
||||||
G_OBJECT_CLASS(klass)->dispose = my_application_dispose;
|
G_OBJECT_CLASS(klass)->dispose = my_application_dispose;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@ import flutter_inappwebview_macos
|
|||||||
import flutter_udid
|
import flutter_udid
|
||||||
import flutter_webrtc
|
import flutter_webrtc
|
||||||
import gal
|
import gal
|
||||||
|
import hotkey_manager_macos
|
||||||
import in_app_review
|
import in_app_review
|
||||||
import livekit_client
|
import livekit_client
|
||||||
import media_kit_libs_macos_video
|
import media_kit_libs_macos_video
|
||||||
@ -29,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 tray_manager
|
||||||
import url_launcher_macos
|
import url_launcher_macos
|
||||||
import video_compress
|
import video_compress
|
||||||
import wakelock_plus
|
import wakelock_plus
|
||||||
@ -47,6 +49,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
|||||||
FlutterUdidPlugin.register(with: registry.registrar(forPlugin: "FlutterUdidPlugin"))
|
FlutterUdidPlugin.register(with: registry.registrar(forPlugin: "FlutterUdidPlugin"))
|
||||||
FlutterWebRTCPlugin.register(with: registry.registrar(forPlugin: "FlutterWebRTCPlugin"))
|
FlutterWebRTCPlugin.register(with: registry.registrar(forPlugin: "FlutterWebRTCPlugin"))
|
||||||
GalPlugin.register(with: registry.registrar(forPlugin: "GalPlugin"))
|
GalPlugin.register(with: registry.registrar(forPlugin: "GalPlugin"))
|
||||||
|
HotkeyManagerMacosPlugin.register(with: registry.registrar(forPlugin: "HotkeyManagerMacosPlugin"))
|
||||||
InAppReviewPlugin.register(with: registry.registrar(forPlugin: "InAppReviewPlugin"))
|
InAppReviewPlugin.register(with: registry.registrar(forPlugin: "InAppReviewPlugin"))
|
||||||
LiveKitPlugin.register(with: registry.registrar(forPlugin: "LiveKitPlugin"))
|
LiveKitPlugin.register(with: registry.registrar(forPlugin: "LiveKitPlugin"))
|
||||||
MediaKitLibsMacosVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitLibsMacosVideoPlugin"))
|
MediaKitLibsMacosVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitLibsMacosVideoPlugin"))
|
||||||
@ -58,6 +61,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"))
|
||||||
|
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"))
|
||||||
WakelockPlusMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockPlusMacosPlugin"))
|
WakelockPlusMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockPlusMacosPlugin"))
|
||||||
|
@ -2,7 +2,6 @@ PODS:
|
|||||||
- bitsdojo_window_macos (0.0.1):
|
- bitsdojo_window_macos (0.0.1):
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- connectivity_plus (0.0.1):
|
- connectivity_plus (0.0.1):
|
||||||
- Flutter
|
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- croppy (0.0.1):
|
- croppy (0.0.1):
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
@ -14,59 +13,59 @@ PODS:
|
|||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- file_selector_macos (0.0.1):
|
- file_selector_macos (0.0.1):
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- Firebase/Analytics (11.6.0):
|
- Firebase/Analytics (11.7.0):
|
||||||
- Firebase/Core
|
- Firebase/Core
|
||||||
- Firebase/Core (11.6.0):
|
- Firebase/Core (11.7.0):
|
||||||
- Firebase/CoreOnly
|
- Firebase/CoreOnly
|
||||||
- FirebaseAnalytics (~> 11.6.0)
|
- FirebaseAnalytics (~> 11.7.0)
|
||||||
- Firebase/CoreOnly (11.6.0):
|
- Firebase/CoreOnly (11.7.0):
|
||||||
- FirebaseCore (~> 11.6.0)
|
- FirebaseCore (~> 11.7.0)
|
||||||
- Firebase/Messaging (11.6.0):
|
- Firebase/Messaging (11.7.0):
|
||||||
- Firebase/CoreOnly
|
- Firebase/CoreOnly
|
||||||
- FirebaseMessaging (~> 11.6.0)
|
- FirebaseMessaging (~> 11.7.0)
|
||||||
- firebase_analytics (11.4.1):
|
- firebase_analytics (11.4.2):
|
||||||
- Firebase/Analytics (= 11.6.0)
|
- Firebase/Analytics (= 11.7.0)
|
||||||
- firebase_core
|
- firebase_core
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- firebase_core (3.10.1):
|
- firebase_core (3.11.0):
|
||||||
- Firebase/CoreOnly (~> 11.6.0)
|
- Firebase/CoreOnly (~> 11.7.0)
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- firebase_messaging (15.2.1):
|
- firebase_messaging (15.2.2):
|
||||||
- Firebase/CoreOnly (~> 11.6.0)
|
- Firebase/CoreOnly (~> 11.7.0)
|
||||||
- Firebase/Messaging (~> 11.6.0)
|
- Firebase/Messaging (~> 11.7.0)
|
||||||
- firebase_core
|
- firebase_core
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- FirebaseAnalytics (11.6.0):
|
- FirebaseAnalytics (11.7.0):
|
||||||
- FirebaseAnalytics/AdIdSupport (= 11.6.0)
|
- FirebaseAnalytics/AdIdSupport (= 11.7.0)
|
||||||
- FirebaseCore (~> 11.6.0)
|
- FirebaseCore (~> 11.7.0)
|
||||||
- FirebaseInstallations (~> 11.0)
|
- FirebaseInstallations (~> 11.0)
|
||||||
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
|
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
|
||||||
- GoogleUtilities/MethodSwizzler (~> 8.0)
|
- GoogleUtilities/MethodSwizzler (~> 8.0)
|
||||||
- GoogleUtilities/Network (~> 8.0)
|
- GoogleUtilities/Network (~> 8.0)
|
||||||
- "GoogleUtilities/NSData+zlib (~> 8.0)"
|
- "GoogleUtilities/NSData+zlib (~> 8.0)"
|
||||||
- nanopb (~> 3.30910.0)
|
- nanopb (~> 3.30910.0)
|
||||||
- FirebaseAnalytics/AdIdSupport (11.6.0):
|
- FirebaseAnalytics/AdIdSupport (11.7.0):
|
||||||
- FirebaseCore (~> 11.6.0)
|
- FirebaseCore (~> 11.7.0)
|
||||||
- FirebaseInstallations (~> 11.0)
|
- FirebaseInstallations (~> 11.0)
|
||||||
- GoogleAppMeasurement (= 11.6.0)
|
- GoogleAppMeasurement (= 11.7.0)
|
||||||
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
|
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
|
||||||
- GoogleUtilities/MethodSwizzler (~> 8.0)
|
- GoogleUtilities/MethodSwizzler (~> 8.0)
|
||||||
- GoogleUtilities/Network (~> 8.0)
|
- GoogleUtilities/Network (~> 8.0)
|
||||||
- "GoogleUtilities/NSData+zlib (~> 8.0)"
|
- "GoogleUtilities/NSData+zlib (~> 8.0)"
|
||||||
- nanopb (~> 3.30910.0)
|
- nanopb (~> 3.30910.0)
|
||||||
- FirebaseCore (11.6.0):
|
- FirebaseCore (11.7.0):
|
||||||
- FirebaseCoreInternal (~> 11.6.0)
|
- FirebaseCoreInternal (~> 11.7.0)
|
||||||
- GoogleUtilities/Environment (~> 8.0)
|
- GoogleUtilities/Environment (~> 8.0)
|
||||||
- GoogleUtilities/Logger (~> 8.0)
|
- GoogleUtilities/Logger (~> 8.0)
|
||||||
- FirebaseCoreInternal (11.6.0):
|
- FirebaseCoreInternal (11.7.0):
|
||||||
- "GoogleUtilities/NSData+zlib (~> 8.0)"
|
- "GoogleUtilities/NSData+zlib (~> 8.0)"
|
||||||
- FirebaseInstallations (11.6.0):
|
- FirebaseInstallations (11.7.0):
|
||||||
- FirebaseCore (~> 11.6.0)
|
- FirebaseCore (~> 11.7.0)
|
||||||
- GoogleUtilities/Environment (~> 8.0)
|
- GoogleUtilities/Environment (~> 8.0)
|
||||||
- GoogleUtilities/UserDefaults (~> 8.0)
|
- GoogleUtilities/UserDefaults (~> 8.0)
|
||||||
- PromisesObjC (~> 2.4)
|
- PromisesObjC (~> 2.4)
|
||||||
- FirebaseMessaging (11.6.0):
|
- FirebaseMessaging (11.7.0):
|
||||||
- FirebaseCore (~> 11.6.0)
|
- FirebaseCore (~> 11.7.0)
|
||||||
- FirebaseInstallations (~> 11.0)
|
- FirebaseInstallations (~> 11.0)
|
||||||
- GoogleDataTransport (~> 10.0)
|
- GoogleDataTransport (~> 10.0)
|
||||||
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
|
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
|
||||||
@ -87,21 +86,21 @@ PODS:
|
|||||||
- gal (1.0.0):
|
- gal (1.0.0):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- GoogleAppMeasurement (11.6.0):
|
- GoogleAppMeasurement (11.7.0):
|
||||||
- GoogleAppMeasurement/AdIdSupport (= 11.6.0)
|
- GoogleAppMeasurement/AdIdSupport (= 11.7.0)
|
||||||
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
|
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
|
||||||
- GoogleUtilities/MethodSwizzler (~> 8.0)
|
- GoogleUtilities/MethodSwizzler (~> 8.0)
|
||||||
- GoogleUtilities/Network (~> 8.0)
|
- GoogleUtilities/Network (~> 8.0)
|
||||||
- "GoogleUtilities/NSData+zlib (~> 8.0)"
|
- "GoogleUtilities/NSData+zlib (~> 8.0)"
|
||||||
- nanopb (~> 3.30910.0)
|
- nanopb (~> 3.30910.0)
|
||||||
- GoogleAppMeasurement/AdIdSupport (11.6.0):
|
- GoogleAppMeasurement/AdIdSupport (11.7.0):
|
||||||
- GoogleAppMeasurement/WithoutAdIdSupport (= 11.6.0)
|
- GoogleAppMeasurement/WithoutAdIdSupport (= 11.7.0)
|
||||||
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
|
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
|
||||||
- GoogleUtilities/MethodSwizzler (~> 8.0)
|
- GoogleUtilities/MethodSwizzler (~> 8.0)
|
||||||
- GoogleUtilities/Network (~> 8.0)
|
- GoogleUtilities/Network (~> 8.0)
|
||||||
- "GoogleUtilities/NSData+zlib (~> 8.0)"
|
- "GoogleUtilities/NSData+zlib (~> 8.0)"
|
||||||
- nanopb (~> 3.30910.0)
|
- nanopb (~> 3.30910.0)
|
||||||
- GoogleAppMeasurement/WithoutAdIdSupport (11.6.0):
|
- GoogleAppMeasurement/WithoutAdIdSupport (11.7.0):
|
||||||
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
|
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
|
||||||
- GoogleUtilities/MethodSwizzler (~> 8.0)
|
- GoogleUtilities/MethodSwizzler (~> 8.0)
|
||||||
- GoogleUtilities/Network (~> 8.0)
|
- GoogleUtilities/Network (~> 8.0)
|
||||||
@ -137,9 +136,13 @@ PODS:
|
|||||||
- GoogleUtilities/UserDefaults (8.0.2):
|
- GoogleUtilities/UserDefaults (8.0.2):
|
||||||
- GoogleUtilities/Logger
|
- GoogleUtilities/Logger
|
||||||
- GoogleUtilities/Privacy
|
- GoogleUtilities/Privacy
|
||||||
|
- HotKey (0.2.1)
|
||||||
|
- hotkey_manager_macos (0.0.1):
|
||||||
|
- FlutterMacOS
|
||||||
|
- HotKey
|
||||||
- in_app_review (2.0.0):
|
- in_app_review (2.0.0):
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- livekit_client (2.3.5):
|
- livekit_client (2.3.6):
|
||||||
- flutter_webrtc
|
- flutter_webrtc
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- WebRTC-SDK (= 125.6422.06)
|
- WebRTC-SDK (= 125.6422.06)
|
||||||
@ -174,6 +177,8 @@ PODS:
|
|||||||
- sqflite_darwin (0.0.4):
|
- sqflite_darwin (0.0.4):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
|
- tray_manager (0.0.1):
|
||||||
|
- FlutterMacOS
|
||||||
- url_launcher_macos (0.0.1):
|
- url_launcher_macos (0.0.1):
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- video_compress (0.3.0):
|
- video_compress (0.3.0):
|
||||||
@ -184,7 +189,7 @@ PODS:
|
|||||||
|
|
||||||
DEPENDENCIES:
|
DEPENDENCIES:
|
||||||
- bitsdojo_window_macos (from `Flutter/ephemeral/.symlinks/plugins/bitsdojo_window_macos/macos`)
|
- bitsdojo_window_macos (from `Flutter/ephemeral/.symlinks/plugins/bitsdojo_window_macos/macos`)
|
||||||
- connectivity_plus (from `Flutter/ephemeral/.symlinks/plugins/connectivity_plus/darwin`)
|
- connectivity_plus (from `Flutter/ephemeral/.symlinks/plugins/connectivity_plus/macos`)
|
||||||
- croppy (from `Flutter/ephemeral/.symlinks/plugins/croppy/macos`)
|
- croppy (from `Flutter/ephemeral/.symlinks/plugins/croppy/macos`)
|
||||||
- device_info_plus (from `Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos`)
|
- device_info_plus (from `Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos`)
|
||||||
- file_picker (from `Flutter/ephemeral/.symlinks/plugins/file_picker/macos`)
|
- file_picker (from `Flutter/ephemeral/.symlinks/plugins/file_picker/macos`)
|
||||||
@ -198,6 +203,7 @@ DEPENDENCIES:
|
|||||||
- flutter_webrtc (from `Flutter/ephemeral/.symlinks/plugins/flutter_webrtc/macos`)
|
- flutter_webrtc (from `Flutter/ephemeral/.symlinks/plugins/flutter_webrtc/macos`)
|
||||||
- FlutterMacOS (from `Flutter/ephemeral`)
|
- FlutterMacOS (from `Flutter/ephemeral`)
|
||||||
- gal (from `Flutter/ephemeral/.symlinks/plugins/gal/darwin`)
|
- gal (from `Flutter/ephemeral/.symlinks/plugins/gal/darwin`)
|
||||||
|
- hotkey_manager_macos (from `Flutter/ephemeral/.symlinks/plugins/hotkey_manager_macos/macos`)
|
||||||
- in_app_review (from `Flutter/ephemeral/.symlinks/plugins/in_app_review/macos`)
|
- in_app_review (from `Flutter/ephemeral/.symlinks/plugins/in_app_review/macos`)
|
||||||
- livekit_client (from `Flutter/ephemeral/.symlinks/plugins/livekit_client/macos`)
|
- livekit_client (from `Flutter/ephemeral/.symlinks/plugins/livekit_client/macos`)
|
||||||
- media_kit_libs_macos_video (from `Flutter/ephemeral/.symlinks/plugins/media_kit_libs_macos_video/macos`)
|
- media_kit_libs_macos_video (from `Flutter/ephemeral/.symlinks/plugins/media_kit_libs_macos_video/macos`)
|
||||||
@ -210,6 +216,7 @@ DEPENDENCIES:
|
|||||||
- share_plus (from `Flutter/ephemeral/.symlinks/plugins/share_plus/macos`)
|
- share_plus (from `Flutter/ephemeral/.symlinks/plugins/share_plus/macos`)
|
||||||
- shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`)
|
- shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||||
- sqflite_darwin (from `Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin`)
|
- sqflite_darwin (from `Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin`)
|
||||||
|
- tray_manager (from `Flutter/ephemeral/.symlinks/plugins/tray_manager/macos`)
|
||||||
- url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
|
- url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
|
||||||
- video_compress (from `Flutter/ephemeral/.symlinks/plugins/video_compress/macos`)
|
- video_compress (from `Flutter/ephemeral/.symlinks/plugins/video_compress/macos`)
|
||||||
- wakelock_plus (from `Flutter/ephemeral/.symlinks/plugins/wakelock_plus/macos`)
|
- wakelock_plus (from `Flutter/ephemeral/.symlinks/plugins/wakelock_plus/macos`)
|
||||||
@ -225,6 +232,7 @@ SPEC REPOS:
|
|||||||
- GoogleAppMeasurement
|
- GoogleAppMeasurement
|
||||||
- GoogleDataTransport
|
- GoogleDataTransport
|
||||||
- GoogleUtilities
|
- GoogleUtilities
|
||||||
|
- HotKey
|
||||||
- nanopb
|
- nanopb
|
||||||
- OrderedSet
|
- OrderedSet
|
||||||
- PromisesObjC
|
- PromisesObjC
|
||||||
@ -235,7 +243,7 @@ EXTERNAL SOURCES:
|
|||||||
bitsdojo_window_macos:
|
bitsdojo_window_macos:
|
||||||
:path: Flutter/ephemeral/.symlinks/plugins/bitsdojo_window_macos/macos
|
:path: Flutter/ephemeral/.symlinks/plugins/bitsdojo_window_macos/macos
|
||||||
connectivity_plus:
|
connectivity_plus:
|
||||||
:path: Flutter/ephemeral/.symlinks/plugins/connectivity_plus/darwin
|
:path: Flutter/ephemeral/.symlinks/plugins/connectivity_plus/macos
|
||||||
croppy:
|
croppy:
|
||||||
:path: Flutter/ephemeral/.symlinks/plugins/croppy/macos
|
:path: Flutter/ephemeral/.symlinks/plugins/croppy/macos
|
||||||
device_info_plus:
|
device_info_plus:
|
||||||
@ -262,6 +270,8 @@ EXTERNAL SOURCES:
|
|||||||
:path: Flutter/ephemeral
|
:path: Flutter/ephemeral
|
||||||
gal:
|
gal:
|
||||||
:path: Flutter/ephemeral/.symlinks/plugins/gal/darwin
|
:path: Flutter/ephemeral/.symlinks/plugins/gal/darwin
|
||||||
|
hotkey_manager_macos:
|
||||||
|
:path: Flutter/ephemeral/.symlinks/plugins/hotkey_manager_macos/macos
|
||||||
in_app_review:
|
in_app_review:
|
||||||
:path: Flutter/ephemeral/.symlinks/plugins/in_app_review/macos
|
:path: Flutter/ephemeral/.symlinks/plugins/in_app_review/macos
|
||||||
livekit_client:
|
livekit_client:
|
||||||
@ -286,6 +296,8 @@ EXTERNAL SOURCES:
|
|||||||
:path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin
|
:path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin
|
||||||
sqflite_darwin:
|
sqflite_darwin:
|
||||||
:path: Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin
|
:path: Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin
|
||||||
|
tray_manager:
|
||||||
|
:path: Flutter/ephemeral/.symlinks/plugins/tray_manager/macos
|
||||||
url_launcher_macos:
|
url_launcher_macos:
|
||||||
:path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos
|
:path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos
|
||||||
video_compress:
|
video_compress:
|
||||||
@ -295,31 +307,33 @@ EXTERNAL SOURCES:
|
|||||||
|
|
||||||
SPEC CHECKSUMS:
|
SPEC CHECKSUMS:
|
||||||
bitsdojo_window_macos: 44e3b8fe3dd463820e0321f6256c5b1c16bb6a00
|
bitsdojo_window_macos: 44e3b8fe3dd463820e0321f6256c5b1c16bb6a00
|
||||||
connectivity_plus: 18382e7311ba19efcaee94442b23b32507b20695
|
connectivity_plus: 0a976dfd033b59192912fa3c6c7b54aab5093802
|
||||||
croppy: 25a638bd7d05411d8c697f481568f261037694fc
|
croppy: 25a638bd7d05411d8c697f481568f261037694fc
|
||||||
device_info_plus: 1b14eed9bf95428983aed283a8d51cce3d8c4215
|
device_info_plus: 1b14eed9bf95428983aed283a8d51cce3d8c4215
|
||||||
file_picker: e716a70a9fe5fd9e09ebc922d7541464289443af
|
file_picker: e716a70a9fe5fd9e09ebc922d7541464289443af
|
||||||
file_saver: 44e6fbf666677faf097302460e214e977fdd977b
|
file_saver: 44e6fbf666677faf097302460e214e977fdd977b
|
||||||
file_selector_macos: cc3858c981fe6889f364731200d6232dac1d812d
|
file_selector_macos: cc3858c981fe6889f364731200d6232dac1d812d
|
||||||
Firebase: 374a441a91ead896215703a674d58cdb3e9d772b
|
Firebase: a64bf6a8546e6eab54f1c715cd6151f39d2329f4
|
||||||
firebase_analytics: 91efc58e8e37964469fdd59ad11ba36bc97e75d6
|
firebase_analytics: 41d88c024a7756462a803e36236ba74f24cdc2c5
|
||||||
firebase_core: 75e003524565fb5bd80c9960bc5892e8475821cd
|
firebase_core: 751d3d919b95d4ae46ab049d0d64d42d4eec086b
|
||||||
firebase_messaging: 082a385eb98b5bb843a566cb30404859c4bd6e25
|
firebase_messaging: cc174f19945e9541e140e3cb0118448e59b38c6c
|
||||||
FirebaseAnalytics: 7114c698cac995602e3b1b96663473e50d54d6e7
|
FirebaseAnalytics: bc9e565af9044ba8d6c6e4157e4edca8e5fdf7ec
|
||||||
FirebaseCore: 48b0dd707581cf9c1a1220da68223fb0a562afaa
|
FirebaseCore: 3227e35f4197a924206fbcdc0349325baf4f5de4
|
||||||
FirebaseCoreInternal: d98ab91e2d80a56d7b246856a8885443b302c0c2
|
FirebaseCoreInternal: d6c17dafc8dc33614733a8b52df78fcb4394c881
|
||||||
FirebaseInstallations: efc0946fc756e4d22d8113f7c761948120322e8c
|
FirebaseInstallations: 9347e719c3d52d8d7b9074b2c32407dd027305e9
|
||||||
FirebaseMessaging: e1aca1fcc23e8b9eddb0e33f375ff90944623021
|
FirebaseMessaging: 00ece041b71ddb52a2862ffdee73fb6e9824bd0c
|
||||||
flutter_inappwebview_macos: bdf207b8f4ebd58e86ae06cd96b147de99a67c9b
|
flutter_inappwebview_macos: bdf207b8f4ebd58e86ae06cd96b147de99a67c9b
|
||||||
flutter_udid: 2e7b3da4b5fdfba86a396b97898f5fe8f4ec1a52
|
flutter_udid: 2e7b3da4b5fdfba86a396b97898f5fe8f4ec1a52
|
||||||
flutter_webrtc: d55fd3f5c75b42940b6b4b2cf376a5797398d1f8
|
flutter_webrtc: d55fd3f5c75b42940b6b4b2cf376a5797398d1f8
|
||||||
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
|
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
|
||||||
gal: 6a522c75909f1244732d4596d11d6a2f86ff37a5
|
gal: 6a522c75909f1244732d4596d11d6a2f86ff37a5
|
||||||
GoogleAppMeasurement: 6a9e6317b6a6d810ad03d4a66564ca6c4c5818a3
|
GoogleAppMeasurement: 0471a5b5bff51f3a91b1e76df22c952d04c63967
|
||||||
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
|
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
|
||||||
GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d
|
GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d
|
||||||
|
HotKey: 400beb7caa29054ea8d864c96f5ba7e5b4852277
|
||||||
|
hotkey_manager_macos: 1e2edb0c7ae4fe67108af44a9d3445de41404160
|
||||||
in_app_review: a6a031b9acd03c7d103e341aa334adf2c493fb93
|
in_app_review: a6a031b9acd03c7d103e341aa334adf2c493fb93
|
||||||
livekit_client: 91c68237edede55f8891a166a28c1daec8a1e4b1
|
livekit_client: 0ad107154753a5a76802d2222c040223ad049499
|
||||||
media_kit_libs_macos_video: b3e2bbec2eef97c285f2b1baa7963c67c753fb82
|
media_kit_libs_macos_video: b3e2bbec2eef97c285f2b1baa7963c67c753fb82
|
||||||
media_kit_native_event_loop: 81fd5b45192b72f8b5b69eaf5b540f45777eb8d5
|
media_kit_native_event_loop: 81fd5b45192b72f8b5b69eaf5b540f45777eb8d5
|
||||||
media_kit_video: c75b07f14d59706c775778e4dd47dd027de8d1e5
|
media_kit_video: c75b07f14d59706c775778e4dd47dd027de8d1e5
|
||||||
@ -334,6 +348,7 @@ SPEC CHECKSUMS:
|
|||||||
share_plus: 1fa619de8392a4398bfaf176d441853922614e89
|
share_plus: 1fa619de8392a4398bfaf176d441853922614e89
|
||||||
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
|
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
|
||||||
sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d
|
sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d
|
||||||
|
tray_manager: 9064e219c56d75c476e46b9a21182087930baf90
|
||||||
url_launcher_macos: c82c93949963e55b228a30115bd219499a6fe404
|
url_launcher_macos: c82c93949963e55b228a30115bd219499a6fe404
|
||||||
video_compress: c896234f100791b5fef7f049afa38f6d2ef7b42f
|
video_compress: c896234f100791b5fef7f049afa38f6d2ef7b42f
|
||||||
wakelock_plus: 4783562c9a43d209c458cb9b30692134af456269
|
wakelock_plus: 4783562c9a43d209c458cb9b30692134af456269
|
||||||
|
206
pubspec.lock
206
pubspec.lock
@ -13,10 +13,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: _flutterfire_internals
|
name: _flutterfire_internals
|
||||||
sha256: e4f2a7ef31b0ab2c89d2bde35ef3e6e6aff1dce5e66069c6540b0e9cfe33ee6b
|
sha256: e051259913915ea5bc8fe18664596bea08592fd123930605d562969cd7315fcd
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.50"
|
version: "1.3.51"
|
||||||
_macros:
|
_macros:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: dart
|
description: dart
|
||||||
@ -138,26 +138,26 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: build_daemon
|
name: build_daemon
|
||||||
sha256: "294a2edaf4814a378725bfe6358210196f5ea37af89ecd81bfa32960113d4948"
|
sha256: "8e928697a82be082206edb0b9c99c5a4ad6bc31c9e9b8b2f291ae65cd4a25daa"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.0.3"
|
version: "4.0.4"
|
||||||
build_resolvers:
|
build_resolvers:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: build_resolvers
|
name: build_resolvers
|
||||||
sha256: "99d3980049739a985cf9b21f30881f46db3ebc62c5b8d5e60e27440876b1ba1e"
|
sha256: b9e4fda21d846e192628e7a4f6deda6888c36b5b69ba02ff291a01fd529140f0
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.3"
|
version: "2.4.4"
|
||||||
build_runner:
|
build_runner:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: build_runner
|
name: build_runner
|
||||||
sha256: "74691599a5bc750dc96a6b4bfd48f7d9d66453eab04c7f4063134800d6a5c573"
|
sha256: "058fe9dce1de7d69c4b84fada934df3e0153dd000758c4d65964d0166779aa99"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.14"
|
version: "2.4.15"
|
||||||
build_runner_core:
|
build_runner_core:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -214,6 +214,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.4.3"
|
version: "0.4.3"
|
||||||
|
chalkdart:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: chalkdart
|
||||||
|
sha256: "08c910ee341fcdd1e46f20ddce59b13c1d85f5d97f2fd2f12014c46ede670e40"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.3.2"
|
||||||
characters:
|
characters:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -266,10 +274,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: connectivity_plus
|
name: connectivity_plus
|
||||||
sha256: "8a68739d3ee113e51ad35583fdf9ab82c55d09d693d3c39da1aebab87c938412"
|
sha256: "04bf81bb0b77de31557b58d052b24b3eee33f09a6e7a8c68a3e247c7df19ec27"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.1.2"
|
version: "6.1.3"
|
||||||
connectivity_plus_platform_interface:
|
connectivity_plus_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -346,10 +354,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: dart_webrtc
|
name: dart_webrtc
|
||||||
sha256: e65506edb452148220efab53d8d2f8bb9d827bd8bcd53cf3a3e6df70b27f3d86
|
sha256: "03df5b41b23bc185ebcf4b0ffc92d002e295bf56287fb5f9d2c321ddaf7760cc"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.10"
|
version: "1.5.1"
|
||||||
dbus:
|
dbus:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -362,10 +370,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: device_info_plus
|
name: device_info_plus
|
||||||
sha256: e3fc9a65820fef83035af8ee8c09004a719d5d1d54e6de978fcb0d84bbeb241a
|
sha256: "72d146c6d7098689ff5c5f66bcf593ac11efc530095385356e131070333e64da"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "11.2.2"
|
version: "11.3.0"
|
||||||
device_info_plus_platform_interface:
|
device_info_plus_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -490,10 +498,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: file_picker
|
name: file_picker
|
||||||
sha256: c9943dd7d702ab4199d199bc151a2d79c86db031a02ad84566dab58c494d2adc
|
sha256: ab13ae8ef5580a411c458d6207b6774a6c237d77ac37011b13994879f68a8810
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "8.3.1"
|
version: "8.3.7"
|
||||||
file_saver:
|
file_saver:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -530,42 +538,42 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: file_selector_windows
|
name: file_selector_windows
|
||||||
sha256: "8f5d2f6590d51ecd9179ba39c64f722edc15226cc93dcc8698466ad36a4a85a4"
|
sha256: "320fcfb6f33caa90f0b58380489fc5ac05d99ee94b61aa96ec2bff0ba81d3c2b"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.9.3+3"
|
version: "0.9.3+4"
|
||||||
firebase_analytics:
|
firebase_analytics:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: firebase_analytics
|
name: firebase_analytics
|
||||||
sha256: eac382bbcd5ae78c1d1ce5619d13f5a7424429f4bf55df9e3ad5110da34d1060
|
sha256: "47428047a0778f72af53a3c7cb5d556e1cb25e2327cc8aa40d544971dc6245b2"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "11.4.1"
|
version: "11.4.2"
|
||||||
firebase_analytics_platform_interface:
|
firebase_analytics_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: firebase_analytics_platform_interface
|
name: firebase_analytics_platform_interface
|
||||||
sha256: a34db46c367265c4c961626e4b128bfb7b7e50958e7add4c27ba103f5f81b9b0
|
sha256: "1076f4b041f76143e14878c70f0758f17fe5910c0cd992db9e93bd3c3584512b"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.3.1"
|
version: "4.3.2"
|
||||||
firebase_analytics_web:
|
firebase_analytics_web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: firebase_analytics_web
|
name: firebase_analytics_web
|
||||||
sha256: b6b4cef08e45e4c7d48476d9fc49fe9577081809a59026fe95b1a1b1eea165fa
|
sha256: "8f6dd64ea6d28b7f5b9e739d183a9e1c7f17027794a3e9aba1879621d42426ef"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.5.10+7"
|
version: "0.5.10+8"
|
||||||
firebase_core:
|
firebase_core:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: firebase_core
|
name: firebase_core
|
||||||
sha256: d851c1ca98fd5a4c07c747f8c65dacc2edd84a4d9ac055d32a5f0342529069f5
|
sha256: "93dc4dd12f9b02c5767f235307f609e61ed9211047132d07f9e02c668f0bfc33"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.10.1"
|
version: "3.11.0"
|
||||||
firebase_core_platform_interface:
|
firebase_core_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -578,34 +586,34 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: firebase_core_web
|
name: firebase_core_web
|
||||||
sha256: fbc008cf390d909b823763064b63afefe9f02d8afdb13eb3f485b871afee956b
|
sha256: "0e13c80f0de8acaa5d0519cbe23c8b4cc138a2d5d508b5755c861bdfc9762678"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.19.0"
|
version: "2.20.0"
|
||||||
firebase_messaging:
|
firebase_messaging:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: firebase_messaging
|
name: firebase_messaging
|
||||||
sha256: e20ea2a0ecf9b0971575ab3ab42a6e285a94e50092c555b090c1a588a81b4d54
|
sha256: "3dee3b0cbfe719e64773cb7d1cad57c58b2235a8c136f5715fe733a54058c783"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "15.2.1"
|
version: "15.2.2"
|
||||||
firebase_messaging_platform_interface:
|
firebase_messaging_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: firebase_messaging_platform_interface
|
name: firebase_messaging_platform_interface
|
||||||
sha256: c57a92b5ae1857ef4fe4ae2e73452b44d32e984e15ab8b53415ea1bb514bdabd
|
sha256: e9ea726b9bb864fc6223bb66422bd9877b9973ae51967754a769b0d01e201c1e
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.6.1"
|
version: "4.6.2"
|
||||||
firebase_messaging_web:
|
firebase_messaging_web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: firebase_messaging_web
|
name: firebase_messaging_web
|
||||||
sha256: "83694a990d8525d6b01039240b97757298369622ca0253ad0ebcfed221bf8ee0"
|
sha256: "5f7b40e8bf861a37f8b8196e347d8a919750421a45f0b45d1bb74e98fa72726e"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.10.1"
|
version: "3.10.2"
|
||||||
fixnum:
|
fixnum:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -764,10 +772,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: flutter_markdown
|
name: flutter_markdown
|
||||||
sha256: e37f4c69a07b07bb92622ef6b131a53c9aae48f64b176340af9e8e5238718487
|
sha256: e7bbc718adc9476aa14cfddc1ef048d2e21e4e8f18311aaac723266db9f9e7b5
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.5"
|
version: "0.7.6+2"
|
||||||
flutter_native_splash:
|
flutter_native_splash:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
@ -830,10 +838,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: flutter_webrtc
|
name: flutter_webrtc
|
||||||
sha256: "4c76069f724f79dc6e8739c8f16c2ee00ca30a1f8e3aa75c0e830f8183278f03"
|
sha256: "572df3de6c828e571db4b75b4a96a15c2f34fa3d420a84438f44a3158b22e81a"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.12.7"
|
version: "0.12.9"
|
||||||
freezed:
|
freezed:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
@ -886,10 +894,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: go_router
|
name: go_router
|
||||||
sha256: "9b736a9fa879d8ad6df7932cbdcc58237c173ab004ef90d8377923d7ad731eaa"
|
sha256: "04539267a740931c6d4479a10d466717ca5901c6fdfd3fcda09391bbb8ebd651"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "14.7.2"
|
version: "14.8.0"
|
||||||
google_fonts:
|
google_fonts:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -934,10 +942,50 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: home_widget
|
name: home_widget
|
||||||
sha256: b313e3304c0429669fddf1286e1fbf61a64b873f38ba30b3eb890ef0d7560b12
|
sha256: "7430f7549d42cef2e729bd3c779de748b93f1eb78b1abfe6bca8fffd1cfce3e9"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.0"
|
version: "0.7.0+1"
|
||||||
|
hotkey_manager:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: hotkey_manager
|
||||||
|
sha256: "06f0655b76c8dd322fb7101dc615afbdbf39c3d3414df9e059c33892104479cd"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.2.3"
|
||||||
|
hotkey_manager_linux:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: hotkey_manager_linux
|
||||||
|
sha256: "83676bda8210a3377bc6f1977f193bc1dbdd4c46f1bdd02875f44b6eff9a8473"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.2.0"
|
||||||
|
hotkey_manager_macos:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: hotkey_manager_macos
|
||||||
|
sha256: "03b5967e64357b9ac05188ea4a5df6fe4ed4205762cb80aaccf8916ee1713c96"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.2.0"
|
||||||
|
hotkey_manager_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: hotkey_manager_platform_interface
|
||||||
|
sha256: "98ffca25b8cc9081552902747b2942e3bc37855389a4218c9d50ca316b653b13"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.2.0"
|
||||||
|
hotkey_manager_windows:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: hotkey_manager_windows
|
||||||
|
sha256: "0d03ced9fe563ed0b68f0a0e1b22c9ffe26eb8053cb960e401f68a4f070e0117"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.2.0"
|
||||||
html:
|
html:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -995,7 +1043,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.2"
|
version: "1.1.2"
|
||||||
image_picker_android:
|
image_picker_android:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: image_picker_android
|
name: image_picker_android
|
||||||
sha256: b62d34a506e12bb965e824b6db4fbf709ee4589cf5d3e99b45ab2287b008ee0c
|
sha256: b62d34a506e12bb965e824b6db4fbf709ee4589cf5d3e99b45ab2287b008ee0c
|
||||||
@ -1150,10 +1198,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: livekit_client
|
name: livekit_client
|
||||||
sha256: "02b4653d903852d0ae86b15fbe4324747606dae6410fe860d0c07a11c79988de"
|
sha256: "0cfb2f48eff7a93ea8927696dc6f218aebd2fcd1fcc1b1a7b2f53ff3597fdb52"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.5"
|
version: "2.3.6"
|
||||||
logging:
|
logging:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -1206,10 +1254,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: material_symbols_icons
|
name: material_symbols_icons
|
||||||
sha256: "89aac72d25dd49303f71b3b1e70f8374791846729365b25bebc2a2531e5b86cd"
|
sha256: "1403944c2a68dbdf934fca2b2115a372e70a4a185c136ab71a9d16e2108d0b14"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.2801.1"
|
version: "4.2805.1"
|
||||||
media_kit:
|
media_kit:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -1282,6 +1330,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.5"
|
version: "1.2.5"
|
||||||
|
menu_base:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: menu_base
|
||||||
|
sha256: "820368014a171bd1241030278e6c2617354f492f5c703d7b7d4570a6b8b84405"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.1.1"
|
||||||
meta:
|
meta:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -1334,18 +1390,18 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: package_info_plus
|
name: package_info_plus
|
||||||
sha256: b15fad91c4d3d1f2b48c053dd41cb82da007c27407dc9ab5f9aa59881d0e39d4
|
sha256: "67eae327b1b0faf761964a1d2e5d323c797f3799db0e85aa232db8d9e922bc35"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "8.1.4"
|
version: "8.2.1"
|
||||||
package_info_plus_platform_interface:
|
package_info_plus_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: package_info_plus_platform_interface
|
name: package_info_plus_platform_interface
|
||||||
sha256: a5ef9986efc7bf772f2696183a3992615baa76c1ffb1189318dd8803778fb05b
|
sha256: "205ec83335c2ab9107bbba3f8997f9356d72ca3c715d2f038fc773d0366b4c76"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.2"
|
version: "3.1.0"
|
||||||
pasteboard:
|
pasteboard:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -1710,18 +1766,18 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: shared_preferences
|
name: shared_preferences
|
||||||
sha256: "688ee90fbfb6989c980254a56cb26ebe9bb30a3a2dff439a78894211f73de67a"
|
sha256: "846849e3e9b68f3ef4b60c60cf4b3e02e9321bc7f4d8c4692cf87ffa82fc8a3a"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.5.1"
|
version: "2.5.2"
|
||||||
shared_preferences_android:
|
shared_preferences_android:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: shared_preferences_android
|
name: shared_preferences_android
|
||||||
sha256: "650584dcc0a39856f369782874e562efd002a9c94aec032412c9eb81419cce1f"
|
sha256: ea86be7b7114f9e94fddfbb52649e59a03d6627ccd2387ebddcd6624719e9f16
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.4"
|
version: "2.4.5"
|
||||||
shared_preferences_foundation:
|
shared_preferences_foundation:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -1774,10 +1830,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: shelf_web_socket
|
name: shelf_web_socket
|
||||||
sha256: cc36c297b52866d203dbf9332263c94becc2fe0ceaa9681d07b6ef9807023b67
|
sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.1"
|
version: "3.0.0"
|
||||||
|
shortid:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shortid
|
||||||
|
sha256: d0b40e3dbb50497dad107e19c54ca7de0d1a274eb9b4404991e443dadb9ebedb
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.1.2"
|
||||||
sky_engine:
|
sky_engine:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: flutter
|
description: flutter
|
||||||
@ -1951,6 +2015,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.2"
|
version: "1.0.2"
|
||||||
|
tray_manager:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: tray_manager
|
||||||
|
sha256: "80be6c508159a6f3c57983de795209ac13453e9832fd574143b06dceee188ed2"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.3.2"
|
||||||
typed_data:
|
typed_data:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -1959,6 +2031,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.0"
|
version: "1.4.0"
|
||||||
|
uni_platform:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: uni_platform
|
||||||
|
sha256: e02213a7ee5352212412ca026afd41d269eb00d982faa552f419ffc2debfad84
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.1.3"
|
||||||
universal_io:
|
universal_io:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -2059,10 +2139,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: vector_graphics
|
name: vector_graphics
|
||||||
sha256: "7ed22c21d7fdcc88dd6ba7860384af438cd220b251ad65dfc142ab722fabef61"
|
sha256: "44cc7104ff32563122a929e4620cf3efd584194eec6d1d913eb5ba593dbcf6de"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.16"
|
version: "1.1.18"
|
||||||
vector_graphics_codec:
|
vector_graphics_codec:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -2107,10 +2187,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: video_compress
|
name: video_compress
|
||||||
sha256: "5b42d89f3970c956bad7a86c29682b0892c11a4ddf95ae6e29897ee28788e377"
|
sha256: "31bc5cdb9a02ba666456e5e1907393c28e6e0e972980d7d8d619a7beda0d4f20"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.3"
|
version: "3.1.4"
|
||||||
vm_service:
|
vm_service:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -2179,10 +2259,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: webrtc_interface
|
name: webrtc_interface
|
||||||
sha256: abec3ab7956bd5ac539cf34a42fa0c82ea26675847c0966bb85160400eea9388
|
sha256: "10fc6dc0ac16f909f5e434c18902415211d759313c87261f1e4ec5b4f6a04c26"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.0"
|
version: "1.2.1"
|
||||||
win32:
|
win32:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
|
|||||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||||
# In Windows, build-name is used as the major, minor, and patch parts
|
# In Windows, build-name is used as the major, minor, and patch parts
|
||||||
# of the product and file versions while build-number is used as the build suffix.
|
# of the product and file versions while build-number is used as the build suffix.
|
||||||
version: 2.2.2+61
|
version: 2.3.2+70
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.5.4
|
sdk: ^3.5.4
|
||||||
@ -118,6 +118,9 @@ dependencies:
|
|||||||
flutter_inappwebview: ^6.1.5
|
flutter_inappwebview: ^6.1.5
|
||||||
html: ^0.15.5
|
html: ^0.15.5
|
||||||
xml: ^6.5.0
|
xml: ^6.5.0
|
||||||
|
tray_manager: ^0.3.2
|
||||||
|
hotkey_manager: ^0.2.3
|
||||||
|
image_picker_android: ^0.8.12+20
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
@ -152,6 +155,8 @@ flutter:
|
|||||||
- assets/icon/icon.png
|
- assets/icon/icon.png
|
||||||
- assets/icon/icon-dark.png
|
- assets/icon/icon-dark.png
|
||||||
- assets/icon/icon-light-radius.png
|
- assets/icon/icon-light-radius.png
|
||||||
|
- assets/icon/tray-icon.ico
|
||||||
|
- assets/icon/tray-icon.png
|
||||||
- assets/translations/
|
- assets/translations/
|
||||||
|
|
||||||
# An image asset can refer to one or more resolution-specific "variants", see
|
# An image asset can refer to one or more resolution-specific "variants", see
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
#include <flutter_udid/flutter_udid_plugin_c_api.h>
|
#include <flutter_udid/flutter_udid_plugin_c_api.h>
|
||||||
#include <flutter_webrtc/flutter_web_r_t_c_plugin.h>
|
#include <flutter_webrtc/flutter_web_r_t_c_plugin.h>
|
||||||
#include <gal/gal_plugin_c_api.h>
|
#include <gal/gal_plugin_c_api.h>
|
||||||
|
#include <hotkey_manager_windows/hotkey_manager_windows_plugin_c_api.h>
|
||||||
#include <livekit_client/live_kit_plugin.h>
|
#include <livekit_client/live_kit_plugin.h>
|
||||||
#include <media_kit_libs_windows_video/media_kit_libs_windows_video_plugin_c_api.h>
|
#include <media_kit_libs_windows_video/media_kit_libs_windows_video_plugin_c_api.h>
|
||||||
#include <media_kit_video/media_kit_video_plugin_c_api.h>
|
#include <media_kit_video/media_kit_video_plugin_c_api.h>
|
||||||
@ -22,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 <tray_manager/tray_manager_plugin.h>
|
||||||
#include <url_launcher_windows/url_launcher_windows.h>
|
#include <url_launcher_windows/url_launcher_windows.h>
|
||||||
|
|
||||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||||
@ -43,6 +45,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
|
|||||||
registry->GetRegistrarForPlugin("FlutterWebRTCPlugin"));
|
registry->GetRegistrarForPlugin("FlutterWebRTCPlugin"));
|
||||||
GalPluginCApiRegisterWithRegistrar(
|
GalPluginCApiRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("GalPluginCApi"));
|
registry->GetRegistrarForPlugin("GalPluginCApi"));
|
||||||
|
HotkeyManagerWindowsPluginCApiRegisterWithRegistrar(
|
||||||
|
registry->GetRegistrarForPlugin("HotkeyManagerWindowsPluginCApi"));
|
||||||
LiveKitPluginRegisterWithRegistrar(
|
LiveKitPluginRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("LiveKitPlugin"));
|
registry->GetRegistrarForPlugin("LiveKitPlugin"));
|
||||||
MediaKitLibsWindowsVideoPluginCApiRegisterWithRegistrar(
|
MediaKitLibsWindowsVideoPluginCApiRegisterWithRegistrar(
|
||||||
@ -57,6 +61,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
|
|||||||
registry->GetRegistrarForPlugin("ScreenBrightnessWindowsPlugin"));
|
registry->GetRegistrarForPlugin("ScreenBrightnessWindowsPlugin"));
|
||||||
SharePlusWindowsPluginCApiRegisterWithRegistrar(
|
SharePlusWindowsPluginCApiRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi"));
|
registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi"));
|
||||||
|
TrayManagerPluginRegisterWithRegistrar(
|
||||||
|
registry->GetRegistrarForPlugin("TrayManagerPlugin"));
|
||||||
UrlLauncherWindowsRegisterWithRegistrar(
|
UrlLauncherWindowsRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
|
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
|||||||
flutter_udid
|
flutter_udid
|
||||||
flutter_webrtc
|
flutter_webrtc
|
||||||
gal
|
gal
|
||||||
|
hotkey_manager_windows
|
||||||
livekit_client
|
livekit_client
|
||||||
media_kit_libs_windows_video
|
media_kit_libs_windows_video
|
||||||
media_kit_video
|
media_kit_video
|
||||||
@ -19,6 +20,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
|||||||
permission_handler_windows
|
permission_handler_windows
|
||||||
screen_brightness_windows
|
screen_brightness_windows
|
||||||
share_plus
|
share_plus
|
||||||
|
tray_manager
|
||||||
url_launcher_windows
|
url_launcher_windows
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user