Compare commits
22 Commits
Author | SHA1 | Date | |
---|---|---|---|
dad869967e | |||
2d5b3b554e | |||
74882116e3 | |||
a97c3bce3a | |||
1aa70827dc | |||
fe028860e9 | |||
a2d2ce4d38 | |||
167c11b9eb | |||
8cb3933fcc | |||
3818328afe | |||
11627e2455 | |||
3f82c06ff8 | |||
2350f59131 | |||
9fe7c9530a | |||
52f1826e91 | |||
28a4c86dbf | |||
85e48ce03b | |||
efef61a8ea | |||
10ead95af9 | |||
838ee4d55d | |||
13e42429a9 | |||
c6ce3fe2b7 |
@ -12,9 +12,9 @@ post {
|
|||||||
|
|
||||||
body:json {
|
body:json {
|
||||||
{
|
{
|
||||||
"alias": "AteChip",
|
"alias": "Meltdown",
|
||||||
"name": "Cat ate chips",
|
"name": "Meltdown",
|
||||||
"attachment_id": "d0b692cc64054463",
|
"attachment_id": "IpDPHEbWDDCbBofX",
|
||||||
"pack_id": 2
|
"pack_id": 4
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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
|
||||||
|
}
|
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": 9705,
|
||||||
|
"payee_id": 2
|
||||||
|
}
|
||||||
|
}
|
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 |
@ -154,9 +154,11 @@
|
|||||||
"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",
|
||||||
"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 +168,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": {
|
||||||
@ -609,5 +611,11 @@
|
|||||||
"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."
|
||||||
}
|
}
|
||||||
|
@ -138,9 +138,11 @@
|
|||||||
"fieldPublisherBelongToRealmUnset": "未设置发布者所属领域",
|
"fieldPublisherBelongToRealmUnset": "未设置发布者所属领域",
|
||||||
"writePostTypeStory": "发动态",
|
"writePostTypeStory": "发动态",
|
||||||
"writePostTypeArticle": "写文章",
|
"writePostTypeArticle": "写文章",
|
||||||
|
"writePostTypeQuestion": "提问题",
|
||||||
"fieldPostPublisher": "帖子发布者",
|
"fieldPostPublisher": "帖子发布者",
|
||||||
"fieldPostContent": "发生什么事了?!",
|
"fieldPostContent": "发生什么事了?!",
|
||||||
"fieldPostTitle": "标题",
|
"fieldPostTitle": "标题",
|
||||||
|
"fieldPostQuestionReward": "回答奖励源点",
|
||||||
"fieldPostDescription": "描述",
|
"fieldPostDescription": "描述",
|
||||||
"fieldPostTags": "标签",
|
"fieldPostTags": "标签",
|
||||||
"fieldPostCategories": "分类",
|
"fieldPostCategories": "分类",
|
||||||
@ -607,5 +609,12 @@
|
|||||||
"other": "{} 源点"
|
"other": "{} 源点"
|
||||||
},
|
},
|
||||||
"aiThinkingProcess": "AI 思考过程",
|
"aiThinkingProcess": "AI 思考过程",
|
||||||
"accountSettingsApplied": "帐号设置已应用。"
|
"accountSettingsApplied": "帐号设置已应用。",
|
||||||
|
"trayMenuExit": "退出",
|
||||||
|
"postQuestionUnanswered": "未解答的问题",
|
||||||
|
"postQuestionUnansweredWithReward": "未解答的问题,悬赏源点 {}",
|
||||||
|
"postQuestionAnswered": "已解答的问题",
|
||||||
|
"postQuestionAnswerTitle": "精选解答",
|
||||||
|
"postQuestionAnswerSelect": "选择解答",
|
||||||
|
"postQuestionAnswerSelected": "解答已选择,奖励已发放。"
|
||||||
}
|
}
|
||||||
|
@ -138,9 +138,11 @@
|
|||||||
"fieldPublisherBelongToRealmUnset": "未設置發佈者所屬領域",
|
"fieldPublisherBelongToRealmUnset": "未設置發佈者所屬領域",
|
||||||
"writePostTypeStory": "發動態",
|
"writePostTypeStory": "發動態",
|
||||||
"writePostTypeArticle": "寫文章",
|
"writePostTypeArticle": "寫文章",
|
||||||
|
"writePostTypeQuestion": "提問題",
|
||||||
"fieldPostPublisher": "帖子發佈者",
|
"fieldPostPublisher": "帖子發佈者",
|
||||||
"fieldPostContent": "發生什麼事了?!",
|
"fieldPostContent": "發生什麼事了?!",
|
||||||
"fieldPostTitle": "標題",
|
"fieldPostTitle": "標題",
|
||||||
|
"fieldPostQuestionReward": "回答獎勵源點",
|
||||||
"fieldPostDescription": "描述",
|
"fieldPostDescription": "描述",
|
||||||
"fieldPostTags": "標籤",
|
"fieldPostTags": "標籤",
|
||||||
"fieldPostCategories": "分類",
|
"fieldPostCategories": "分類",
|
||||||
@ -607,5 +609,12 @@
|
|||||||
"other": "{} 源點"
|
"other": "{} 源點"
|
||||||
},
|
},
|
||||||
"aiThinkingProcess": "AI 思考過程",
|
"aiThinkingProcess": "AI 思考過程",
|
||||||
"accountSettingsApplied": "帳號設置已應用。"
|
"accountSettingsApplied": "帳號設置已應用。",
|
||||||
|
"trayMenuExit": "退出",
|
||||||
|
"postQuestionUnanswered": "未解答的問題",
|
||||||
|
"postQuestionUnansweredWithReward": "未解答的問題,懸賞源點 {}",
|
||||||
|
"postQuestionAnswered": "已解答的問題",
|
||||||
|
"postQuestionAnswerTitle": "精選解答",
|
||||||
|
"postQuestionAnswerSelect": "選擇解答",
|
||||||
|
"postQuestionAnswerSelected": "解答已選擇,獎勵已發放。"
|
||||||
}
|
}
|
||||||
|
@ -138,9 +138,11 @@
|
|||||||
"fieldPublisherBelongToRealmUnset": "未設置發佈者所屬領域",
|
"fieldPublisherBelongToRealmUnset": "未設置發佈者所屬領域",
|
||||||
"writePostTypeStory": "發動態",
|
"writePostTypeStory": "發動態",
|
||||||
"writePostTypeArticle": "寫文章",
|
"writePostTypeArticle": "寫文章",
|
||||||
|
"writePostTypeQuestion": "提問題",
|
||||||
"fieldPostPublisher": "帖子發佈者",
|
"fieldPostPublisher": "帖子發佈者",
|
||||||
"fieldPostContent": "發生什麼事了?!",
|
"fieldPostContent": "發生什麼事了?!",
|
||||||
"fieldPostTitle": "標題",
|
"fieldPostTitle": "標題",
|
||||||
|
"fieldPostQuestionReward": "回答獎勵源點",
|
||||||
"fieldPostDescription": "描述",
|
"fieldPostDescription": "描述",
|
||||||
"fieldPostTags": "標籤",
|
"fieldPostTags": "標籤",
|
||||||
"fieldPostCategories": "分類",
|
"fieldPostCategories": "分類",
|
||||||
@ -607,5 +609,12 @@
|
|||||||
"other": "{} 源點"
|
"other": "{} 源點"
|
||||||
},
|
},
|
||||||
"aiThinkingProcess": "AI 思考過程",
|
"aiThinkingProcess": "AI 思考過程",
|
||||||
"accountSettingsApplied": "帳號設置已應用。"
|
"accountSettingsApplied": "帳號設置已應用。",
|
||||||
|
"trayMenuExit": "退出",
|
||||||
|
"postQuestionUnanswered": "未解答的問題",
|
||||||
|
"postQuestionUnansweredWithReward": "未解答的問題,懸賞源點 {}",
|
||||||
|
"postQuestionAnswered": "已解答的問題",
|
||||||
|
"postQuestionAnswerTitle": "精選解答",
|
||||||
|
"postQuestionAnswerSelect": "選擇解答",
|
||||||
|
"postQuestionAnswerSelected": "解答已選擇,獎勵已發放。"
|
||||||
}
|
}
|
||||||
|
@ -43,58 +43,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 +123,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,7 +179,7 @@ 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.5):
|
||||||
- Flutter
|
- Flutter
|
||||||
- flutter_webrtc
|
- flutter_webrtc
|
||||||
@ -381,15 +381,15 @@ SPEC CHECKSUMS:
|
|||||||
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,13 +397,13 @@ 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: dcc5fd47ba69c98fc6baeb12e862c9d43807d976
|
||||||
media_kit_libs_ios_video: a5fe24bc7875ccd6378a0978c13185e1344651c1
|
media_kit_libs_ios_video: a5fe24bc7875ccd6378a0978c13185e1344651c1
|
||||||
media_kit_native_event_loop: e6b2ab20cf0746eb1c33be961fcf79667304fa2a
|
media_kit_native_event_loop: e6b2ab20cf0746eb1c33be961fcf79667304fa2a
|
||||||
|
@ -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;
|
||||||
|
@ -144,6 +144,7 @@ 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',
|
||||||
};
|
};
|
||||||
|
|
||||||
static const kAttachmentProgressWeight = 0.9;
|
static const kAttachmentProgressWeight = 0.9;
|
||||||
@ -153,6 +154,7 @@ 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();
|
||||||
|
|
||||||
bool _temporarySaveActive = false;
|
bool _temporarySaveActive = false;
|
||||||
|
|
||||||
@ -168,6 +170,7 @@ class PostWriteController extends ChangeNotifier {
|
|||||||
});
|
});
|
||||||
contentController.addListener(() {
|
contentController.addListener(() {
|
||||||
_temporaryPlanSave();
|
_temporaryPlanSave();
|
||||||
|
notifyListeners();
|
||||||
});
|
});
|
||||||
if (doLoadFromTemporary) _temporaryLoad();
|
if (doLoadFromTemporary) _temporaryLoad();
|
||||||
}
|
}
|
||||||
@ -214,6 +217,7 @@ 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() ?? '';
|
||||||
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);
|
||||||
@ -347,6 +351,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),
|
||||||
@ -375,6 +380,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>());
|
||||||
@ -473,6 +479,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 +506,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!.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,
|
||||||
},
|
},
|
||||||
onSendProgress: (count, total) {
|
onSendProgress: (count, total) {
|
||||||
progress = baseProgressVal + (count / total) * (kPostingProgressWeight / 2);
|
progress = baseProgressVal + (count / total) * (kPostingProgressWeight / 2);
|
||||||
|
106
lib/main.dart
106
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,6 +43,7 @@ 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';
|
||||||
@ -206,7 +210,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 +285,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 +298,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 +361,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>();
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,7 +34,16 @@ 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
_connectCompleter = Completer<void>();
|
||||||
|
|
||||||
if (!_ua.isAuthorized) return;
|
if (!_ua.isAuthorized) return;
|
||||||
if (isConnected || conn != null) {
|
if (isConnected || conn != null) {
|
||||||
disconnect();
|
disconnect();
|
||||||
@ -50,6 +60,7 @@ class WebSocketProvider extends ChangeNotifier {
|
|||||||
try {
|
try {
|
||||||
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,7 @@ class WebSocketProvider extends ChangeNotifier {
|
|||||||
} finally {
|
} finally {
|
||||||
isBusy = false;
|
isBusy = false;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
_connectCompleter!.complete();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,11 +95,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]);
|
||||||
|
@ -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,9 +472,7 @@ 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',
|
|
||||||
queryParameters: {
|
|
||||||
'take': 10,
|
'take': 10,
|
||||||
'offset': 0,
|
'offset': 0,
|
||||||
});
|
});
|
||||||
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -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!);
|
||||||
|
@ -166,6 +166,27 @@ 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),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: RefreshIndicator(
|
body: RefreshIndicator(
|
||||||
@ -225,10 +246,7 @@ class _ExploreScreenState extends State<ExploreScreen> {
|
|||||||
onFetchData: _fetchPosts,
|
onFetchData: _fetchPosts,
|
||||||
itemBuilder: (context, idx) {
|
itemBuilder: (context, idx) {
|
||||||
return Center(
|
return Center(
|
||||||
child: OpenContainer(
|
child: OpenablePostItem(
|
||||||
closedBuilder: (_, __) => Container(
|
|
||||||
constraints: const BoxConstraints(maxWidth: 640),
|
|
||||||
child: PostItem(
|
|
||||||
data: _posts[idx],
|
data: _posts[idx],
|
||||||
maxWidth: 640,
|
maxWidth: 640,
|
||||||
onChanged: (data) {
|
onChanged: (data) {
|
||||||
@ -238,22 +256,6 @@ class _ExploreScreenState extends State<ExploreScreen> {
|
|||||||
_refreshPosts();
|
_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),
|
||||||
|
@ -7,6 +7,7 @@ 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,6 +55,7 @@ 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(
|
||||||
@ -62,6 +64,7 @@ class _NotificationScreenState extends State<NotificationScreen> {
|
|||||||
.cast<SnNotification>() ??
|
.cast<SnNotification>() ??
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
|
nty.updateTray();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
context.showErrorDialog(err);
|
context.showErrorDialog(err);
|
||||||
@ -88,9 +91,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,7 +183,7 @@ class _PostDetailScreenState extends State<PostDetailScreen> {
|
|||||||
if (_data != null)
|
if (_data != null)
|
||||||
PostCommentSliverList(
|
PostCommentSliverList(
|
||||||
key: _childListKey,
|
key: _childListKey,
|
||||||
parentPostId: _data!.id,
|
parentPost: _data!,
|
||||||
maxWidth: 640,
|
maxWidth: 640,
|
||||||
),
|
),
|
||||||
SliverGap(math.max(MediaQuery.of(context).padding.bottom, 16)),
|
SliverGap(math.max(MediaQuery.of(context).padding.bottom, 16)),
|
||||||
|
@ -1,20 +1,26 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:dropdown_button2/dropdown_button2.dart';
|
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.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:go_router/go_router.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:responsive_framework/responsive_framework.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
import 'package:surface/controllers/post_write_controller.dart';
|
import 'package:surface/controllers/post_write_controller.dart';
|
||||||
import 'package:surface/providers/config.dart';
|
import 'package:surface/providers/config.dart';
|
||||||
import 'package:surface/providers/sn_network.dart';
|
import 'package:surface/providers/sn_network.dart';
|
||||||
|
import 'package:surface/types/attachment.dart';
|
||||||
import 'package:surface/types/post.dart';
|
import 'package:surface/types/post.dart';
|
||||||
import 'package:surface/widgets/account/account_image.dart';
|
import 'package:surface/widgets/account/account_image.dart';
|
||||||
import 'package:surface/widgets/loading_indicator.dart';
|
import 'package:surface/widgets/loading_indicator.dart';
|
||||||
|
import 'package:surface/widgets/markdown_content.dart';
|
||||||
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
||||||
import 'package:surface/widgets/post/post_item.dart';
|
|
||||||
import 'package:surface/widgets/post/post_media_pending_list.dart';
|
import 'package:surface/widgets/post/post_media_pending_list.dart';
|
||||||
import 'package:surface/widgets/post/post_meta_editor.dart';
|
import 'package:surface/widgets/post/post_meta_editor.dart';
|
||||||
import 'package:surface/widgets/dialog.dart';
|
import 'package:surface/widgets/dialog.dart';
|
||||||
@ -94,15 +100,49 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final HotKey _pasteHotKey = HotKey(
|
||||||
|
key: PhysicalKeyboardKey.keyV,
|
||||||
|
modifiers: [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;
|
||||||
|
_writeController.addAttachments([
|
||||||
|
PostWriteMedia.fromBytes(
|
||||||
|
imageBytes,
|
||||||
|
'attachmentPastedImage'.tr(),
|
||||||
|
SnMediaType.image,
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
setState(() {});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showPublisherPopup() {
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => _PostPublisherPopup(
|
||||||
|
controller: _writeController,
|
||||||
|
publishers: _publishers,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_writeController.dispose();
|
_writeController.dispose();
|
||||||
|
if (!kIsWeb && !(Platform.isAndroid || Platform.isIOS)) hotKeyManager.unregister(_pasteHotKey);
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
_registerHotKey();
|
||||||
if (!PostWriteController.kTitleMap.keys.contains(widget.mode)) {
|
if (!PostWriteController.kTitleMap.keys.contains(widget.mode)) {
|
||||||
context.showErrorDialog('Unknown post type');
|
context.showErrorDialog('Unknown post type');
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
@ -153,6 +193,7 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
]),
|
]),
|
||||||
|
maxLines: 2,
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
IconButton(
|
IconButton(
|
||||||
@ -164,176 +205,53 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
),
|
),
|
||||||
body: Column(
|
body: Column(
|
||||||
children: [
|
children: [
|
||||||
DropdownButtonHideUnderline(
|
|
||||||
child: DropdownButton2<SnPublisher>(
|
|
||||||
isExpanded: true,
|
|
||||||
hint: Text(
|
|
||||||
'fieldPostPublisher',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 14,
|
|
||||||
color: Theme.of(context).hintColor,
|
|
||||||
),
|
|
||||||
).tr(),
|
|
||||||
items: <DropdownMenuItem<SnPublisher>>[
|
|
||||||
...(_publishers?.map(
|
|
||||||
(item) => DropdownMenuItem<SnPublisher>(
|
|
||||||
enabled: _writeController.editingPost == null,
|
|
||||||
value: item,
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
AccountImage(content: item.avatar, radius: 16),
|
|
||||||
const Gap(8),
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(item.nick).textStyle(Theme.of(context).textTheme.bodyMedium!),
|
|
||||||
Text('@${item.name}')
|
|
||||||
.textStyle(Theme.of(context).textTheme.bodySmall!)
|
|
||||||
.fontSize(12),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
) ??
|
|
||||||
[]),
|
|
||||||
DropdownMenuItem<SnPublisher>(
|
|
||||||
value: null,
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
CircleAvatar(
|
|
||||||
radius: 16,
|
|
||||||
backgroundColor: Colors.transparent,
|
|
||||||
foregroundColor: Theme.of(context).colorScheme.onSurface,
|
|
||||||
child: const Icon(Symbols.add),
|
|
||||||
),
|
|
||||||
const Gap(8),
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text('publishersNew').tr().textStyle(Theme.of(context).textTheme.bodyMedium!),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
value: _writeController.publisher,
|
|
||||||
onChanged: (SnPublisher? value) {
|
|
||||||
if (value == null) {
|
|
||||||
GoRouter.of(context).pushNamed('accountPublisherNew').then((value) {
|
|
||||||
if (value == true) {
|
|
||||||
_publishers = null;
|
|
||||||
_fetchPublishers();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
_writeController.setPublisher(value);
|
|
||||||
final config = context.read<ConfigProvider>();
|
|
||||||
config.prefs.setInt('int_last_publisher_id', value.id);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
buttonStyleData: const ButtonStyleData(
|
|
||||||
padding: EdgeInsets.only(right: 16),
|
|
||||||
height: 48,
|
|
||||||
),
|
|
||||||
menuItemStyleData: const MenuItemStyleData(
|
|
||||||
height: 48,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Divider(height: 1),
|
|
||||||
Expanded(
|
|
||||||
child: SingleChildScrollView(
|
|
||||||
padding: EdgeInsets.only(bottom: 8),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
// Replying Notice
|
|
||||||
if (_writeController.replyingPost != null)
|
|
||||||
Column(
|
|
||||||
children: [
|
|
||||||
ExpansionTile(
|
|
||||||
minTileHeight: 48,
|
|
||||||
leading: const Icon(Symbols.reply).padding(left: 4),
|
|
||||||
title: Text('postReplyingNotice')
|
|
||||||
.fontSize(15)
|
|
||||||
.tr(args: ['@${_writeController.replyingPost!.publisher.name}']),
|
|
||||||
children: <Widget>[PostItem(data: _writeController.replyingPost!)],
|
|
||||||
),
|
|
||||||
const Divider(height: 1),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
// Reposting Notice
|
|
||||||
if (_writeController.repostingPost != null)
|
|
||||||
Column(
|
|
||||||
children: [
|
|
||||||
ExpansionTile(
|
|
||||||
minTileHeight: 48,
|
|
||||||
leading: const Icon(Symbols.forward).padding(left: 4),
|
|
||||||
title: Text('postRepostingNotice')
|
|
||||||
.fontSize(15)
|
|
||||||
.tr(args: ['@${_writeController.repostingPost!.publisher.name}']),
|
|
||||||
children: <Widget>[
|
|
||||||
PostItem(
|
|
||||||
data: _writeController.repostingPost!,
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const Divider(height: 1),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
// Editing Notice
|
|
||||||
if (_writeController.editingPost != null)
|
if (_writeController.editingPost != null)
|
||||||
Column(
|
|
||||||
children: [
|
|
||||||
ExpansionTile(
|
|
||||||
minTileHeight: 48,
|
|
||||||
leading: const Icon(Symbols.edit_note).padding(left: 4),
|
|
||||||
title: Text('postEditingNotice')
|
|
||||||
.fontSize(15)
|
|
||||||
.tr(args: ['@${_writeController.editingPost!.publisher.name}']),
|
|
||||||
children: <Widget>[PostItem(data: _writeController.editingPost!)],
|
|
||||||
),
|
|
||||||
const Divider(height: 1),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
// Content Input Area
|
|
||||||
Container(
|
Container(
|
||||||
constraints: const BoxConstraints(maxWidth: 640),
|
padding: const EdgeInsets.only(top: 4, bottom: 4, left: 20, right: 20),
|
||||||
child: TextField(
|
decoration: BoxDecoration(
|
||||||
controller: _writeController.contentController,
|
border: Border(
|
||||||
maxLines: null,
|
bottom: BorderSide(
|
||||||
decoration: InputDecoration(
|
color: Theme.of(context).dividerColor,
|
||||||
hintText: 'fieldPostContent'.tr(),
|
width: 1 / MediaQuery.of(context).devicePixelRatio,
|
||||||
hintStyle: TextStyle(fontSize: 14),
|
|
||||||
isCollapsed: true,
|
|
||||||
contentPadding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 16,
|
|
||||||
),
|
|
||||||
border: InputBorder.none,
|
|
||||||
),
|
|
||||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
]
|
),
|
||||||
.expandIndexed(
|
child: Row(
|
||||||
(idx, ele) => [
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
if (idx != 0 || _writeController.isRelatedNull) const Gap(8),
|
children: [
|
||||||
ele,
|
const Icon(Icons.edit, size: 16),
|
||||||
|
const Gap(10),
|
||||||
|
Text('postEditingNotice').tr(args: ['@${_writeController.editingPost!.publisher.name}']),
|
||||||
],
|
],
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
SingleChildScrollView(
|
||||||
|
padding: EdgeInsets.only(bottom: 160),
|
||||||
|
child: switch (_writeController.mode) {
|
||||||
|
'stories' => _PostStoryEditor(
|
||||||
|
controller: _writeController,
|
||||||
|
onTapPublisher: _showPublisherPopup,
|
||||||
|
),
|
||||||
|
'articles' => _PostArticleEditor(
|
||||||
|
controller: _writeController,
|
||||||
|
onTapPublisher: _showPublisherPopup,
|
||||||
|
),
|
||||||
|
'questions' => _PostQuestionEditor(
|
||||||
|
controller: _writeController,
|
||||||
|
onTapPublisher: _showPublisherPopup,
|
||||||
|
),
|
||||||
|
_ => const Placeholder(),
|
||||||
|
},
|
||||||
),
|
),
|
||||||
if (_writeController.attachments.isNotEmpty || _writeController.thumbnail != null)
|
if (_writeController.attachments.isNotEmpty || _writeController.thumbnail != null)
|
||||||
PostMediaPendingList(
|
Positioned(
|
||||||
|
bottom: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
child: PostMediaPendingList(
|
||||||
thumbnail: _writeController.thumbnail,
|
thumbnail: _writeController.thumbnail,
|
||||||
attachments: _writeController.attachments,
|
attachments: _writeController.attachments,
|
||||||
isBusy: _writeController.isBusy,
|
isBusy: _writeController.isBusy,
|
||||||
@ -365,6 +283,10 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
},
|
},
|
||||||
onUpdateBusy: (state) => _writeController.setIsBusy(state),
|
onUpdateBusy: (state) => _writeController.setIsBusy(state),
|
||||||
).padding(bottom: 8),
|
).padding(bottom: 8),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
Material(
|
Material(
|
||||||
elevation: 2,
|
elevation: 2,
|
||||||
child: Column(
|
child: Column(
|
||||||
@ -466,3 +388,302 @@ class _PostEditorActionScrollBehavior extends MaterialScrollBehavior {
|
|||||||
PointerDeviceKind.mouse,
|
PointerDeviceKind.mouse,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _PostPublisherPopup extends StatelessWidget {
|
||||||
|
final PostWriteController controller;
|
||||||
|
final List<SnPublisher>? publishers;
|
||||||
|
|
||||||
|
const _PostPublisherPopup({required this.controller, this.publishers});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const Icon(Symbols.face, size: 24),
|
||||||
|
const Gap(16),
|
||||||
|
Text('accountPublishers', style: Theme.of(context).textTheme.titleLarge).tr(),
|
||||||
|
],
|
||||||
|
).padding(horizontal: 20, top: 16, bottom: 12),
|
||||||
|
Expanded(
|
||||||
|
child: ListView.builder(
|
||||||
|
itemCount: publishers?.length ?? 0,
|
||||||
|
itemBuilder: (context, idx) {
|
||||||
|
final publisher = publishers![idx];
|
||||||
|
return ListTile(
|
||||||
|
title: Text(publisher.nick),
|
||||||
|
subtitle: Text('@${publisher.name}'),
|
||||||
|
leading: AccountImage(content: publisher.avatar, radius: 18),
|
||||||
|
onTap: () {
|
||||||
|
controller.setPublisher(publisher);
|
||||||
|
Navigator.pop(context, true);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PostStoryEditor extends StatelessWidget {
|
||||||
|
final PostWriteController controller;
|
||||||
|
final Function? onTapPublisher;
|
||||||
|
|
||||||
|
const _PostStoryEditor({required this.controller, this.onTapPublisher});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||||
|
constraints: const BoxConstraints(maxWidth: 640),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Material(
|
||||||
|
elevation: 2,
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(24)),
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
onTapPublisher?.call();
|
||||||
|
},
|
||||||
|
child: AccountImage(
|
||||||
|
content: controller.publisher?.avatar,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
const Gap(6),
|
||||||
|
TextField(
|
||||||
|
controller: controller.titleController,
|
||||||
|
decoration: InputDecoration.collapsed(
|
||||||
|
hintText: 'fieldPostTitle'.tr(),
|
||||||
|
border: InputBorder.none,
|
||||||
|
),
|
||||||
|
style: Theme.of(context).textTheme.titleLarge,
|
||||||
|
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
|
).padding(horizontal: 16),
|
||||||
|
const Gap(8),
|
||||||
|
TextField(
|
||||||
|
controller: controller.contentController,
|
||||||
|
maxLines: null,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: 'fieldPostContent'.tr(),
|
||||||
|
hintStyle: TextStyle(fontSize: 14),
|
||||||
|
isCollapsed: true,
|
||||||
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 16,
|
||||||
|
),
|
||||||
|
border: InputBorder.none,
|
||||||
|
),
|
||||||
|
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).padding(bottom: 8),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PostArticleEditor extends StatelessWidget {
|
||||||
|
final PostWriteController controller;
|
||||||
|
final Function? onTapPublisher;
|
||||||
|
|
||||||
|
const _PostArticleEditor({required this.controller, this.onTapPublisher});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final editorWidgets = <Widget>[
|
||||||
|
Material(
|
||||||
|
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||||
|
child: InkWell(
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
AccountImage(content: controller.publisher?.avatar, radius: 20),
|
||||||
|
const Gap(8),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(controller.publisher?.nick ?? 'loading'.tr()).bold(),
|
||||||
|
Text('@${controller.publisher?.name}'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).padding(horizontal: 12, vertical: 8),
|
||||||
|
onTap: () {
|
||||||
|
onTapPublisher?.call();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Gap(16),
|
||||||
|
TextField(
|
||||||
|
controller: controller.titleController,
|
||||||
|
decoration: InputDecoration.collapsed(
|
||||||
|
hintText: 'fieldPostTitle'.tr(),
|
||||||
|
border: InputBorder.none,
|
||||||
|
),
|
||||||
|
style: Theme.of(context).textTheme.titleLarge,
|
||||||
|
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
|
).padding(horizontal: 16),
|
||||||
|
const Gap(8),
|
||||||
|
TextField(
|
||||||
|
controller: controller.descriptionController,
|
||||||
|
decoration: InputDecoration.collapsed(
|
||||||
|
hintText: 'fieldPostDescription'.tr(),
|
||||||
|
border: InputBorder.none,
|
||||||
|
),
|
||||||
|
maxLines: null,
|
||||||
|
keyboardType: TextInputType.multiline,
|
||||||
|
style: Theme.of(context).textTheme.bodyLarge,
|
||||||
|
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
|
).padding(horizontal: 16),
|
||||||
|
const Gap(4),
|
||||||
|
];
|
||||||
|
|
||||||
|
if (ResponsiveBreakpoints.of(context).largerThan(MOBILE)) {
|
||||||
|
return Container(
|
||||||
|
constraints: const BoxConstraints(maxWidth: 640 * 2 + 8),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
...editorWidgets,
|
||||||
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: TextField(
|
||||||
|
controller: controller.contentController,
|
||||||
|
maxLines: null,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: 'fieldPostContent'.tr(),
|
||||||
|
hintStyle: TextStyle(fontSize: 14),
|
||||||
|
isCollapsed: true,
|
||||||
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 16,
|
||||||
|
),
|
||||||
|
border: InputBorder.none,
|
||||||
|
),
|
||||||
|
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Gap(8),
|
||||||
|
Expanded(
|
||||||
|
child: MarkdownTextContent(
|
||||||
|
content: controller.contentController.text,
|
||||||
|
).padding(horizontal: 24),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
...editorWidgets,
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.only(top: 8),
|
||||||
|
constraints: const BoxConstraints(maxWidth: 640),
|
||||||
|
child: TextField(
|
||||||
|
controller: controller.contentController,
|
||||||
|
maxLines: null,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: 'fieldPostContent'.tr(),
|
||||||
|
hintStyle: TextStyle(fontSize: 14),
|
||||||
|
isCollapsed: true,
|
||||||
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 16,
|
||||||
|
),
|
||||||
|
border: InputBorder.none,
|
||||||
|
),
|
||||||
|
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PostQuestionEditor extends StatelessWidget {
|
||||||
|
final PostWriteController controller;
|
||||||
|
final Function? onTapPublisher;
|
||||||
|
|
||||||
|
const _PostQuestionEditor({required this.controller, this.onTapPublisher});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||||
|
constraints: const BoxConstraints(maxWidth: 640),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Material(
|
||||||
|
elevation: 1,
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(24)),
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
onTapPublisher?.call();
|
||||||
|
},
|
||||||
|
child: AccountImage(
|
||||||
|
content: controller.publisher?.avatar,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
const Gap(6),
|
||||||
|
TextField(
|
||||||
|
controller: controller.titleController,
|
||||||
|
decoration: InputDecoration.collapsed(
|
||||||
|
hintText: 'fieldPostTitle'.tr(),
|
||||||
|
border: InputBorder.none,
|
||||||
|
),
|
||||||
|
style: Theme.of(context).textTheme.titleLarge,
|
||||||
|
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
|
).padding(horizontal: 16),
|
||||||
|
const Gap(8),
|
||||||
|
TextField(
|
||||||
|
controller: controller.rewardController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: 'fieldPostQuestionReward'.tr(),
|
||||||
|
suffixText: 'walletCurrencyShort'.tr(),
|
||||||
|
border: InputBorder.none,
|
||||||
|
isCollapsed: true,
|
||||||
|
),
|
||||||
|
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
|
).padding(horizontal: 16),
|
||||||
|
const Gap(8),
|
||||||
|
TextField(
|
||||||
|
controller: controller.contentController,
|
||||||
|
maxLines: null,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: 'fieldPostContent'.tr(),
|
||||||
|
hintStyle: TextStyle(fontSize: 14),
|
||||||
|
isCollapsed: true,
|
||||||
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 16,
|
||||||
|
),
|
||||||
|
border: InputBorder.none,
|
||||||
|
),
|
||||||
|
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).padding(top: 8),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -134,7 +134,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,8 +142,7 @@ 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) {
|
||||||
@ -152,17 +151,9 @@ class _PostSearchScreenState extends State<PostSearchScreen> {
|
|||||||
onDeleted: () {
|
onDeleted: () {
|
||||||
_refreshPosts();
|
_refreshPosts();
|
||||||
},
|
},
|
||||||
),
|
|
||||||
onTap: () {
|
|
||||||
GoRouter.of(context).pushNamed(
|
|
||||||
'postDetail',
|
|
||||||
pathParameters: {'slug': _posts[idx].id.toString()},
|
|
||||||
extra: _posts[idx],
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
separatorBuilder: (_, __) => const Gap(8),
|
||||||
},
|
|
||||||
separatorBuilder: (context, index) => const Divider(height: 1),
|
|
||||||
),
|
),
|
||||||
Positioned(
|
Positioned(
|
||||||
top: 16,
|
top: 16,
|
||||||
|
@ -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,
|
onDeleted: onDeleted,
|
||||||
),
|
|
||||||
onTap: () {
|
|
||||||
GoRouter.of(context).pushNamed(
|
|
||||||
'postDetail',
|
|
||||||
pathParameters: {'slug': posts[idx].id.toString()},
|
|
||||||
extra: posts[idx],
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
separatorBuilder: (_, __) => const Gap(8),
|
||||||
},
|
|
||||||
separatorBuilder: (context, index) => const Divider(height: 1),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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';
|
||||||
@ -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;
|
||||||
|
@ -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,9 +51,9 @@ 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: {
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
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';
|
||||||
@ -96,10 +98,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),
|
||||||
|
@ -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,30 @@ 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: [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(() {});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@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();
|
||||||
@ -144,10 +176,34 @@ 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();
|
||||||
|
if (!kIsWeb && !(Platform.isAndroid || Platform.isIOS)) hotKeyManager.unregister(_pasteHotKey);
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -280,6 +336,7 @@ 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;
|
||||||
@ -289,6 +346,19 @@ class ChatMessageInputState extends State<ChatMessageInput> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(
|
||||||
|
Symbols.mood,
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
),
|
||||||
|
visualDensity: const VisualDensity(
|
||||||
|
horizontal: -4,
|
||||||
|
vertical: -4,
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
_showEmojiPicker(context);
|
||||||
|
},
|
||||||
|
),
|
||||||
AddPostMediaButton(
|
AddPostMediaButton(
|
||||||
onAdd: (items) {
|
onAdd: (items) {
|
||||||
setState(() {
|
setState(() {
|
||||||
@ -314,3 +384,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(
|
||||||
|
child: UniversalImage(
|
||||||
sn.getAttachmentUrl(snapshot.data!.attachment.rid),
|
sn.getAttachmentUrl(snapshot.data!.attachment.rid),
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.contain,
|
||||||
width: size,
|
width: size,
|
||||||
height: size,
|
height: size,
|
||||||
cacheHeight: size,
|
cacheHeight: size,
|
||||||
cacheWidth: 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();
|
||||||
},
|
},
|
||||||
|
@ -8,17 +8,23 @@ 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';
|
||||||
|
|
||||||
|
import '../../providers/sn_network.dart';
|
||||||
|
|
||||||
class PostCommentSliverList extends StatefulWidget {
|
class PostCommentSliverList extends StatefulWidget {
|
||||||
final int parentPostId;
|
final SnPost parentPost;
|
||||||
final double? maxWidth;
|
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 +43,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,6 +54,21 @@ 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();
|
||||||
_fetchPosts();
|
_fetchPosts();
|
||||||
@ -71,6 +92,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 +116,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 +145,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 +164,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 +172,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,6 +23,7 @@ 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';
|
||||||
@ -38,6 +40,65 @@ 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 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 +108,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 +120,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) {
|
||||||
@ -142,6 +205,7 @@ 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) {}
|
||||||
},
|
},
|
||||||
@ -224,10 +288,12 @@ 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.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,
|
||||||
@ -333,6 +399,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 +505,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,7 +620,7 @@ 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,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -652,6 +743,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 +754,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 {
|
||||||
@ -760,6 +853,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(
|
||||||
@ -833,7 +940,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),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -1139,8 +1246,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 +1283,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 +1299,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 +1307,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 +1326,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 +1422,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 +1444,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;
|
||||||
|
|
||||||
|
try {
|
||||||
final document = XmlDocument.parse(out);
|
final document = XmlDocument.parse(out);
|
||||||
_thinkingProcess = document.getElement('think')?.innerText.trim();
|
_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) {
|
||||||
|
@ -292,7 +292,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 +337,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).dividerColor,
|
|
||||||
width: 1,
|
|
||||||
),
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||||
),
|
borderRadius: BorderRadius.circular(8),
|
||||||
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(
|
||||||
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -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"))
|
||||||
|
@ -14,59 +14,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 +87,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,6 +137,10 @@ 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.5):
|
||||||
@ -174,6 +178,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):
|
||||||
@ -198,6 +204,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 +217,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 +233,7 @@ SPEC REPOS:
|
|||||||
- GoogleAppMeasurement
|
- GoogleAppMeasurement
|
||||||
- GoogleDataTransport
|
- GoogleDataTransport
|
||||||
- GoogleUtilities
|
- GoogleUtilities
|
||||||
|
- HotKey
|
||||||
- nanopb
|
- nanopb
|
||||||
- OrderedSet
|
- OrderedSet
|
||||||
- PromisesObjC
|
- PromisesObjC
|
||||||
@ -262,6 +271,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 +297,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:
|
||||||
@ -301,23 +314,25 @@ SPEC CHECKSUMS:
|
|||||||
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: 91c68237edede55f8891a166a28c1daec8a1e4b1
|
||||||
media_kit_libs_macos_video: b3e2bbec2eef97c285f2b1baa7963c67c753fb82
|
media_kit_libs_macos_video: b3e2bbec2eef97c285f2b1baa7963c67c753fb82
|
||||||
@ -334,6 +349,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
|
||||||
|
124
pubspec.lock
124
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
|
||||||
@ -538,34 +538,34 @@ packages:
|
|||||||
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 +578,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 +764,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: flutter_markdown
|
name: flutter_markdown
|
||||||
sha256: e37f4c69a07b07bb92622ef6b131a53c9aae48f64b176340af9e8e5238718487
|
sha256: b3ff1ef5fb3924ee02b4d38b974ffae3969d50603e68787684ee9dd45f6f144a
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.5"
|
version: "0.7.6+1"
|
||||||
flutter_native_splash:
|
flutter_native_splash:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
@ -938,6 +938,46 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.0"
|
version: "0.7.0"
|
||||||
|
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:
|
||||||
@ -1282,6 +1322,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 +1382,18 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: package_info_plus
|
name: package_info_plus
|
||||||
sha256: b15fad91c4d3d1f2b48c053dd41cb82da007c27407dc9ab5f9aa59881d0e39d4
|
sha256: c447a3c3e7be4addf129b8f9ab6a4bd5d166b78918223e223b61fddf4d07e254
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "8.1.4"
|
version: "8.2.0"
|
||||||
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:
|
||||||
@ -1778,6 +1826,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.1"
|
version: "2.0.1"
|
||||||
|
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 +2007,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 +2023,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 +2131,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:
|
||||||
|
@ -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+64
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.5.4
|
sdk: ^3.5.4
|
||||||
@ -118,6 +118,8 @@ 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
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
@ -152,6 +154,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