Compare commits
21 Commits
Author | SHA1 | Date | |
---|---|---|---|
0dcfcaad56 | |||
687e720956 | |||
180876949e | |||
9718965809 | |||
5377161fb0 | |||
963e538ae5 | |||
a355e3bf90 | |||
cb4a2598c8 | |||
950612dc07 | |||
cbd1eaf1af | |||
ac41cbd99f | |||
9f9c90abc4 | |||
87029e3538 | |||
127d9adc09 | |||
c82dc7ad85 | |||
36bcff7a7c | |||
38201b547a | |||
ed0334fcda | |||
fbb486b90b | |||
9b34f385d5 | |||
bb7b731602 |
@ -19,6 +19,10 @@
|
|||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:enableOnBackInvokedCallback="true"
|
android:enableOnBackInvokedCallback="true"
|
||||||
android:requestLegacyExternalStorage="true">
|
android:requestLegacyExternalStorage="true">
|
||||||
|
<meta-data
|
||||||
|
android:name="flutterEmbedding"
|
||||||
|
android:value="2" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
|
11
api/Reader/List News Sources.bru
Normal file
11
api/Reader/List News Sources.bru
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
meta {
|
||||||
|
name: List News Sources
|
||||||
|
type: http
|
||||||
|
seq: 3
|
||||||
|
}
|
||||||
|
|
||||||
|
get {
|
||||||
|
url: {{endpoint}}/cgi/re/well-known/sources
|
||||||
|
body: none
|
||||||
|
auth: none
|
||||||
|
}
|
17
api/Reader/List News.bru
Normal file
17
api/Reader/List News.bru
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
meta {
|
||||||
|
name: List News
|
||||||
|
type: http
|
||||||
|
seq: 2
|
||||||
|
}
|
||||||
|
|
||||||
|
get {
|
||||||
|
url: {{endpoint}}/cgi/re/news?take=10&offset=0&source=shadiao
|
||||||
|
body: none
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
params:query {
|
||||||
|
take: 10
|
||||||
|
offset: 0
|
||||||
|
source: shadiao
|
||||||
|
}
|
18
api/Reader/Trigger Scan News.bru
Normal file
18
api/Reader/Trigger Scan News.bru
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
meta {
|
||||||
|
name: Trigger Scan News
|
||||||
|
type: http
|
||||||
|
seq: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
post {
|
||||||
|
url: {{endpoint}}/cgi/re/admin/scan
|
||||||
|
body: json
|
||||||
|
auth: inherit
|
||||||
|
}
|
||||||
|
|
||||||
|
body:json {
|
||||||
|
{
|
||||||
|
"sources": ["taiwan-ltn"],
|
||||||
|
"eager": true
|
||||||
|
}
|
||||||
|
}
|
@ -17,6 +17,7 @@
|
|||||||
"screenAccountProfileEdit": "Edit Profile",
|
"screenAccountProfileEdit": "Edit Profile",
|
||||||
"screenAbuseReport": "Abuse Reports",
|
"screenAbuseReport": "Abuse Reports",
|
||||||
"screenSettings": "Settings",
|
"screenSettings": "Settings",
|
||||||
|
"screenNews": "News",
|
||||||
"screenAlbum": "Album",
|
"screenAlbum": "Album",
|
||||||
"screenChat": "Chat",
|
"screenChat": "Chat",
|
||||||
"screenChatManage": "Edit Channel",
|
"screenChatManage": "Edit Channel",
|
||||||
@ -193,6 +194,13 @@
|
|||||||
"settingsColorSchemeDescription": "Set the application primary color.",
|
"settingsColorSchemeDescription": "Set the application primary color.",
|
||||||
"settingsColorSeed": "Color Seed",
|
"settingsColorSeed": "Color Seed",
|
||||||
"settingsColorSeedDescription": "Select one of the present color schemes.",
|
"settingsColorSeedDescription": "Select one of the present color schemes.",
|
||||||
|
"settingsFeatures": "Features",
|
||||||
|
"settingsNotifyWithHaptic": "Haptic when Notified",
|
||||||
|
"settingsNotifyWithHapticDescription": "Vibrate lightly when a new notification appears in the foreground.",
|
||||||
|
"settingsExpandPostLink": "Expand Post Link",
|
||||||
|
"settingsExpandPostLinkDescription": "Expand the post link in the post list.",
|
||||||
|
"settingsExpandChatLink": "Expand Chat Link",
|
||||||
|
"settingsExpandChatLinkDescription": "Expand the chat link in the chat list.",
|
||||||
"settingsNetwork": "Network",
|
"settingsNetwork": "Network",
|
||||||
"settingsNetworkServer": "HyperNet Server",
|
"settingsNetworkServer": "HyperNet Server",
|
||||||
"settingsNetworkServerDescription": "Set the HyperNet server address, choose ours or build your own.",
|
"settingsNetworkServerDescription": "Set the HyperNet server address, choose ours or build your own.",
|
||||||
@ -551,5 +559,11 @@
|
|||||||
"postCategoryKnowledge": "Knowledge",
|
"postCategoryKnowledge": "Knowledge",
|
||||||
"postCategoryLiterature": "Literature",
|
"postCategoryLiterature": "Literature",
|
||||||
"postCategoryFunny": "Funny",
|
"postCategoryFunny": "Funny",
|
||||||
"postCategoryUncategorized": "Uncategorized"
|
"postCategoryUncategorized": "Uncategorized",
|
||||||
|
"newsAllSources": "All News",
|
||||||
|
"newsReadingProviderSwap": "Swap",
|
||||||
|
"newsReadingFromReader": "You're reading from HyperNet.Reader",
|
||||||
|
"newsReadingFromOriginal": "You're reading the original article",
|
||||||
|
"newsDisclaimer": "This article is fetched from the Internet, we do not guarantee its authenticity, please judge for yourself. All content in this article belongs to the original author.",
|
||||||
|
"newsToday": "Today's News"
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
"screenAccountProfileEdit": "编辑资料",
|
"screenAccountProfileEdit": "编辑资料",
|
||||||
"screenAbuseReport": "滥用检举",
|
"screenAbuseReport": "滥用检举",
|
||||||
"screenSettings": "设置",
|
"screenSettings": "设置",
|
||||||
|
"screenNews": "新闻",
|
||||||
"screenAlbum": "相册",
|
"screenAlbum": "相册",
|
||||||
"screenChat": "聊天",
|
"screenChat": "聊天",
|
||||||
"screenChatManage": "编辑聊天频道",
|
"screenChatManage": "编辑聊天频道",
|
||||||
@ -191,6 +192,13 @@
|
|||||||
"settingsColorSchemeDescription": "设置应用主题色。",
|
"settingsColorSchemeDescription": "设置应用主题色。",
|
||||||
"settingsColorSeed": "预设色彩主题",
|
"settingsColorSeed": "预设色彩主题",
|
||||||
"settingsColorSeedDescription": "选择一个预设色彩主题。",
|
"settingsColorSeedDescription": "选择一个预设色彩主题。",
|
||||||
|
"settingsFeatures": "功能",
|
||||||
|
"settingsNotifyWithHaptic": "新通知时振动",
|
||||||
|
"settingsNotifyWithHapticDescription": "在应用在前台时收到新通知出现时出发轻量的振动。",
|
||||||
|
"settingsExpandPostLink": "展开帖子链接",
|
||||||
|
"settingsExpandPostLinkDescription": "在帖子列表中展开显示帖子中的链接。",
|
||||||
|
"settingsExpandChatLink": "展开聊天链接",
|
||||||
|
"settingsExpandChatLinkDescription": "在聊天信息中展开显示内容中的链接。",
|
||||||
"settingsNetwork": "网络",
|
"settingsNetwork": "网络",
|
||||||
"settingsNetworkServer": "HyperNet 服务器",
|
"settingsNetworkServer": "HyperNet 服务器",
|
||||||
"settingsNetworkServerDescription": "设置 HyperNet 服务器地址,选择我们提供的,或者自己搭建。",
|
"settingsNetworkServerDescription": "设置 HyperNet 服务器地址,选择我们提供的,或者自己搭建。",
|
||||||
@ -549,5 +557,11 @@
|
|||||||
"postCategoryKnowledge": "知识",
|
"postCategoryKnowledge": "知识",
|
||||||
"postCategoryLiterature": "文学",
|
"postCategoryLiterature": "文学",
|
||||||
"postCategoryFunny": "搞笑",
|
"postCategoryFunny": "搞笑",
|
||||||
"postCategoryUncategorized": "未分类"
|
"postCategoryUncategorized": "未分类",
|
||||||
|
"newsAllSources": "所有新闻",
|
||||||
|
"newsReadingProviderSwap": "切换",
|
||||||
|
"newsReadingFromReader": "你正在从 HyperNet.Reader 阅读文章",
|
||||||
|
"newsReadingFromOriginal": "你正在阅读原始文章",
|
||||||
|
"newsDisclaimer": "本文由 HyperNet.Reader 从互联网上获取,我们不担保其内容的真实性,请自行判断。本文章的所有内容版权归原作者所有。",
|
||||||
|
"newsToday": "快讯"
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
"screenAccountProfileEdit": "編輯資料",
|
"screenAccountProfileEdit": "編輯資料",
|
||||||
"screenAbuseReport": "濫用檢舉",
|
"screenAbuseReport": "濫用檢舉",
|
||||||
"screenSettings": "設置",
|
"screenSettings": "設置",
|
||||||
|
"screenNews": "新聞",
|
||||||
"screenAlbum": "相冊",
|
"screenAlbum": "相冊",
|
||||||
"screenChat": "聊天",
|
"screenChat": "聊天",
|
||||||
"screenChatManage": "編輯聊天頻道",
|
"screenChatManage": "編輯聊天頻道",
|
||||||
@ -191,6 +192,13 @@
|
|||||||
"settingsColorSchemeDescription": "設置應用主題色。",
|
"settingsColorSchemeDescription": "設置應用主題色。",
|
||||||
"settingsColorSeed": "預設色彩主題",
|
"settingsColorSeed": "預設色彩主題",
|
||||||
"settingsColorSeedDescription": "選擇一個預設色彩主題。",
|
"settingsColorSeedDescription": "選擇一個預設色彩主題。",
|
||||||
|
"settingsFeatures": "功能",
|
||||||
|
"settingsNotifyWithHaptic": "新通知時振動",
|
||||||
|
"settingsNotifyWithHapticDescription": "在應用在前台時收到新通知出現時出發輕量的振動。",
|
||||||
|
"settingsExpandPostLink": "展開帖子鏈接",
|
||||||
|
"settingsExpandPostLinkDescription": "在帖子列表中展開顯示帖子中的鏈接。",
|
||||||
|
"settingsExpandChatLink": "展開聊天鏈接",
|
||||||
|
"settingsExpandChatLinkDescription": "在聊天信息中展開顯示內容中的鏈接。",
|
||||||
"settingsNetwork": "網絡",
|
"settingsNetwork": "網絡",
|
||||||
"settingsNetworkServer": "HyperNet 服務器",
|
"settingsNetworkServer": "HyperNet 服務器",
|
||||||
"settingsNetworkServerDescription": "設置 HyperNet 服務器地址,選擇我們提供的,或者自己搭建。",
|
"settingsNetworkServerDescription": "設置 HyperNet 服務器地址,選擇我們提供的,或者自己搭建。",
|
||||||
@ -213,8 +221,9 @@
|
|||||||
"sensitiveContentCollapsed": "敏感內容已摺疊。",
|
"sensitiveContentCollapsed": "敏感內容已摺疊。",
|
||||||
"sensitiveContentDescription": "此內容已被標記,可能不適合所有人查看。",
|
"sensitiveContentDescription": "此內容已被標記,可能不適合所有人查看。",
|
||||||
"sensitiveContentReveal": "顯示內容",
|
"sensitiveContentReveal": "顯示內容",
|
||||||
"serverConnecting": "正在連接服務器…",
|
"serverConnecting": "正在連接…",
|
||||||
"serverDisconnected": "已與服務器斷開連接",
|
"serverDisconnected": "已斷開連接",
|
||||||
|
"serverConnected": "已連接",
|
||||||
"fieldChatAlias": "頻道別名",
|
"fieldChatAlias": "頻道別名",
|
||||||
"fieldChatAliasHint": "全站範圍內唯一的頻道別名,用於在 URL 中表示該頻道,留空則自動生成。應遵循 URL-Safe 的原則。",
|
"fieldChatAliasHint": "全站範圍內唯一的頻道別名,用於在 URL 中表示該頻道,留空則自動生成。應遵循 URL-Safe 的原則。",
|
||||||
"fieldChatName": "名稱",
|
"fieldChatName": "名稱",
|
||||||
@ -292,6 +301,7 @@
|
|||||||
"addAttachmentFromCameraPhoto": "拍攝照片",
|
"addAttachmentFromCameraPhoto": "拍攝照片",
|
||||||
"addAttachmentFromCameraVideo": "拍攝視頻",
|
"addAttachmentFromCameraVideo": "拍攝視頻",
|
||||||
"addAttachmentFromRandomId": "通過訪問 ID 鏈接",
|
"addAttachmentFromRandomId": "通過訪問 ID 鏈接",
|
||||||
|
"attachmentDetailInfo": "附件詳細信息",
|
||||||
"attachmentPastedImage": "粘貼的圖片",
|
"attachmentPastedImage": "粘貼的圖片",
|
||||||
"attachmentInsertLink": "插入連接",
|
"attachmentInsertLink": "插入連接",
|
||||||
"attachmentSetAsPostThumbnail": "設置為帖子縮略圖",
|
"attachmentSetAsPostThumbnail": "設置為帖子縮略圖",
|
||||||
@ -547,5 +557,11 @@
|
|||||||
"postCategoryKnowledge": "知識",
|
"postCategoryKnowledge": "知識",
|
||||||
"postCategoryLiterature": "文學",
|
"postCategoryLiterature": "文學",
|
||||||
"postCategoryFunny": "搞笑",
|
"postCategoryFunny": "搞笑",
|
||||||
"postCategoryUncategorized": "未分類"
|
"postCategoryUncategorized": "未分類",
|
||||||
|
"newsAllSources": "所有新聞",
|
||||||
|
"newsReadingProviderSwap": "切換",
|
||||||
|
"newsReadingFromReader": "你正在從 HyperNet.Reader 閲讀文章",
|
||||||
|
"newsReadingFromOriginal": "你正在閲讀原始文章",
|
||||||
|
"newsDisclaimer": "本文由 HyperNet.Reader 從互聯網上獲取,我們不擔保其內容的真實性,請自行判斷。本文章的所有內容版權歸原作者所有。",
|
||||||
|
"newsToday": "快訊"
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
"screenAccountProfileEdit": "編輯資料",
|
"screenAccountProfileEdit": "編輯資料",
|
||||||
"screenAbuseReport": "濫用檢舉",
|
"screenAbuseReport": "濫用檢舉",
|
||||||
"screenSettings": "設置",
|
"screenSettings": "設置",
|
||||||
|
"screenNews": "新聞",
|
||||||
"screenAlbum": "相冊",
|
"screenAlbum": "相冊",
|
||||||
"screenChat": "聊天",
|
"screenChat": "聊天",
|
||||||
"screenChatManage": "編輯聊天頻道",
|
"screenChatManage": "編輯聊天頻道",
|
||||||
@ -191,6 +192,13 @@
|
|||||||
"settingsColorSchemeDescription": "設置應用主題色。",
|
"settingsColorSchemeDescription": "設置應用主題色。",
|
||||||
"settingsColorSeed": "預設色彩主題",
|
"settingsColorSeed": "預設色彩主題",
|
||||||
"settingsColorSeedDescription": "選擇一個預設色彩主題。",
|
"settingsColorSeedDescription": "選擇一個預設色彩主題。",
|
||||||
|
"settingsFeatures": "功能",
|
||||||
|
"settingsNotifyWithHaptic": "新通知時振動",
|
||||||
|
"settingsNotifyWithHapticDescription": "在應用在前臺時收到新通知出現時出發輕量的振動。",
|
||||||
|
"settingsExpandPostLink": "展開帖子鏈接",
|
||||||
|
"settingsExpandPostLinkDescription": "在帖子列表中展開顯示帖子中的鏈接。",
|
||||||
|
"settingsExpandChatLink": "展開聊天鏈接",
|
||||||
|
"settingsExpandChatLinkDescription": "在聊天信息中展開顯示內容中的鏈接。",
|
||||||
"settingsNetwork": "網絡",
|
"settingsNetwork": "網絡",
|
||||||
"settingsNetworkServer": "HyperNet 服務器",
|
"settingsNetworkServer": "HyperNet 服務器",
|
||||||
"settingsNetworkServerDescription": "設置 HyperNet 服務器地址,選擇我們提供的,或者自己搭建。",
|
"settingsNetworkServerDescription": "設置 HyperNet 服務器地址,選擇我們提供的,或者自己搭建。",
|
||||||
@ -213,8 +221,9 @@
|
|||||||
"sensitiveContentCollapsed": "敏感內容已摺疊。",
|
"sensitiveContentCollapsed": "敏感內容已摺疊。",
|
||||||
"sensitiveContentDescription": "此內容已被標記,可能不適合所有人查看。",
|
"sensitiveContentDescription": "此內容已被標記,可能不適合所有人查看。",
|
||||||
"sensitiveContentReveal": "顯示內容",
|
"sensitiveContentReveal": "顯示內容",
|
||||||
"serverConnecting": "正在連接服務器…",
|
"serverConnecting": "正在連接…",
|
||||||
"serverDisconnected": "已與服務器斷開連接",
|
"serverDisconnected": "已斷開連接",
|
||||||
|
"serverConnected": "已連接",
|
||||||
"fieldChatAlias": "頻道別名",
|
"fieldChatAlias": "頻道別名",
|
||||||
"fieldChatAliasHint": "全站範圍內唯一的頻道別名,用於在 URL 中表示該頻道,留空則自動生成。應遵循 URL-Safe 的原則。",
|
"fieldChatAliasHint": "全站範圍內唯一的頻道別名,用於在 URL 中表示該頻道,留空則自動生成。應遵循 URL-Safe 的原則。",
|
||||||
"fieldChatName": "名稱",
|
"fieldChatName": "名稱",
|
||||||
@ -292,6 +301,7 @@
|
|||||||
"addAttachmentFromCameraPhoto": "拍攝照片",
|
"addAttachmentFromCameraPhoto": "拍攝照片",
|
||||||
"addAttachmentFromCameraVideo": "拍攝視頻",
|
"addAttachmentFromCameraVideo": "拍攝視頻",
|
||||||
"addAttachmentFromRandomId": "通過訪問 ID 鏈接",
|
"addAttachmentFromRandomId": "通過訪問 ID 鏈接",
|
||||||
|
"attachmentDetailInfo": "附件詳細信息",
|
||||||
"attachmentPastedImage": "粘貼的圖片",
|
"attachmentPastedImage": "粘貼的圖片",
|
||||||
"attachmentInsertLink": "插入連接",
|
"attachmentInsertLink": "插入連接",
|
||||||
"attachmentSetAsPostThumbnail": "設置為帖子縮略圖",
|
"attachmentSetAsPostThumbnail": "設置為帖子縮略圖",
|
||||||
@ -547,5 +557,11 @@
|
|||||||
"postCategoryKnowledge": "知識",
|
"postCategoryKnowledge": "知識",
|
||||||
"postCategoryLiterature": "文學",
|
"postCategoryLiterature": "文學",
|
||||||
"postCategoryFunny": "搞笑",
|
"postCategoryFunny": "搞笑",
|
||||||
"postCategoryUncategorized": "未分類"
|
"postCategoryUncategorized": "未分類",
|
||||||
|
"newsAllSources": "所有新聞",
|
||||||
|
"newsReadingProviderSwap": "切換",
|
||||||
|
"newsReadingFromReader": "你正在從 HyperNet.Reader 閱讀文章",
|
||||||
|
"newsReadingFromOriginal": "你正在閱讀原始文章",
|
||||||
|
"newsDisclaimer": "本文由 HyperNet.Reader 從互聯網上獲取,我們不擔保其內容的真實性,請自行判斷。本文章的所有內容版權歸原作者所有。",
|
||||||
|
"newsToday": "快訊"
|
||||||
}
|
}
|
||||||
|
@ -105,6 +105,13 @@ PODS:
|
|||||||
- Flutter (1.0.0)
|
- Flutter (1.0.0)
|
||||||
- flutter_app_update (0.0.1):
|
- flutter_app_update (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
|
- flutter_inappwebview_ios (0.0.1):
|
||||||
|
- Flutter
|
||||||
|
- flutter_inappwebview_ios/Core (= 0.0.1)
|
||||||
|
- OrderedSet (~> 6.0.3)
|
||||||
|
- flutter_inappwebview_ios/Core (0.0.1):
|
||||||
|
- Flutter
|
||||||
|
- OrderedSet (~> 6.0.3)
|
||||||
- flutter_native_splash (2.4.3):
|
- flutter_native_splash (2.4.3):
|
||||||
- Flutter
|
- Flutter
|
||||||
- flutter_udid (0.0.1):
|
- flutter_udid (0.0.1):
|
||||||
@ -188,6 +195,7 @@ PODS:
|
|||||||
- nanopb/encode (= 3.30910.0)
|
- nanopb/encode (= 3.30910.0)
|
||||||
- nanopb/decode (3.30910.0)
|
- nanopb/decode (3.30910.0)
|
||||||
- nanopb/encode (3.30910.0)
|
- nanopb/encode (3.30910.0)
|
||||||
|
- OrderedSet (6.0.3)
|
||||||
- package_info_plus (0.4.5):
|
- package_info_plus (0.4.5):
|
||||||
- Flutter
|
- Flutter
|
||||||
- pasteboard (0.0.1):
|
- pasteboard (0.0.1):
|
||||||
@ -239,6 +247,7 @@ DEPENDENCIES:
|
|||||||
- firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`)
|
- firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`)
|
||||||
- Flutter (from `Flutter`)
|
- Flutter (from `Flutter`)
|
||||||
- flutter_app_update (from `.symlinks/plugins/flutter_app_update/ios`)
|
- flutter_app_update (from `.symlinks/plugins/flutter_app_update/ios`)
|
||||||
|
- flutter_inappwebview_ios (from `.symlinks/plugins/flutter_inappwebview_ios/ios`)
|
||||||
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
|
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
|
||||||
- flutter_udid (from `.symlinks/plugins/flutter_udid/ios`)
|
- flutter_udid (from `.symlinks/plugins/flutter_udid/ios`)
|
||||||
- flutter_webrtc (from `.symlinks/plugins/flutter_webrtc/ios`)
|
- flutter_webrtc (from `.symlinks/plugins/flutter_webrtc/ios`)
|
||||||
@ -282,6 +291,7 @@ SPEC REPOS:
|
|||||||
- GoogleUtilities
|
- GoogleUtilities
|
||||||
- Kingfisher
|
- Kingfisher
|
||||||
- nanopb
|
- nanopb
|
||||||
|
- OrderedSet
|
||||||
- PromisesObjC
|
- PromisesObjC
|
||||||
- SAMKeychain
|
- SAMKeychain
|
||||||
- SDWebImage
|
- SDWebImage
|
||||||
@ -309,6 +319,8 @@ EXTERNAL SOURCES:
|
|||||||
:path: Flutter
|
:path: Flutter
|
||||||
flutter_app_update:
|
flutter_app_update:
|
||||||
:path: ".symlinks/plugins/flutter_app_update/ios"
|
:path: ".symlinks/plugins/flutter_app_update/ios"
|
||||||
|
flutter_inappwebview_ios:
|
||||||
|
:path: ".symlinks/plugins/flutter_inappwebview_ios/ios"
|
||||||
flutter_native_splash:
|
flutter_native_splash:
|
||||||
:path: ".symlinks/plugins/flutter_native_splash/ios"
|
:path: ".symlinks/plugins/flutter_native_splash/ios"
|
||||||
flutter_udid:
|
flutter_udid:
|
||||||
@ -380,6 +392,7 @@ SPEC CHECKSUMS:
|
|||||||
FirebaseMessaging: e1aca1fcc23e8b9eddb0e33f375ff90944623021
|
FirebaseMessaging: e1aca1fcc23e8b9eddb0e33f375ff90944623021
|
||||||
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
||||||
flutter_app_update: 65f61da626cb111d1b24674abc4b01728d7723bc
|
flutter_app_update: 65f61da626cb111d1b24674abc4b01728d7723bc
|
||||||
|
flutter_inappwebview_ios: 6f63631e2c62a7c350263b13fa5427aedefe81d4
|
||||||
flutter_native_splash: f71420956eb811e6d310720fee915f1d42852e7a
|
flutter_native_splash: f71420956eb811e6d310720fee915f1d42852e7a
|
||||||
flutter_udid: b2417673f287ee62817a1de3d1643f47b9f508ab
|
flutter_udid: b2417673f287ee62817a1de3d1643f47b9f508ab
|
||||||
flutter_webrtc: 90260f83024b1b96d239a575ea4e3708e79344d1
|
flutter_webrtc: 90260f83024b1b96d239a575ea4e3708e79344d1
|
||||||
@ -396,6 +409,7 @@ SPEC CHECKSUMS:
|
|||||||
media_kit_native_event_loop: e6b2ab20cf0746eb1c33be961fcf79667304fa2a
|
media_kit_native_event_loop: e6b2ab20cf0746eb1c33be961fcf79667304fa2a
|
||||||
media_kit_video: 5da63f157170e5bf303bf85453b7ef6971218a2e
|
media_kit_video: 5da63f157170e5bf303bf85453b7ef6971218a2e
|
||||||
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
|
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
|
||||||
|
OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94
|
||||||
package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4
|
package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4
|
||||||
pasteboard: 982969ebaa7c78af3e6cc7761e8f5e77565d9ce0
|
pasteboard: 982969ebaa7c78af3e6cc7761e8f5e77565d9ce0
|
||||||
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
|
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
|
||||||
|
@ -14,6 +14,9 @@ const kAppbarTransparentStoreKey = 'app_bar_transparent';
|
|||||||
const kAppBackgroundStoreKey = 'app_has_background';
|
const kAppBackgroundStoreKey = 'app_has_background';
|
||||||
const kAppColorSchemeStoreKey = 'app_color_scheme';
|
const kAppColorSchemeStoreKey = 'app_color_scheme';
|
||||||
const kAppDrawerPreferCollapse = 'app_drawer_prefer_collapse';
|
const kAppDrawerPreferCollapse = 'app_drawer_prefer_collapse';
|
||||||
|
const kAppNotifyWithHaptic = 'app_notify_with_haptic';
|
||||||
|
const kAppExpandPostLink = 'app_expand_post_link';
|
||||||
|
const kAppExpandChatLink = 'app_expand_chat_link';
|
||||||
|
|
||||||
const Map<String, FilterQuality> kImageQualityLevel = {
|
const Map<String, FilterQuality> kImageQualityLevel = {
|
||||||
'settingsImageQualityLowest': FilterQuality.none,
|
'settingsImageQualityLowest': FilterQuality.none,
|
||||||
@ -38,14 +41,22 @@ class ConfigProvider extends ChangeNotifier {
|
|||||||
bool drawerIsCollapsed = false;
|
bool drawerIsCollapsed = false;
|
||||||
bool drawerIsExpanded = false;
|
bool drawerIsExpanded = false;
|
||||||
|
|
||||||
void calcDrawerSize(BuildContext context) {
|
void calcDrawerSize(BuildContext context, {bool withMediaQuery = false}) {
|
||||||
|
bool newDrawerIsCollapsed = false;
|
||||||
|
bool newDrawerIsExpanded = false;
|
||||||
|
if (withMediaQuery) {
|
||||||
|
newDrawerIsCollapsed = MediaQuery.of(context).size.width < 450;
|
||||||
|
newDrawerIsExpanded = MediaQuery.of(context).size.width >= 451;
|
||||||
|
} else {
|
||||||
final rpb = ResponsiveBreakpoints.of(context);
|
final rpb = ResponsiveBreakpoints.of(context);
|
||||||
final newDrawerIsCollapsed = rpb.smallerOrEqualTo(MOBILE);
|
newDrawerIsCollapsed = rpb.smallerOrEqualTo(MOBILE);
|
||||||
final newDrawerIsExpanded = rpb.largerThan(TABLET)
|
newDrawerIsExpanded = rpb.largerThan(TABLET)
|
||||||
? (prefs.getBool(kAppDrawerPreferCollapse) ?? false)
|
? (prefs.getBool(kAppDrawerPreferCollapse) ?? false)
|
||||||
? false
|
? false
|
||||||
: true
|
: true
|
||||||
: false;
|
: false;
|
||||||
|
}
|
||||||
|
|
||||||
if (newDrawerIsExpanded != drawerIsExpanded || newDrawerIsCollapsed != drawerIsCollapsed) {
|
if (newDrawerIsExpanded != drawerIsExpanded || newDrawerIsCollapsed != drawerIsCollapsed) {
|
||||||
drawerIsExpanded = newDrawerIsExpanded;
|
drawerIsExpanded = newDrawerIsExpanded;
|
||||||
drawerIsCollapsed = newDrawerIsCollapsed;
|
drawerIsCollapsed = newDrawerIsCollapsed;
|
||||||
|
@ -58,6 +58,11 @@ class NavigationProvider extends ChangeNotifier {
|
|||||||
screen: 'realm',
|
screen: 'realm',
|
||||||
label: 'screenRealm',
|
label: 'screenRealm',
|
||||||
),
|
),
|
||||||
|
AppNavDestination(
|
||||||
|
icon: Icon(Symbols.newspaper, weight: 400, opticalSize: 20),
|
||||||
|
screen: 'news',
|
||||||
|
label: 'screenNews',
|
||||||
|
),
|
||||||
AppNavDestination(
|
AppNavDestination(
|
||||||
icon: Icon(Symbols.photo_library, weight: 400, opticalSize: 20),
|
icon: Icon(Symbols.photo_library, weight: 400, opticalSize: 20),
|
||||||
screen: 'album',
|
screen: 'album',
|
||||||
@ -83,8 +88,7 @@ class NavigationProvider extends ChangeNotifier {
|
|||||||
|
|
||||||
List<AppNavDestination> destinations = [];
|
List<AppNavDestination> destinations = [];
|
||||||
|
|
||||||
int get pinnedDestinationCount =>
|
int get pinnedDestinationCount => destinations.where((ele) => ele.isPinned).length;
|
||||||
destinations.where((ele) => ele.isPinned).length;
|
|
||||||
|
|
||||||
NavigationProvider() {
|
NavigationProvider() {
|
||||||
buildDestinations(kDefaultPinnedDestination);
|
buildDestinations(kDefaultPinnedDestination);
|
||||||
@ -113,17 +117,13 @@ class NavigationProvider extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool isIndexInRange(int min, int max) {
|
bool isIndexInRange(int min, int max) {
|
||||||
return _currentIndex != null &&
|
return _currentIndex != null && _currentIndex! >= min && _currentIndex! < max;
|
||||||
_currentIndex! >= min &&
|
|
||||||
_currentIndex! < max;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void autoDetectIndex(GoRouter? state) {
|
void autoDetectIndex(GoRouter? state) {
|
||||||
if (state == null) return;
|
if (state == null) return;
|
||||||
final idx = destinations.indexWhere(
|
final idx = destinations.indexWhere(
|
||||||
(ele) =>
|
(ele) => ele.screen == state.routerDelegate.currentConfiguration.last.route.name,
|
||||||
ele.screen ==
|
|
||||||
state.routerDelegate.currentConfiguration.last.route.name,
|
|
||||||
);
|
);
|
||||||
_currentIndex = idx == -1 ? null : idx;
|
_currentIndex = idx == -1 ? null : idx;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
@ -4,8 +4,10 @@ import 'dart:io';
|
|||||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_udid/flutter_udid.dart';
|
import 'package:flutter_udid/flutter_udid.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.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/providers/websocket.dart';
|
import 'package:surface/providers/websocket.dart';
|
||||||
@ -15,11 +17,13 @@ class NotificationProvider extends ChangeNotifier {
|
|||||||
late final SnNetworkProvider _sn;
|
late final SnNetworkProvider _sn;
|
||||||
late final UserProvider _ua;
|
late final UserProvider _ua;
|
||||||
late final WebSocketProvider _ws;
|
late final WebSocketProvider _ws;
|
||||||
|
late final ConfigProvider _cfg;
|
||||||
|
|
||||||
NotificationProvider(BuildContext context) {
|
NotificationProvider(BuildContext context) {
|
||||||
_sn = context.read<SnNetworkProvider>();
|
_sn = context.read<SnNetworkProvider>();
|
||||||
_ua = context.read<UserProvider>();
|
_ua = context.read<UserProvider>();
|
||||||
_ws = context.read<WebSocketProvider>();
|
_ws = context.read<WebSocketProvider>();
|
||||||
|
_cfg = context.read<ConfigProvider>();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> registerPushNotifications() async {
|
Future<void> registerPushNotifications() async {
|
||||||
@ -75,6 +79,8 @@ class NotificationProvider extends ChangeNotifier {
|
|||||||
final notification = SnNotification.fromJson(event.payload!);
|
final notification = SnNotification.fromJson(event.payload!);
|
||||||
notifications.add(notification);
|
notifications.add(notification);
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
final doHaptic = _cfg.prefs.getBool(kAppNotifyWithHaptic) ?? true;
|
||||||
|
if (doHaptic) HapticFeedback.lightImpact();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
186
lib/router.dart
186
lib/router.dart
@ -19,6 +19,8 @@ import 'package:surface/screens/chat/room.dart';
|
|||||||
import 'package:surface/screens/explore.dart';
|
import 'package:surface/screens/explore.dart';
|
||||||
import 'package:surface/screens/friend.dart';
|
import 'package:surface/screens/friend.dart';
|
||||||
import 'package:surface/screens/home.dart';
|
import 'package:surface/screens/home.dart';
|
||||||
|
import 'package:surface/screens/news/news_detail.dart';
|
||||||
|
import 'package:surface/screens/news/news_list.dart';
|
||||||
import 'package:surface/screens/notification.dart';
|
import 'package:surface/screens/notification.dart';
|
||||||
import 'package:surface/screens/post/post_detail.dart';
|
import 'package:surface/screens/post/post_detail.dart';
|
||||||
import 'package:surface/screens/post/post_editor.dart';
|
import 'package:surface/screens/post/post_editor.dart';
|
||||||
@ -31,35 +33,33 @@ import 'package:surface/screens/settings.dart';
|
|||||||
import 'package:surface/screens/sharing.dart';
|
import 'package:surface/screens/sharing.dart';
|
||||||
import 'package:surface/types/post.dart';
|
import 'package:surface/types/post.dart';
|
||||||
import 'package:surface/widgets/about.dart';
|
import 'package:surface/widgets/about.dart';
|
||||||
import 'package:surface/widgets/navigation/app_background.dart';
|
|
||||||
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
||||||
|
|
||||||
|
Widget _fadeThroughTransition(
|
||||||
|
BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
|
||||||
|
return FadeThroughTransition(
|
||||||
|
animation: animation,
|
||||||
|
secondaryAnimation: secondaryAnimation,
|
||||||
|
fillColor: Colors.transparent,
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
final _appRoutes = [
|
final _appRoutes = [
|
||||||
ShellRoute(
|
|
||||||
builder: (context, state, child) => AppPageScaffold(
|
|
||||||
body: child,
|
|
||||||
showAppBar: false,
|
|
||||||
),
|
|
||||||
routes: [
|
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/',
|
path: '/',
|
||||||
name: 'home',
|
name: 'home',
|
||||||
pageBuilder: (context, state) => NoTransitionPage(
|
builder: (context, state) => const HomeScreen(),
|
||||||
child: const HomeScreen(),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/posts',
|
path: '/posts',
|
||||||
name: 'explore',
|
name: 'explore',
|
||||||
pageBuilder: (context, state) => NoTransitionPage(
|
builder: (context, state) => const ExploreScreen(),
|
||||||
child: const ExploreScreen(),
|
|
||||||
),
|
|
||||||
routes: [
|
routes: [
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/write/:mode',
|
path: '/write/:mode',
|
||||||
name: 'postEditor',
|
name: 'postEditor',
|
||||||
builder: (context, state) => AppBackground(
|
builder: (context, state) => PostEditorScreen(
|
||||||
child: PostEditorScreen(
|
|
||||||
mode: state.pathParameters['mode']!,
|
mode: state.pathParameters['mode']!,
|
||||||
postEditId: int.tryParse(
|
postEditId: int.tryParse(
|
||||||
state.uri.queryParameters['editing'] ?? '',
|
state.uri.queryParameters['editing'] ?? '',
|
||||||
@ -73,216 +73,160 @@ final _appRoutes = [
|
|||||||
extraProps: state.extra as PostEditorExtraProps?,
|
extraProps: state.extra as PostEditorExtraProps?,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/search',
|
path: '/search',
|
||||||
name: 'postSearch',
|
name: 'postSearch',
|
||||||
builder: (context, state) => AppBackground(
|
builder: (context, state) => PostSearchScreen(
|
||||||
child: PostSearchScreen(
|
|
||||||
initialTags: state.uri.queryParameters['tags']?.split(','),
|
initialTags: state.uri.queryParameters['tags']?.split(','),
|
||||||
initialCategories: state.uri.queryParameters['categories']?.split(','),
|
initialCategories: state.uri.queryParameters['categories']?.split(','),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/publishers/:name',
|
path: '/publishers/:name',
|
||||||
name: 'postPublisher',
|
name: 'postPublisher',
|
||||||
builder: (context, state) => AppBackground(
|
builder: (context, state) => PostPublisherScreen(name: state.pathParameters['name']!),
|
||||||
child: PostPublisherScreen(name: state.pathParameters['name']!),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/:slug',
|
path: '/:slug',
|
||||||
name: 'postDetail',
|
name: 'postDetail',
|
||||||
builder: (context, state) => AppBackground(
|
builder: (context, state) => PostDetailScreen(
|
||||||
child: PostDetailScreen(
|
|
||||||
slug: state.pathParameters['slug']!,
|
slug: state.pathParameters['slug']!,
|
||||||
preload: state.extra as SnPost?,
|
preload: state.extra as SnPost?,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/account',
|
path: '/account',
|
||||||
name: 'account',
|
name: 'account',
|
||||||
pageBuilder: (context, state) => NoTransitionPage(
|
builder: (context, state) => const AccountScreen(),
|
||||||
child: const AccountScreen(),
|
|
||||||
),
|
|
||||||
routes: [],
|
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/chat',
|
path: '/chat',
|
||||||
name: 'chat',
|
name: 'chat',
|
||||||
pageBuilder: (context, state) => NoTransitionPage(
|
builder: (context, state) => const ChatScreen(),
|
||||||
child: const ChatScreen(),
|
|
||||||
),
|
|
||||||
routes: [
|
routes: [
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/:scope/:alias',
|
path: '/:scope/:alias',
|
||||||
name: 'chatRoom',
|
name: 'chatRoom',
|
||||||
builder: (context, state) => AppBackground(
|
builder: (context, state) => ChatRoomScreen(
|
||||||
child: ChatRoomScreen(
|
|
||||||
scope: state.pathParameters['scope']!,
|
scope: state.pathParameters['scope']!,
|
||||||
alias: state.pathParameters['alias']!,
|
alias: state.pathParameters['alias']!,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/:scope/:alias/call',
|
path: '/:scope/:alias/call',
|
||||||
name: 'chatCallRoom',
|
name: 'chatCallRoom',
|
||||||
builder: (context, state) => AppBackground(
|
builder: (context, state) => CallRoomScreen(
|
||||||
child: CallRoomScreen(
|
|
||||||
scope: state.pathParameters['scope']!,
|
scope: state.pathParameters['scope']!,
|
||||||
alias: state.pathParameters['alias']!,
|
alias: state.pathParameters['alias']!,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/:scope/:alias/detail',
|
path: '/:scope/:alias/detail',
|
||||||
name: 'channelDetail',
|
name: 'channelDetail',
|
||||||
builder: (context, state) => AppBackground(
|
builder: (context, state) => ChannelDetailScreen(
|
||||||
child: ChannelDetailScreen(
|
|
||||||
scope: state.pathParameters['scope']!,
|
scope: state.pathParameters['scope']!,
|
||||||
alias: state.pathParameters['alias']!,
|
alias: state.pathParameters['alias']!,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/manage',
|
path: '/manage',
|
||||||
name: 'chatManage',
|
name: 'chatManage',
|
||||||
pageBuilder: (context, state) => CustomTransitionPage(
|
builder: (context, state) => ChatManageScreen(
|
||||||
child: ChatManageScreen(
|
|
||||||
editingChannelAlias: state.uri.queryParameters['editing'],
|
editingChannelAlias: state.uri.queryParameters['editing'],
|
||||||
),
|
),
|
||||||
transitionsBuilder: (context, animation, secondaryAnimation, child) {
|
|
||||||
return FadeThroughTransition(
|
|
||||||
animation: animation,
|
|
||||||
secondaryAnimation: secondaryAnimation,
|
|
||||||
fillColor: Colors.transparent,
|
|
||||||
child: AppBackground(
|
|
||||||
child: child,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
GoRoute(
|
|
||||||
path: '/:alias',
|
|
||||||
name: 'realmDetail',
|
|
||||||
builder: (context, state) => AppBackground(
|
|
||||||
child: RealmDetailScreen(alias: state.pathParameters['alias']!),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/realm',
|
path: '/realm',
|
||||||
name: 'realm',
|
name: 'realm',
|
||||||
pageBuilder: (context, state) => NoTransitionPage(
|
pageBuilder: (context, state) => CustomTransitionPage(
|
||||||
|
transitionsBuilder: _fadeThroughTransition,
|
||||||
child: const RealmScreen(),
|
child: const RealmScreen(),
|
||||||
),
|
),
|
||||||
routes: [
|
routes: [
|
||||||
|
GoRoute(
|
||||||
|
path: '/:alias',
|
||||||
|
name: 'realmDetail',
|
||||||
|
builder: (context, state) => RealmDetailScreen(alias: state.pathParameters['alias']!),
|
||||||
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/manage',
|
path: '/manage',
|
||||||
name: 'realmManage',
|
name: 'realmManage',
|
||||||
pageBuilder: (context, state) => CustomTransitionPage(
|
builder: (context, state) => RealmManageScreen(
|
||||||
child: RealmManageScreen(
|
|
||||||
editingRealmAlias: state.uri.queryParameters['editing'],
|
editingRealmAlias: state.uri.queryParameters['editing'],
|
||||||
),
|
),
|
||||||
transitionsBuilder: (context, animation, secondaryAnimation, child) {
|
|
||||||
return FadeThroughTransition(
|
|
||||||
animation: animation,
|
|
||||||
secondaryAnimation: secondaryAnimation,
|
|
||||||
fillColor: Colors.transparent,
|
|
||||||
child: AppBackground(
|
|
||||||
child: child,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/news',
|
||||||
|
name: 'news',
|
||||||
|
builder: (context, state) => const NewsScreen(),
|
||||||
|
routes: [
|
||||||
|
GoRoute(
|
||||||
|
path: '/:hash',
|
||||||
|
name: 'newsDetail',
|
||||||
|
builder: (context, state) => NewsDetailScreen(
|
||||||
|
hash: state.pathParameters['hash']!,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/album',
|
path: '/album',
|
||||||
name: 'album',
|
name: 'album',
|
||||||
pageBuilder: (context, state) => NoTransitionPage(
|
builder: (context, state) => const AlbumScreen(),
|
||||||
child: const AlbumScreen(),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/friend',
|
path: '/friend',
|
||||||
name: 'friend',
|
name: 'friend',
|
||||||
pageBuilder: (context, state) => NoTransitionPage(
|
builder: (context, state) => const FriendScreen(),
|
||||||
child: const FriendScreen(),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/notification',
|
path: '/notification',
|
||||||
name: 'notification',
|
name: 'notification',
|
||||||
pageBuilder: (context, state) => NoTransitionPage(
|
builder: (context, state) => const NotificationScreen(),
|
||||||
child: const NotificationScreen(),
|
|
||||||
),
|
),
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
ShellRoute(
|
|
||||||
builder: (context, state, child) => AppPageScaffold(body: child),
|
|
||||||
routes: [
|
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/auth/login',
|
path: '/auth/login',
|
||||||
name: 'authLogin',
|
name: 'authLogin',
|
||||||
builder: (context, state) => const AppBackground(
|
builder: (context, state) => LoginScreen(),
|
||||||
child: LoginScreen(),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/auth/register',
|
path: '/auth/register',
|
||||||
name: 'authRegister',
|
name: 'authRegister',
|
||||||
builder: (context, state) => const AppBackground(
|
builder: (context, state) => RegisterScreen(),
|
||||||
child: RegisterScreen(),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/reports',
|
path: '/reports',
|
||||||
name: 'abuseReport',
|
name: 'abuseReport',
|
||||||
builder: (context, state) => const AppBackground(
|
builder: (context, state) => AbuseReportScreen(),
|
||||||
child: AbuseReportScreen(),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/account/profile/edit',
|
path: '/account/profile/edit',
|
||||||
name: 'accountProfileEdit',
|
name: 'accountProfileEdit',
|
||||||
builder: (context, state) => const AppBackground(
|
builder: (context, state) => ProfileEditScreen(),
|
||||||
child: ProfileEditScreen(),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/account/publishers',
|
path: '/account/publishers',
|
||||||
name: 'accountPublishers',
|
name: 'accountPublishers',
|
||||||
builder: (context, state) => const AppBackground(
|
builder: (context, state) => PublisherScreen(),
|
||||||
child: PublisherScreen(),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/account/publishers/new',
|
path: '/account/publishers/new',
|
||||||
name: 'accountPublisherNew',
|
name: 'accountPublisherNew',
|
||||||
builder: (context, state) => const AppBackground(
|
builder: (context, state) => AccountPublisherNewScreen(),
|
||||||
child: AccountPublisherNewScreen(),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/account/publishers/edit/:name',
|
path: '/account/publishers/edit/:name',
|
||||||
name: 'accountPublisherEdit',
|
name: 'accountPublisherEdit',
|
||||||
builder: (context, state) => AppBackground(
|
builder: (context, state) => AccountPublisherEditScreen(
|
||||||
child: AccountPublisherEditScreen(
|
|
||||||
name: state.pathParameters['name']!,
|
name: state.pathParameters['name']!,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/account/:name',
|
path: '/account/:name',
|
||||||
name: 'accountProfilePage',
|
name: 'accountProfilePage',
|
||||||
@ -290,29 +234,15 @@ final _appRoutes = [
|
|||||||
child: UserScreen(name: state.pathParameters['name']!),
|
child: UserScreen(name: state.pathParameters['name']!),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
ShellRoute(
|
|
||||||
builder: (context, state, child) => AppPageScaffold(body: child),
|
|
||||||
routes: [
|
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/settings',
|
path: '/settings',
|
||||||
name: 'settings',
|
name: 'settings',
|
||||||
builder: (context, state) => const AppBackground(
|
builder: (context, state) => SettingsScreen(),
|
||||||
child: SettingsScreen(),
|
|
||||||
),
|
),
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
ShellRoute(
|
|
||||||
builder: (context, state, child) => AppPageScaffold(body: child),
|
|
||||||
routes: [
|
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/about',
|
path: '/about',
|
||||||
name: 'about',
|
name: 'about',
|
||||||
builder: (context, state) => const AppBackground(
|
builder: (context, state) => AboutScreen(),
|
||||||
child: AboutScreen(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ import 'package:provider/provider.dart';
|
|||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
import 'package:surface/providers/sn_network.dart';
|
import 'package:surface/providers/sn_network.dart';
|
||||||
import 'package:surface/widgets/dialog.dart';
|
import 'package:surface/widgets/dialog.dart';
|
||||||
|
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
||||||
|
|
||||||
import '../types/account.dart';
|
import '../types/account.dart';
|
||||||
|
|
||||||
@ -56,7 +57,11 @@ class _AbuseReportScreenState extends State<AbuseReportScreen> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return AppScaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
leading: const PageBackButton(),
|
||||||
|
title: Text('screenAbuseReport').tr(),
|
||||||
|
),
|
||||||
body: Column(
|
body: Column(
|
||||||
children: [
|
children: [
|
||||||
ListTile(
|
ListTile(
|
||||||
@ -73,6 +78,7 @@ class _AbuseReportScreenState extends State<AbuseReportScreen> {
|
|||||||
else
|
else
|
||||||
Expanded(
|
Expanded(
|
||||||
child: ListView.builder(
|
child: ListView.builder(
|
||||||
|
padding: EdgeInsets.only(top: 8),
|
||||||
itemCount: _reports.length,
|
itemCount: _reports.length,
|
||||||
itemBuilder: (context, idx) {
|
itemBuilder: (context, idx) {
|
||||||
return ListTile(
|
return ListTile(
|
||||||
|
@ -12,6 +12,7 @@ import 'package:surface/providers/websocket.dart';
|
|||||||
import 'package:surface/widgets/account/account_image.dart';
|
import 'package:surface/widgets/account/account_image.dart';
|
||||||
import 'package:surface/widgets/app_bar_leading.dart';
|
import 'package:surface/widgets/app_bar_leading.dart';
|
||||||
import 'package:surface/widgets/dialog.dart';
|
import 'package:surface/widgets/dialog.dart';
|
||||||
|
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
||||||
|
|
||||||
class AccountScreen extends StatelessWidget {
|
class AccountScreen extends StatelessWidget {
|
||||||
const AccountScreen({super.key});
|
const AccountScreen({super.key});
|
||||||
@ -20,7 +21,7 @@ class AccountScreen extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final ua = context.watch<UserProvider>();
|
final ua = context.watch<UserProvider>();
|
||||||
|
|
||||||
return Scaffold(
|
return AppScaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
leading: AutoAppBarLeading(),
|
leading: AutoAppBarLeading(),
|
||||||
title: Text("screenAccount").tr(),
|
title: Text("screenAccount").tr(),
|
||||||
|
@ -18,6 +18,7 @@ import 'package:surface/providers/userinfo.dart';
|
|||||||
import 'package:surface/widgets/account/account_image.dart';
|
import 'package:surface/widgets/account/account_image.dart';
|
||||||
import 'package:surface/widgets/dialog.dart';
|
import 'package:surface/widgets/dialog.dart';
|
||||||
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/universal_image.dart';
|
import 'package:surface/widgets/universal_image.dart';
|
||||||
|
|
||||||
class ProfileEditScreen extends StatefulWidget {
|
class ProfileEditScreen extends StatefulWidget {
|
||||||
@ -81,8 +82,7 @@ class _ProfileEditScreenState extends State<ProfileEditScreen> {
|
|||||||
onDateTimeChanged: (DateTime newDate) {
|
onDateTimeChanged: (DateTime newDate) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_birthday = newDate;
|
_birthday = newDate;
|
||||||
_birthdayController.text =
|
_birthdayController.text = DateFormat(_kDateFormat).format(_birthday!);
|
||||||
DateFormat(_kDateFormat).format(_birthday!);
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -96,11 +96,9 @@ class _ProfileEditScreenState extends State<ProfileEditScreen> {
|
|||||||
if (image == null) return;
|
if (image == null) return;
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
|
|
||||||
final ImageProvider imageProvider =
|
final ImageProvider imageProvider = kIsWeb ? NetworkImage(image.path) : FileImage(File(image.path));
|
||||||
kIsWeb ? NetworkImage(image.path) : FileImage(File(image.path));
|
final aspectRatios =
|
||||||
final aspectRatios = place == 'banner'
|
place == 'banner' ? [CropAspectRatio(width: 16, height: 7)] : [CropAspectRatio(width: 1, height: 1)];
|
||||||
? [CropAspectRatio(width: 16, height: 7)]
|
|
||||||
: [CropAspectRatio(width: 1, height: 1)];
|
|
||||||
final result = (!kIsWeb && (Platform.isIOS || Platform.isMacOS))
|
final result = (!kIsWeb && (Platform.isIOS || Platform.isMacOS))
|
||||||
? await showCupertinoImageCropper(
|
? await showCupertinoImageCropper(
|
||||||
// ignore: use_build_context_synchronously
|
// ignore: use_build_context_synchronously
|
||||||
@ -122,10 +120,7 @@ class _ProfileEditScreenState extends State<ProfileEditScreen> {
|
|||||||
|
|
||||||
setState(() => _isBusy = true);
|
setState(() => _isBusy = true);
|
||||||
|
|
||||||
final rawBytes =
|
final rawBytes = (await result.uiImage.toByteData(format: ImageByteFormat.png))!.buffer.asUint8List();
|
||||||
(await result.uiImage.toByteData(format: ImageByteFormat.png))!
|
|
||||||
.buffer
|
|
||||||
.asUint8List();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final attachment = await attach.directUploadOne(
|
final attachment = await attach.directUploadOne(
|
||||||
@ -212,7 +207,12 @@ class _ProfileEditScreenState extends State<ProfileEditScreen> {
|
|||||||
|
|
||||||
final sn = context.read<SnNetworkProvider>();
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
|
||||||
return SingleChildScrollView(
|
return AppScaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
leading: const PageBackButton(),
|
||||||
|
title: Text('screenAccountProfileEdit').tr(),
|
||||||
|
),
|
||||||
|
body: SingleChildScrollView(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@ -229,8 +229,7 @@ class _ProfileEditScreenState extends State<ProfileEditScreen> {
|
|||||||
child: AspectRatio(
|
child: AspectRatio(
|
||||||
aspectRatio: 16 / 9,
|
aspectRatio: 16 / 9,
|
||||||
child: Container(
|
child: Container(
|
||||||
color:
|
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||||
Theme.of(context).colorScheme.surfaceContainerHigh,
|
|
||||||
child: _banner != null
|
child: _banner != null
|
||||||
? AutoResizeUniversalImage(
|
? AutoResizeUniversalImage(
|
||||||
sn.getAttachmentUrl(_banner!),
|
sn.getAttachmentUrl(_banner!),
|
||||||
@ -343,6 +342,7 @@ class _ProfileEditScreenState extends State<ProfileEditScreen> {
|
|||||||
).padding(horizontal: padding),
|
).padding(horizontal: padding),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -241,6 +241,7 @@ class _UserScreenState extends State<UserScreen> with SingleTickerProviderStateM
|
|||||||
final sn = context.read<SnNetworkProvider>();
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
body: CustomScrollView(
|
body: CustomScrollView(
|
||||||
controller: _scrollController,
|
controller: _scrollController,
|
||||||
slivers: [
|
slivers: [
|
||||||
@ -594,7 +595,7 @@ class _UserScreenState extends State<UserScreen> with SingleTickerProviderStateM
|
|||||||
subtitle: Text('@${ele.name}'),
|
subtitle: Text('@${ele.name}'),
|
||||||
trailing: const Icon(Symbols.chevron_right),
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
GoRouter.of(context).pushNamed(
|
GoRouter.of(context).goNamed(
|
||||||
'postPublisher',
|
'postPublisher',
|
||||||
pathParameters: {'name': ele.name},
|
pathParameters: {'name': ele.name},
|
||||||
);
|
);
|
||||||
|
@ -18,6 +18,7 @@ import 'package:surface/types/post.dart';
|
|||||||
import 'package:surface/widgets/account/account_image.dart';
|
import 'package:surface/widgets/account/account_image.dart';
|
||||||
import 'package:surface/widgets/dialog.dart';
|
import 'package:surface/widgets/dialog.dart';
|
||||||
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/universal_image.dart';
|
import 'package:surface/widgets/universal_image.dart';
|
||||||
|
|
||||||
class AccountPublisherEditScreen extends StatefulWidget {
|
class AccountPublisherEditScreen extends StatefulWidget {
|
||||||
@ -176,7 +177,7 @@ class _AccountPublisherEditScreenState extends State<AccountPublisherEditScreen>
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final sn = context.read<SnNetworkProvider>();
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
|
||||||
return Scaffold(
|
return AppScaffold(
|
||||||
body: SingleChildScrollView(
|
body: SingleChildScrollView(
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
|
@ -10,6 +10,7 @@ import 'package:surface/providers/userinfo.dart';
|
|||||||
import 'package:surface/types/realm.dart';
|
import 'package:surface/types/realm.dart';
|
||||||
import 'package:surface/widgets/account/account_image.dart';
|
import 'package:surface/widgets/account/account_image.dart';
|
||||||
import 'package:surface/widgets/dialog.dart';
|
import 'package:surface/widgets/dialog.dart';
|
||||||
|
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
||||||
|
|
||||||
class AccountPublisherNewScreen extends StatefulWidget {
|
class AccountPublisherNewScreen extends StatefulWidget {
|
||||||
const AccountPublisherNewScreen({super.key});
|
const AccountPublisherNewScreen({super.key});
|
||||||
@ -24,7 +25,11 @@ class _AccountPublisherNewScreenState extends State<AccountPublisherNewScreen> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return AppScaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
leading: const PageBackButton(),
|
||||||
|
title: Text('screenAccountPublisherNew').tr(),
|
||||||
|
),
|
||||||
body: SingleChildScrollView(
|
body: SingleChildScrollView(
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
|
@ -10,6 +10,7 @@ import 'package:surface/types/post.dart';
|
|||||||
import 'package:surface/widgets/account/account_image.dart';
|
import 'package:surface/widgets/account/account_image.dart';
|
||||||
import 'package:surface/widgets/dialog.dart';
|
import 'package:surface/widgets/dialog.dart';
|
||||||
import 'package:surface/widgets/loading_indicator.dart';
|
import 'package:surface/widgets/loading_indicator.dart';
|
||||||
|
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
||||||
|
|
||||||
class PublisherScreen extends StatefulWidget {
|
class PublisherScreen extends StatefulWidget {
|
||||||
const PublisherScreen({super.key});
|
const PublisherScreen({super.key});
|
||||||
@ -32,8 +33,7 @@ class _PublisherScreenState extends State<PublisherScreen> {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
final resp = await sn.client.get('/cgi/co/publishers/me');
|
final resp = await sn.client.get('/cgi/co/publishers/me');
|
||||||
final List<SnPublisher> out = List<SnPublisher>.from(
|
final List<SnPublisher> out = List<SnPublisher>.from(resp.data?.map((e) => SnPublisher.fromJson(e)) ?? []);
|
||||||
resp.data?.map((e) => SnPublisher.fromJson(e)) ?? []);
|
|
||||||
|
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
|
|
||||||
@ -53,7 +53,11 @@ class _PublisherScreenState extends State<PublisherScreen> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return AppScaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
leading: const PageBackButton(),
|
||||||
|
title: Text('screenAccountPublishers').tr(),
|
||||||
|
),
|
||||||
body: Column(
|
body: Column(
|
||||||
children: [
|
children: [
|
||||||
ListTile(
|
ListTile(
|
||||||
@ -62,9 +66,7 @@ class _PublisherScreenState extends State<PublisherScreen> {
|
|||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
leading: const Icon(Symbols.add_circle),
|
leading: const Icon(Symbols.add_circle),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
GoRouter.of(context)
|
GoRouter.of(context).pushNamed('accountPublisherNew').then((value) {
|
||||||
.pushNamed('accountPublisherNew')
|
|
||||||
.then((value) {
|
|
||||||
if (value == true) {
|
if (value == true) {
|
||||||
_publishers.clear();
|
_publishers.clear();
|
||||||
_fetchPublishers();
|
_fetchPublishers();
|
||||||
@ -75,6 +77,9 @@ class _PublisherScreenState extends State<PublisherScreen> {
|
|||||||
const Divider(height: 1),
|
const Divider(height: 1),
|
||||||
LoadingIndicator(isActive: _isBusy),
|
LoadingIndicator(isActive: _isBusy),
|
||||||
Expanded(
|
Expanded(
|
||||||
|
child: MediaQuery.removePadding(
|
||||||
|
context: context,
|
||||||
|
removeTop: true,
|
||||||
child: RefreshIndicator(
|
child: RefreshIndicator(
|
||||||
onRefresh: () {
|
onRefresh: () {
|
||||||
_publishers.clear();
|
_publishers.clear();
|
||||||
@ -120,6 +125,7 @@ class _PublisherScreenState extends State<PublisherScreen> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -11,6 +11,7 @@ import 'package:surface/widgets/app_bar_leading.dart';
|
|||||||
import 'package:surface/widgets/attachment/attachment_zoom.dart';
|
import 'package:surface/widgets/attachment/attachment_zoom.dart';
|
||||||
import 'package:surface/widgets/attachment/attachment_item.dart';
|
import 'package:surface/widgets/attachment/attachment_item.dart';
|
||||||
import 'package:surface/widgets/dialog.dart';
|
import 'package:surface/widgets/dialog.dart';
|
||||||
|
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
||||||
import 'package:uuid/uuid.dart';
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
class AlbumScreen extends StatefulWidget {
|
class AlbumScreen extends StatefulWidget {
|
||||||
@ -82,7 +83,7 @@ class _AlbumScreenState extends State<AlbumScreen> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return AppScaffold(
|
||||||
body: CustomScrollView(
|
body: CustomScrollView(
|
||||||
controller: _scrollController,
|
controller: _scrollController,
|
||||||
slivers: [
|
slivers: [
|
||||||
|
@ -9,6 +9,7 @@ import 'package:surface/providers/sn_network.dart';
|
|||||||
import 'package:surface/providers/userinfo.dart';
|
import 'package:surface/providers/userinfo.dart';
|
||||||
import 'package:surface/types/auth.dart';
|
import 'package:surface/types/auth.dart';
|
||||||
import 'package:surface/widgets/dialog.dart';
|
import 'package:surface/widgets/dialog.dart';
|
||||||
|
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
||||||
import 'package:url_launcher/url_launcher_string.dart';
|
import 'package:url_launcher/url_launcher_string.dart';
|
||||||
|
|
||||||
import '../../providers/websocket.dart';
|
import '../../providers/websocket.dart';
|
||||||
@ -35,7 +36,12 @@ class _LoginScreenState extends State<LoginScreen> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Theme(
|
return AppScaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
leading: const PageBackButton(),
|
||||||
|
title: Text('screenAuthLogin').tr(),
|
||||||
|
),
|
||||||
|
body: Theme(
|
||||||
data: Theme.of(context).copyWith(canvasColor: Colors.transparent),
|
data: Theme.of(context).copyWith(canvasColor: Colors.transparent),
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
child: PageTransitionSwitcher(
|
child: PageTransitionSwitcher(
|
||||||
@ -96,6 +102,7 @@ class _LoginScreenState extends State<LoginScreen> {
|
|||||||
},
|
},
|
||||||
).padding(all: 24),
|
).padding(all: 24),
|
||||||
).center(),
|
).center(),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -441,7 +448,7 @@ class _LoginLookupScreenState extends State<_LoginLookupScreen> {
|
|||||||
|
|
||||||
widget.onNext();
|
widget.onNext();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if(mounted) context.showErrorDialog(err);
|
if (mounted) context.showErrorDialog(err);
|
||||||
return;
|
return;
|
||||||
} finally {
|
} finally {
|
||||||
setState(() => _isBusy = false);
|
setState(() => _isBusy = false);
|
||||||
|
@ -8,6 +8,7 @@ import 'package:provider/provider.dart';
|
|||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
import 'package:surface/providers/sn_network.dart';
|
import 'package:surface/providers/sn_network.dart';
|
||||||
import 'package:surface/widgets/dialog.dart';
|
import 'package:surface/widgets/dialog.dart';
|
||||||
|
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
||||||
import 'package:url_launcher/url_launcher_string.dart';
|
import 'package:url_launcher/url_launcher_string.dart';
|
||||||
|
|
||||||
class RegisterScreen extends StatefulWidget {
|
class RegisterScreen extends StatefulWidget {
|
||||||
@ -54,7 +55,12 @@ class _RegisterScreenState extends State<RegisterScreen> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return StyledWidget(Container(
|
return AppScaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
leading: const PageBackButton(),
|
||||||
|
title: Text('screenAuthRegister').tr(),
|
||||||
|
),
|
||||||
|
body: StyledWidget(Container(
|
||||||
constraints: const BoxConstraints(maxWidth: 380),
|
constraints: const BoxConstraints(maxWidth: 380),
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
child: Column(
|
child: Column(
|
||||||
@ -180,10 +186,7 @@ class _RegisterScreenState extends State<RegisterScreen> {
|
|||||||
'termAcceptNextWithAgree'.tr(),
|
'termAcceptNextWithAgree'.tr(),
|
||||||
textAlign: TextAlign.end,
|
textAlign: TextAlign.end,
|
||||||
style: Theme.of(context).textTheme.bodySmall!.copyWith(
|
style: Theme.of(context).textTheme.bodySmall!.copyWith(
|
||||||
color: Theme.of(context)
|
color: Theme.of(context).colorScheme.onSurface.withAlpha((255 * 0.75).round()),
|
||||||
.colorScheme
|
|
||||||
.onSurface
|
|
||||||
.withAlpha((255 * 0.75).round()),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Material(
|
Material(
|
||||||
@ -223,6 +226,7 @@ class _RegisterScreenState extends State<RegisterScreen> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)).padding(all: 24).center();
|
)).padding(all: 24).center(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ 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/unauthorized_hint.dart';
|
import 'package:surface/widgets/unauthorized_hint.dart';
|
||||||
import 'package:uuid/uuid.dart';
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
@ -120,7 +121,7 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
final ua = context.read<UserProvider>();
|
final ua = context.read<UserProvider>();
|
||||||
|
|
||||||
if (!ua.isAuthorized) {
|
if (!ua.isAuthorized) {
|
||||||
return Scaffold(
|
return AppScaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
leading: AutoAppBarLeading(),
|
leading: AutoAppBarLeading(),
|
||||||
title: Text('screenChat').tr(),
|
title: Text('screenChat').tr(),
|
||||||
@ -131,7 +132,7 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Scaffold(
|
return AppScaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
leading: AutoAppBarLeading(),
|
leading: AutoAppBarLeading(),
|
||||||
title: Text('screenChat').tr(),
|
title: Text('screenChat').tr(),
|
||||||
@ -195,6 +196,9 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
children: [
|
children: [
|
||||||
LoadingIndicator(isActive: _isBusy),
|
LoadingIndicator(isActive: _isBusy),
|
||||||
Expanded(
|
Expanded(
|
||||||
|
child: MediaQuery.removePadding(
|
||||||
|
context: context,
|
||||||
|
removeTop: true,
|
||||||
child: RefreshIndicator(
|
child: RefreshIndicator(
|
||||||
onRefresh: () => Future.sync(() => _refreshChannels()),
|
onRefresh: () => Future.sync(() => _refreshChannels()),
|
||||||
child: ListView.builder(
|
child: ListView.builder(
|
||||||
@ -276,6 +280,7 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -9,10 +9,12 @@ import 'package:styled_widget/styled_widget.dart';
|
|||||||
import 'package:surface/providers/chat_call.dart';
|
import 'package:surface/providers/chat_call.dart';
|
||||||
import 'package:surface/widgets/chat/call/call_controls.dart';
|
import 'package:surface/widgets/chat/call/call_controls.dart';
|
||||||
import 'package:surface/widgets/chat/call/call_participant.dart';
|
import 'package:surface/widgets/chat/call/call_participant.dart';
|
||||||
|
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
||||||
|
|
||||||
class CallRoomScreen extends StatefulWidget {
|
class CallRoomScreen extends StatefulWidget {
|
||||||
final String scope;
|
final String scope;
|
||||||
final String alias;
|
final String alias;
|
||||||
|
|
||||||
const CallRoomScreen({super.key, required this.scope, required this.alias});
|
const CallRoomScreen({super.key, required this.scope, required this.alias});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -35,8 +37,7 @@ class _CallRoomScreenState extends State<CallRoomScreen> {
|
|||||||
return Stack(
|
return Stack(
|
||||||
children: [
|
children: [
|
||||||
Container(
|
Container(
|
||||||
color:
|
color: Theme.of(context).colorScheme.surfaceContainer.withOpacity(0.75),
|
||||||
Theme.of(context).colorScheme.surfaceContainer.withOpacity(0.75),
|
|
||||||
child: call.focusTrack != null
|
child: call.focusTrack != null
|
||||||
? InteractiveParticipantWidget(
|
? InteractiveParticipantWidget(
|
||||||
isFixedAvatar: false,
|
isFixedAvatar: false,
|
||||||
@ -71,8 +72,7 @@ class _CallRoomScreenState extends State<CallRoomScreen> {
|
|||||||
color: Theme.of(context).cardColor,
|
color: Theme.of(context).cardColor,
|
||||||
participant: track,
|
participant: track,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (track.participant.sid !=
|
if (track.participant.sid != call.focusTrack?.participant.sid) {
|
||||||
call.focusTrack?.participant.sid) {
|
|
||||||
call.setFocusTrack(track);
|
call.setFocusTrack(track);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -114,14 +114,10 @@ class _CallRoomScreenState extends State<CallRoomScreen> {
|
|||||||
child: ClipRRect(
|
child: ClipRRect(
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
child: InteractiveParticipantWidget(
|
child: InteractiveParticipantWidget(
|
||||||
color: Theme.of(context)
|
color: Theme.of(context).colorScheme.surfaceContainerHigh.withOpacity(0.75),
|
||||||
.colorScheme
|
|
||||||
.surfaceContainerHigh
|
|
||||||
.withOpacity(0.75),
|
|
||||||
participant: track,
|
participant: track,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (track.participant.sid !=
|
if (track.participant.sid != call.focusTrack?.participant.sid) {
|
||||||
call.focusTrack?.participant.sid) {
|
|
||||||
call.setFocusTrack(track);
|
call.setFocusTrack(track);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -152,31 +148,24 @@ class _CallRoomScreenState extends State<CallRoomScreen> {
|
|||||||
return ListenableBuilder(
|
return ListenableBuilder(
|
||||||
listenable: call,
|
listenable: call,
|
||||||
builder: (context, _) {
|
builder: (context, _) {
|
||||||
return Scaffold(
|
return AppScaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: RichText(
|
title: RichText(
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
text: TextSpan(children: [
|
text: TextSpan(children: [
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: 'call'.tr(),
|
text: 'call'.tr(),
|
||||||
style: Theme.of(context)
|
style: Theme.of(context).textTheme.titleLarge!.copyWith(color: Colors.white),
|
||||||
.textTheme
|
|
||||||
.titleLarge!
|
|
||||||
.copyWith(color: Colors.white),
|
|
||||||
),
|
),
|
||||||
const TextSpan(text: '\n'),
|
const TextSpan(text: '\n'),
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: call.lastDuration.toString(),
|
text: call.lastDuration.toString(),
|
||||||
style: Theme.of(context)
|
style: Theme.of(context).textTheme.bodySmall!.copyWith(color: Colors.white),
|
||||||
.textTheme
|
|
||||||
.bodySmall!
|
|
||||||
.copyWith(color: Colors.white),
|
|
||||||
),
|
),
|
||||||
]),
|
]),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
body: SafeArea(
|
body: GestureDetector(
|
||||||
child: GestureDetector(
|
|
||||||
behavior: HitTestBehavior.translucent,
|
behavior: HitTestBehavior.translucent,
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
@ -190,8 +179,7 @@ class _CallRoomScreenState extends State<CallRoomScreen> {
|
|||||||
Builder(builder: (context) {
|
Builder(builder: (context) {
|
||||||
final call = context.read<ChatCallProvider>();
|
final call = context.read<ChatCallProvider>();
|
||||||
final connectionQuality =
|
final connectionQuality =
|
||||||
call.room.localParticipant?.connectionQuality ??
|
call.room.localParticipant?.connectionQuality ?? livekit.ConnectionQuality.unknown;
|
||||||
livekit.ConnectionQuality.unknown;
|
|
||||||
return Expanded(
|
return Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
@ -213,35 +201,24 @@ class _CallRoomScreenState extends State<CallRoomScreen> {
|
|||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
{
|
{
|
||||||
livekit.ConnectionState.disconnected:
|
livekit.ConnectionState.disconnected: 'callStatusDisconnected'.tr(),
|
||||||
'callStatusDisconnected'.tr(),
|
livekit.ConnectionState.connected: 'callStatusConnected'.tr(),
|
||||||
livekit.ConnectionState.connected:
|
livekit.ConnectionState.connecting: 'callStatusConnecting'.tr(),
|
||||||
'callStatusConnected'.tr(),
|
livekit.ConnectionState.reconnecting: 'callStatusReconnecting'.tr(),
|
||||||
livekit.ConnectionState.connecting:
|
|
||||||
'callStatusConnecting'.tr(),
|
|
||||||
livekit.ConnectionState.reconnecting:
|
|
||||||
'callStatusReconnecting'.tr(),
|
|
||||||
}[call.room.connectionState]!,
|
}[call.room.connectionState]!,
|
||||||
),
|
),
|
||||||
const Gap(6),
|
const Gap(6),
|
||||||
if (connectionQuality !=
|
if (connectionQuality != livekit.ConnectionQuality.unknown)
|
||||||
livekit.ConnectionQuality.unknown)
|
|
||||||
Icon(
|
Icon(
|
||||||
{
|
{
|
||||||
livekit.ConnectionQuality.excellent:
|
livekit.ConnectionQuality.excellent: Icons.signal_cellular_alt,
|
||||||
Icons.signal_cellular_alt,
|
livekit.ConnectionQuality.good: Icons.signal_cellular_alt_2_bar,
|
||||||
livekit.ConnectionQuality.good:
|
livekit.ConnectionQuality.poor: Icons.signal_cellular_alt_1_bar,
|
||||||
Icons.signal_cellular_alt_2_bar,
|
|
||||||
livekit.ConnectionQuality.poor:
|
|
||||||
Icons.signal_cellular_alt_1_bar,
|
|
||||||
}[connectionQuality],
|
}[connectionQuality],
|
||||||
color: {
|
color: {
|
||||||
livekit.ConnectionQuality.excellent:
|
livekit.ConnectionQuality.excellent: Colors.green,
|
||||||
Colors.green,
|
livekit.ConnectionQuality.good: Colors.orange,
|
||||||
livekit.ConnectionQuality.good:
|
livekit.ConnectionQuality.poor: Colors.red,
|
||||||
Colors.orange,
|
|
||||||
livekit.ConnectionQuality.poor:
|
|
||||||
Colors.red,
|
|
||||||
}[connectionQuality],
|
}[connectionQuality],
|
||||||
size: 16,
|
size: 16,
|
||||||
)
|
)
|
||||||
@ -263,9 +240,7 @@ class _CallRoomScreenState extends State<CallRoomScreen> {
|
|||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: _layoutMode == 0
|
icon: _layoutMode == 0 ? const Icon(Icons.view_list) : const Icon(Icons.grid_view),
|
||||||
? const Icon(Icons.view_list)
|
|
||||||
: const Icon(Icons.grid_view),
|
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
_switchLayout();
|
_switchLayout();
|
||||||
},
|
},
|
||||||
@ -277,8 +252,7 @@ class _CallRoomScreenState extends State<CallRoomScreen> {
|
|||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Material(
|
child: Material(
|
||||||
color:
|
color: Theme.of(context).colorScheme.surfaceContainerLow,
|
||||||
Theme.of(context).colorScheme.surfaceContainerLow,
|
|
||||||
child: Builder(
|
child: Builder(
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
switch (_layoutMode) {
|
switch (_layoutMode) {
|
||||||
@ -303,7 +277,6 @@ class _CallRoomScreenState extends State<CallRoomScreen> {
|
|||||||
),
|
),
|
||||||
onTap: () {},
|
onTap: () {},
|
||||||
),
|
),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@ 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/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:very_good_infinite_list/very_good_infinite_list.dart';
|
import 'package:very_good_infinite_list/very_good_infinite_list.dart';
|
||||||
|
|
||||||
class ChannelDetailScreen extends StatefulWidget {
|
class ChannelDetailScreen extends StatefulWidget {
|
||||||
@ -189,7 +190,7 @@ class _ChannelDetailScreenState extends State<ChannelDetailScreen> {
|
|||||||
|
|
||||||
final isOwned = ua.isAuthorized && _channel?.accountId == ua.user?.id;
|
final isOwned = ua.isAuthorized && _channel?.accountId == ua.user?.id;
|
||||||
|
|
||||||
return Scaffold(
|
return AppScaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: _channel != null ? Text(_channel!.name) : Text('loading').tr(),
|
title: _channel != null ? Text(_channel!.name) : Text('loading').tr(),
|
||||||
),
|
),
|
||||||
|
@ -12,6 +12,7 @@ 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/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:uuid/uuid.dart';
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
class ChatManageScreen extends StatefulWidget {
|
class ChatManageScreen extends StatefulWidget {
|
||||||
@ -121,7 +122,7 @@ class _ChatManageScreenState extends State<ChatManageScreen> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return AppScaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: widget.editingChannelAlias != null
|
title: widget.editingChannelAlias != null
|
||||||
? Text('screenChatManage').tr()
|
? Text('screenChatManage').tr()
|
||||||
|
@ -20,6 +20,7 @@ import 'package:surface/widgets/chat/chat_message_input.dart';
|
|||||||
import 'package:surface/widgets/chat/chat_typing_indicator.dart';
|
import 'package:surface/widgets/chat/chat_typing_indicator.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:very_good_infinite_list/very_good_infinite_list.dart';
|
import 'package:very_good_infinite_list/very_good_infinite_list.dart';
|
||||||
|
|
||||||
import '../../providers/user_directory.dart';
|
import '../../providers/user_directory.dart';
|
||||||
@ -211,7 +212,7 @@ class _ChatRoomScreenState extends State<ChatRoomScreen> {
|
|||||||
final call = context.watch<ChatCallProvider>();
|
final call = context.watch<ChatCallProvider>();
|
||||||
final ud = context.read<UserDirectoryProvider>();
|
final ud = context.read<UserDirectoryProvider>();
|
||||||
|
|
||||||
return Scaffold(
|
return AppScaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(
|
title: Text(
|
||||||
_channel?.type == 1
|
_channel?.type == 1
|
||||||
|
@ -13,6 +13,7 @@ import 'package:surface/screens/post/post_detail.dart';
|
|||||||
import 'package:surface/types/post.dart';
|
import 'package:surface/types/post.dart';
|
||||||
import 'package:surface/widgets/app_bar_leading.dart';
|
import 'package:surface/widgets/app_bar_leading.dart';
|
||||||
import 'package:surface/widgets/dialog.dart';
|
import 'package:surface/widgets/dialog.dart';
|
||||||
|
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
||||||
import 'package:surface/widgets/post/post_item.dart';
|
import 'package:surface/widgets/post/post_item.dart';
|
||||||
import 'package:very_good_infinite_list/very_good_infinite_list.dart';
|
import 'package:very_good_infinite_list/very_good_infinite_list.dart';
|
||||||
|
|
||||||
@ -95,7 +96,7 @@ class _ExploreScreenState extends State<ExploreScreen> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return AppScaffold(
|
||||||
floatingActionButtonLocation: ExpandableFab.location,
|
floatingActionButtonLocation: ExpandableFab.location,
|
||||||
floatingActionButton: ExpandableFab(
|
floatingActionButton: ExpandableFab(
|
||||||
key: _fabKey,
|
key: _fabKey,
|
||||||
@ -212,7 +213,7 @@ class _ExploreScreenState extends State<ExploreScreen> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SliverGap(8),
|
const SliverGap(12),
|
||||||
SliverInfiniteList(
|
SliverInfiniteList(
|
||||||
itemCount: _posts.length,
|
itemCount: _posts.length,
|
||||||
isLoading: _isBusy,
|
isLoading: _isBusy,
|
||||||
@ -242,10 +243,10 @@ class _ExploreScreenState extends State<ExploreScreen> {
|
|||||||
),
|
),
|
||||||
openColor: Colors.transparent,
|
openColor: Colors.transparent,
|
||||||
openElevation: 0,
|
openElevation: 0,
|
||||||
closedColor: Theme.of(context).colorScheme.surface,
|
closedColor: Theme.of(context).colorScheme.surfaceContainerLow.withOpacity(0.75),
|
||||||
transitionType: ContainerTransitionType.fade,
|
transitionType: ContainerTransitionType.fade,
|
||||||
closedShape: const RoundedRectangleBorder(
|
closedShape: const RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.all(Radius.circular(8)),
|
borderRadius: BorderRadius.all(Radius.circular(16)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -11,6 +11,7 @@ import 'package:surface/widgets/account/account_image.dart';
|
|||||||
import 'package:surface/widgets/app_bar_leading.dart';
|
import 'package:surface/widgets/app_bar_leading.dart';
|
||||||
import 'package:surface/widgets/dialog.dart';
|
import 'package:surface/widgets/dialog.dart';
|
||||||
import 'package:surface/widgets/loading_indicator.dart';
|
import 'package:surface/widgets/loading_indicator.dart';
|
||||||
|
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
||||||
|
|
||||||
import '../providers/userinfo.dart';
|
import '../providers/userinfo.dart';
|
||||||
import '../widgets/unauthorized_hint.dart';
|
import '../widgets/unauthorized_hint.dart';
|
||||||
@ -180,7 +181,7 @@ class _FriendScreenState extends State<FriendScreen> {
|
|||||||
final ua = context.read<UserProvider>();
|
final ua = context.read<UserProvider>();
|
||||||
|
|
||||||
if (!ua.isAuthorized) {
|
if (!ua.isAuthorized) {
|
||||||
return Scaffold(
|
return AppScaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
leading: AutoAppBarLeading(),
|
leading: AutoAppBarLeading(),
|
||||||
title: Text('screenFriend').tr(),
|
title: Text('screenFriend').tr(),
|
||||||
@ -191,7 +192,7 @@ class _FriendScreenState extends State<FriendScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Scaffold(
|
return AppScaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
leading: AutoAppBarLeading(),
|
leading: AutoAppBarLeading(),
|
||||||
title: Text('screenFriend').tr(),
|
title: Text('screenFriend').tr(),
|
||||||
@ -233,6 +234,9 @@ class _FriendScreenState extends State<FriendScreen> {
|
|||||||
if (_requests.isNotEmpty || _blocks.isNotEmpty)
|
if (_requests.isNotEmpty || _blocks.isNotEmpty)
|
||||||
const Divider(height: 1),
|
const Divider(height: 1),
|
||||||
Expanded(
|
Expanded(
|
||||||
|
child: MediaQuery.removePadding(
|
||||||
|
context: context,
|
||||||
|
removeTop: true,
|
||||||
child: RefreshIndicator(
|
child: RefreshIndicator(
|
||||||
onRefresh: () => Future.wait([
|
onRefresh: () => Future.wait([
|
||||||
_fetchRelations(),
|
_fetchRelations(),
|
||||||
@ -282,6 +286,7 @@ class _FriendScreenState extends State<FriendScreen> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -9,6 +9,7 @@ import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
|
|||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:google_fonts/google_fonts.dart';
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
|
import 'package:html/parser.dart';
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:relative_time/relative_time.dart';
|
import 'package:relative_time/relative_time.dart';
|
||||||
@ -22,9 +23,11 @@ import 'package:surface/providers/special_day.dart';
|
|||||||
import 'package:surface/providers/userinfo.dart';
|
import 'package:surface/providers/userinfo.dart';
|
||||||
import 'package:surface/providers/widget.dart';
|
import 'package:surface/providers/widget.dart';
|
||||||
import 'package:surface/types/check_in.dart';
|
import 'package:surface/types/check_in.dart';
|
||||||
|
import 'package:surface/types/news.dart';
|
||||||
import 'package:surface/types/post.dart';
|
import 'package:surface/types/post.dart';
|
||||||
import 'package:surface/widgets/app_bar_leading.dart';
|
import 'package:surface/widgets/app_bar_leading.dart';
|
||||||
import 'package:surface/widgets/dialog.dart';
|
import 'package:surface/widgets/dialog.dart';
|
||||||
|
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
||||||
import 'package:surface/widgets/post/post_item.dart';
|
import 'package:surface/widgets/post/post_item.dart';
|
||||||
|
|
||||||
class HomeScreenDashEntry {
|
class HomeScreenDashEntry {
|
||||||
@ -51,9 +54,9 @@ class _HomeScreenState extends State<HomeScreen> {
|
|||||||
static const List<HomeScreenDashEntry> kCards = [
|
static const List<HomeScreenDashEntry> kCards = [
|
||||||
HomeScreenDashEntry(
|
HomeScreenDashEntry(
|
||||||
name: 'dashEntryRecommendation',
|
name: 'dashEntryRecommendation',
|
||||||
cols: 2,
|
|
||||||
rows: 2,
|
|
||||||
child: _HomeDashRecommendationPostWidget(),
|
child: _HomeDashRecommendationPostWidget(),
|
||||||
|
rows: 2,
|
||||||
|
cols: 2,
|
||||||
),
|
),
|
||||||
HomeScreenDashEntry(
|
HomeScreenDashEntry(
|
||||||
name: 'dashEntryCheckIn',
|
name: 'dashEntryCheckIn',
|
||||||
@ -63,11 +66,16 @@ class _HomeScreenState extends State<HomeScreen> {
|
|||||||
name: 'dashEntryNotification',
|
name: 'dashEntryNotification',
|
||||||
child: _HomeDashNotificationWidget(),
|
child: _HomeDashNotificationWidget(),
|
||||||
),
|
),
|
||||||
|
HomeScreenDashEntry(
|
||||||
|
name: 'dashEntryTodayNews',
|
||||||
|
child: _HomeDashTodayNews(),
|
||||||
|
cols: 2,
|
||||||
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return AppScaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
leading: AutoAppBarLeading(),
|
leading: AutoAppBarLeading(),
|
||||||
title: Text("screenHome").tr(),
|
title: Text("screenHome").tr(),
|
||||||
@ -229,6 +237,107 @@ class _HomeDashSpecialDayWidgetState extends State<_HomeDashSpecialDayWidget> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _HomeDashTodayNews extends StatefulWidget {
|
||||||
|
const _HomeDashTodayNews();
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<_HomeDashTodayNews> createState() => _HomeDashTodayNewsState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _HomeDashTodayNewsState extends State<_HomeDashTodayNews> {
|
||||||
|
SnNewsArticle? _article;
|
||||||
|
|
||||||
|
Future<void> _fetchArticle() async {
|
||||||
|
try {
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
final resp = await sn.client.get('/cgi/re/news/today');
|
||||||
|
_article = SnNewsArticle.fromJson(resp.data['data']);
|
||||||
|
} catch (err) {
|
||||||
|
if (!mounted) return;
|
||||||
|
context.showErrorDialog(err);
|
||||||
|
rethrow;
|
||||||
|
} finally {
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
initState() {
|
||||||
|
super.initState();
|
||||||
|
_fetchArticle();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Card(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
const Icon(Symbols.newspaper),
|
||||||
|
const Gap(8),
|
||||||
|
Text(
|
||||||
|
'newsToday',
|
||||||
|
style: Theme.of(context).textTheme.titleLarge,
|
||||||
|
).tr()
|
||||||
|
],
|
||||||
|
).padding(horizontal: 18, top: 12, bottom: 8),
|
||||||
|
if (_article != null)
|
||||||
|
Expanded(
|
||||||
|
child: InkWell(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(8)),
|
||||||
|
child: Column(
|
||||||
|
spacing: 4,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
_article!.title,
|
||||||
|
style: Theme.of(context).textTheme.titleMedium!.copyWith(fontSize: 18),
|
||||||
|
maxLines: 2,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
parse(_article!.description).children.map((e) => e.text.trim()).join(),
|
||||||
|
maxLines: 3,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
|
),
|
||||||
|
Builder(
|
||||||
|
builder: (context) {
|
||||||
|
final date = _article!.publishedAt ?? _article!.createdAt;
|
||||||
|
return Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
spacing: 2,
|
||||||
|
children: [
|
||||||
|
Text(DateFormat().format(date)).textStyle(Theme.of(context).textTheme.bodySmall!),
|
||||||
|
Text(' · ').textStyle(Theme.of(context).textTheme.bodySmall!).bold(),
|
||||||
|
Text(RelativeTime(context).format(date)).textStyle(Theme.of(context).textTheme.bodySmall!),
|
||||||
|
],
|
||||||
|
).opacity(0.75);
|
||||||
|
}
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).padding(horizontal: 16),
|
||||||
|
onTap: () {
|
||||||
|
GoRouter.of(context).pushNamed(
|
||||||
|
'newsDetail',
|
||||||
|
pathParameters: {'hash': _article!.hash},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
else
|
||||||
|
Expanded(
|
||||||
|
child: Center(
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class _HomeDashCheckInWidget extends StatefulWidget {
|
class _HomeDashCheckInWidget extends StatefulWidget {
|
||||||
const _HomeDashCheckInWidget();
|
const _HomeDashCheckInWidget();
|
||||||
|
|
||||||
@ -387,6 +496,8 @@ class _HomeDashCheckInWidgetState extends State<_HomeDashCheckInWidget> {
|
|||||||
Text(
|
Text(
|
||||||
'dailyCheckInNone',
|
'dailyCheckInNone',
|
||||||
style: Theme.of(context).textTheme.bodyLarge,
|
style: Theme.of(context).textTheme.bodyLarge,
|
||||||
|
maxLines: 2,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
).tr(),
|
).tr(),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
238
lib/screens/news/news_detail.dart
Normal file
238
lib/screens/news/news_detail.dart
Normal file
@ -0,0 +1,238 @@
|
|||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/gestures.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:gap/gap.dart';
|
||||||
|
import 'package:html/dom.dart' as dom;
|
||||||
|
import 'package:html/parser.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:relative_time/relative_time.dart';
|
||||||
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
import 'package:surface/providers/sn_network.dart';
|
||||||
|
import 'package:surface/types/news.dart';
|
||||||
|
import 'package:surface/widgets/dialog.dart';
|
||||||
|
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
||||||
|
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
||||||
|
import 'package:surface/widgets/universal_image.dart';
|
||||||
|
import 'package:url_launcher/url_launcher_string.dart';
|
||||||
|
|
||||||
|
class NewsDetailScreen extends StatefulWidget {
|
||||||
|
final String hash;
|
||||||
|
|
||||||
|
const NewsDetailScreen({super.key, required this.hash});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<NewsDetailScreen> createState() => _NewsDetailScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _NewsDetailScreenState extends State<NewsDetailScreen> {
|
||||||
|
SnNewsArticle? _article;
|
||||||
|
dom.Document? _articleFragment;
|
||||||
|
|
||||||
|
Future<void> _fetchArticle() async {
|
||||||
|
try {
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
final resp = await sn.client.get('/cgi/re/news/${widget.hash}');
|
||||||
|
_article = SnNewsArticle.fromJson(resp.data);
|
||||||
|
_articleFragment = parse(_article!.content);
|
||||||
|
} catch (err) {
|
||||||
|
if (!mounted) return;
|
||||||
|
context.showErrorDialog(err).then((_) {
|
||||||
|
if (!mounted) return;
|
||||||
|
Navigator.pop(context);
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Widget> _parseHtmlToWidgets(Iterable<dom.Element>? elements) {
|
||||||
|
if (elements == null) return [];
|
||||||
|
|
||||||
|
final List<Widget> widgets = [];
|
||||||
|
|
||||||
|
for (final node in elements) {
|
||||||
|
switch (node.localName) {
|
||||||
|
case 'h1':
|
||||||
|
case 'h2':
|
||||||
|
case 'h3':
|
||||||
|
case 'h4':
|
||||||
|
case 'h5':
|
||||||
|
case 'h6':
|
||||||
|
widgets.add(Text(node.text.trim(), style: Theme.of(context).textTheme.titleMedium));
|
||||||
|
break;
|
||||||
|
case 'p':
|
||||||
|
if (node.text.trim().isEmpty) continue;
|
||||||
|
widgets.add(
|
||||||
|
Text.rich(
|
||||||
|
TextSpan(
|
||||||
|
text: node.text.trim(),
|
||||||
|
children: [
|
||||||
|
for (final child in node.children)
|
||||||
|
switch (child.localName) {
|
||||||
|
'a' => TextSpan(
|
||||||
|
text: child.text.trim(),
|
||||||
|
style: const TextStyle(decoration: TextDecoration.underline),
|
||||||
|
recognizer: TapGestureRecognizer()
|
||||||
|
..onTap = () {
|
||||||
|
launchUrlString(child.attributes['href']!);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
_ => TextSpan(text: child.text.trim()),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
),
|
||||||
|
style: Theme.of(context).textTheme.bodyLarge,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case 'a':
|
||||||
|
// drop single link
|
||||||
|
break;
|
||||||
|
case 'div':
|
||||||
|
// ignore div text, normally it is not meaningful
|
||||||
|
widgets.addAll(_parseHtmlToWidgets(node.children));
|
||||||
|
break;
|
||||||
|
case 'hr':
|
||||||
|
widgets.add(const Divider());
|
||||||
|
break;
|
||||||
|
case 'img':
|
||||||
|
var src = node.attributes['src'];
|
||||||
|
if (src == null) break;
|
||||||
|
final width = double.tryParse(node.attributes['width'] ?? 'null');
|
||||||
|
final height = double.tryParse(node.attributes['height'] ?? 'null');
|
||||||
|
final ratio = width != null && height != null ? width / height : 1.0;
|
||||||
|
if (src.startsWith('//')) {
|
||||||
|
src = 'https:$src';
|
||||||
|
} else if (!src.startsWith('http')) {
|
||||||
|
final baseUri = Uri.parse(_article!.url);
|
||||||
|
final baseUrl = '${baseUri.scheme}://${baseUri.host}';
|
||||||
|
src = '$baseUrl/$src';
|
||||||
|
}
|
||||||
|
widgets.add(
|
||||||
|
AspectRatio(
|
||||||
|
aspectRatio: ratio,
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(8)),
|
||||||
|
border: Border.all(
|
||||||
|
color: Theme.of(context).dividerColor,
|
||||||
|
width: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
height: height ?? double.infinity,
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(8)),
|
||||||
|
child: Container(
|
||||||
|
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||||
|
child: AutoResizeUniversalImage(
|
||||||
|
src,
|
||||||
|
fit: width != null && height != null ? BoxFit.cover : BoxFit.contain,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
widgets.addAll(_parseHtmlToWidgets(node.children));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return widgets;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_fetchArticle();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _isReadingFromReader = true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return AppScaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
leading: const PageBackButton(),
|
||||||
|
title: Text(_article?.title ?? 'loading'.tr()),
|
||||||
|
),
|
||||||
|
body: Column(
|
||||||
|
children: [
|
||||||
|
MaterialBanner(
|
||||||
|
dividerColor: Colors.transparent,
|
||||||
|
leading: const Icon(Icons.info),
|
||||||
|
content: Text(_isReadingFromReader ? 'newsReadingFromReader'.tr() : 'newsReadingFromOriginal'.tr()),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
child: Text('newsReadingProviderSwap').tr(),
|
||||||
|
onPressed: () {
|
||||||
|
setState(() => _isReadingFromReader = !_isReadingFromReader);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (_articleFragment != null && _isReadingFromReader)
|
||||||
|
Expanded(
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
spacing: 8,
|
||||||
|
children: [
|
||||||
|
Text(_article!.title, style: Theme.of(context).textTheme.titleLarge),
|
||||||
|
Builder(builder: (context) {
|
||||||
|
final htmlDescription = parse(_article!.description);
|
||||||
|
return Text(
|
||||||
|
htmlDescription.children.map((ele) => ele.text.trim()).join(),
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
Builder(builder: (context) {
|
||||||
|
final date = _article!.publishedAt ?? _article!.createdAt;
|
||||||
|
return Row(
|
||||||
|
spacing: 2,
|
||||||
|
children: [
|
||||||
|
Text(DateFormat().format(date)).textStyle(Theme.of(context).textTheme.bodySmall!),
|
||||||
|
Text(' · ').textStyle(Theme.of(context).textTheme.bodySmall!).bold(),
|
||||||
|
Text(RelativeTime(context).format(date)).textStyle(Theme.of(context).textTheme.bodySmall!),
|
||||||
|
],
|
||||||
|
).opacity(0.75);
|
||||||
|
}),
|
||||||
|
Text('newsDisclaimer').tr().textStyle(Theme.of(context).textTheme.bodySmall!).opacity(0.75),
|
||||||
|
const Divider(),
|
||||||
|
..._parseHtmlToWidgets(_articleFragment!.children),
|
||||||
|
const Divider(),
|
||||||
|
InkWell(
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Reference from original website',
|
||||||
|
style: TextStyle(decoration: TextDecoration.underline),
|
||||||
|
),
|
||||||
|
const Gap(4),
|
||||||
|
Icon(Icons.launch, size: 16),
|
||||||
|
],
|
||||||
|
).opacity(0.85),
|
||||||
|
onTap: () {
|
||||||
|
launchUrlString(_article!.url);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Gap(MediaQuery.of(context).padding.bottom),
|
||||||
|
],
|
||||||
|
).padding(horizontal: 12, vertical: 16),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
else if (_article != null)
|
||||||
|
Expanded(
|
||||||
|
child: InAppWebView(
|
||||||
|
key: GlobalKey(),
|
||||||
|
initialUrlRequest: URLRequest(url: WebUri(_article!.url)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
227
lib/screens/news/news_list.dart
Normal file
227
lib/screens/news/news_list.dart
Normal file
@ -0,0 +1,227 @@
|
|||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:gap/gap.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:html/parser.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:relative_time/relative_time.dart';
|
||||||
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
import 'package:surface/providers/sn_network.dart';
|
||||||
|
import 'package:surface/types/news.dart';
|
||||||
|
import 'package:surface/widgets/app_bar_leading.dart';
|
||||||
|
import 'package:surface/widgets/dialog.dart';
|
||||||
|
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
||||||
|
import 'package:surface/widgets/universal_image.dart';
|
||||||
|
import 'package:very_good_infinite_list/very_good_infinite_list.dart';
|
||||||
|
|
||||||
|
class NewsScreen extends StatefulWidget {
|
||||||
|
const NewsScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<NewsScreen> createState() => _NewsScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _NewsScreenState extends State<NewsScreen> {
|
||||||
|
List<SnNewsSource>? _sources;
|
||||||
|
|
||||||
|
@override
|
||||||
|
initState() {
|
||||||
|
super.initState();
|
||||||
|
_fetchSources();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _fetchSources() async {
|
||||||
|
try {
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
final resp = await sn.client.get('/cgi/re/well-known/sources');
|
||||||
|
_sources = List<SnNewsSource>.from(
|
||||||
|
resp.data?.map((e) => SnNewsSource.fromJson(e)) ?? [],
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
if (!mounted) return;
|
||||||
|
context.showErrorDialog(err);
|
||||||
|
} finally {
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (_sources == null) {
|
||||||
|
return AppScaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
leading: AutoAppBarLeading(),
|
||||||
|
title: Text('screenNews').tr(),
|
||||||
|
),
|
||||||
|
body: Center(
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return DefaultTabController(
|
||||||
|
length: _sources!.length + 1,
|
||||||
|
child: AppScaffold(
|
||||||
|
body: NestedScrollView(
|
||||||
|
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
|
||||||
|
return <Widget>[
|
||||||
|
SliverOverlapAbsorber(
|
||||||
|
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
||||||
|
sliver: SliverAppBar(
|
||||||
|
leading: AutoAppBarLeading(),
|
||||||
|
title: Text('screenNews').tr(),
|
||||||
|
bottom: TabBar(
|
||||||
|
isScrollable: true,
|
||||||
|
tabs: [
|
||||||
|
Tab(child: Text('newsAllSources'.tr())),
|
||||||
|
for (final source in _sources!) Tab(child: Text(source.label)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
},
|
||||||
|
body: TabBarView(
|
||||||
|
children: [
|
||||||
|
_NewsArticleListWidget(allSources: _sources!),
|
||||||
|
for (final source in _sources!)
|
||||||
|
_NewsArticleListWidget(
|
||||||
|
source: source.id,
|
||||||
|
allSources: _sources!,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _NewsArticleListWidget extends StatefulWidget {
|
||||||
|
final String? source;
|
||||||
|
final List<SnNewsSource> allSources;
|
||||||
|
|
||||||
|
const _NewsArticleListWidget({this.source, required this.allSources});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<_NewsArticleListWidget> createState() => _NewsArticleListWidgetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _NewsArticleListWidgetState extends State<_NewsArticleListWidget> {
|
||||||
|
bool _isBusy = false;
|
||||||
|
|
||||||
|
int? _totalCount;
|
||||||
|
final List<SnNewsArticle> _articles = List.empty(growable: true);
|
||||||
|
|
||||||
|
Future<void> _fetchArticles() async {
|
||||||
|
setState(() => _isBusy = true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
final resp = await sn.client.get('/cgi/re/news', queryParameters: {
|
||||||
|
'take': 10,
|
||||||
|
'offset': _articles.length,
|
||||||
|
if (widget.source != null) 'source': widget.source,
|
||||||
|
});
|
||||||
|
_totalCount = resp.data['count'];
|
||||||
|
_articles.addAll(List<SnNewsArticle>.from(
|
||||||
|
resp.data['data']?.map((e) => SnNewsArticle.fromJson(e)) ?? [],
|
||||||
|
));
|
||||||
|
} catch (err) {
|
||||||
|
if (!mounted) return;
|
||||||
|
context.showErrorDialog(err);
|
||||||
|
} finally {
|
||||||
|
setState(() => _isBusy = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_fetchArticles();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return MediaQuery.removePadding(
|
||||||
|
context: context,
|
||||||
|
removeTop: true,
|
||||||
|
child: RefreshIndicator(
|
||||||
|
onRefresh: _fetchArticles,
|
||||||
|
child: InfiniteList(
|
||||||
|
isLoading: _isBusy,
|
||||||
|
itemCount: _articles.length,
|
||||||
|
hasReachedMax: _totalCount != null && _articles.length >= _totalCount!,
|
||||||
|
onFetchData: () {
|
||||||
|
_fetchArticles();
|
||||||
|
},
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final article = _articles[index];
|
||||||
|
|
||||||
|
final baseUri = Uri.parse(article.url);
|
||||||
|
final baseUrl = '${baseUri.scheme}://${baseUri.host}';
|
||||||
|
|
||||||
|
final htmlDescription = parse(article.description);
|
||||||
|
final date = article.publishedAt ?? article.createdAt;
|
||||||
|
|
||||||
|
return Card(
|
||||||
|
child: InkWell(
|
||||||
|
radius: 8,
|
||||||
|
onTap: () {
|
||||||
|
GoRouter.of(context).pushNamed(
|
||||||
|
'newsDetail',
|
||||||
|
pathParameters: {'hash': article.hash},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
if (article.thumbnail.isNotEmpty && !article.thumbnail.endsWith('.svg'))
|
||||||
|
ClipRRect(
|
||||||
|
borderRadius: BorderRadius.only(
|
||||||
|
topRight: Radius.circular(8),
|
||||||
|
topLeft: Radius.circular(8),
|
||||||
|
),
|
||||||
|
child: AspectRatio(
|
||||||
|
aspectRatio: 16 / 9,
|
||||||
|
child: Container(
|
||||||
|
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||||
|
child: AutoResizeUniversalImage(
|
||||||
|
article.thumbnail.startsWith('http') ? article.thumbnail : '$baseUrl/${article.thumbnail}',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Gap(16),
|
||||||
|
Text(article.title).textStyle(Theme.of(context).textTheme.titleLarge!).padding(horizontal: 16),
|
||||||
|
const Gap(8),
|
||||||
|
Text(htmlDescription.children.map((ele) => ele.text.trim()).join())
|
||||||
|
.textStyle(Theme.of(context).textTheme.bodyMedium!)
|
||||||
|
.padding(horizontal: 16),
|
||||||
|
const Gap(8),
|
||||||
|
Row(
|
||||||
|
spacing: 2,
|
||||||
|
children: [
|
||||||
|
Text(widget.allSources.where((x) => x.id == article.source).first.label)
|
||||||
|
.textStyle(Theme.of(context).textTheme.bodySmall!),
|
||||||
|
],
|
||||||
|
).opacity(0.75).padding(horizontal: 16),
|
||||||
|
Row(
|
||||||
|
spacing: 2,
|
||||||
|
children: [
|
||||||
|
Text(DateFormat().format(date)).textStyle(Theme.of(context).textTheme.bodySmall!),
|
||||||
|
Text(' · ').textStyle(Theme.of(context).textTheme.bodySmall!).bold(),
|
||||||
|
Text(RelativeTime(context).format(date)).textStyle(Theme.of(context).textTheme.bodySmall!),
|
||||||
|
],
|
||||||
|
).opacity(0.75).padding(horizontal: 16),
|
||||||
|
const Gap(16),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -14,6 +14,7 @@ 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/markdown_content.dart';
|
import 'package:surface/widgets/markdown_content.dart';
|
||||||
|
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
||||||
import 'package:surface/widgets/post/post_item.dart';
|
import 'package:surface/widgets/post/post_item.dart';
|
||||||
import 'package:very_good_infinite_list/very_good_infinite_list.dart';
|
import 'package:very_good_infinite_list/very_good_infinite_list.dart';
|
||||||
|
|
||||||
@ -137,7 +138,7 @@ class _NotificationScreenState extends State<NotificationScreen> {
|
|||||||
final ua = context.read<UserProvider>();
|
final ua = context.read<UserProvider>();
|
||||||
|
|
||||||
if (!ua.isAuthorized) {
|
if (!ua.isAuthorized) {
|
||||||
return Scaffold(
|
return AppScaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
leading: AutoAppBarLeading(),
|
leading: AutoAppBarLeading(),
|
||||||
title: Text('screenNotification').tr(),
|
title: Text('screenNotification').tr(),
|
||||||
@ -148,7 +149,7 @@ class _NotificationScreenState extends State<NotificationScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Scaffold(
|
return AppScaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
leading: AutoAppBarLeading(),
|
leading: AutoAppBarLeading(),
|
||||||
title: Text('screenNotification').tr(),
|
title: Text('screenNotification').tr(),
|
||||||
|
@ -14,6 +14,7 @@ import 'package:surface/types/post.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_background.dart';
|
import 'package:surface/widgets/navigation/app_background.dart';
|
||||||
|
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
||||||
import 'package:surface/widgets/post/post_comment_list.dart';
|
import 'package:surface/widgets/post/post_comment_list.dart';
|
||||||
import 'package:surface/widgets/post/post_item.dart';
|
import 'package:surface/widgets/post/post_item.dart';
|
||||||
import 'package:surface/widgets/post/post_mini_editor.dart';
|
import 'package:surface/widgets/post/post_mini_editor.dart';
|
||||||
@ -67,7 +68,7 @@ class _PostDetailScreenState extends State<PostDetailScreen> {
|
|||||||
|
|
||||||
return AppBackground(
|
return AppBackground(
|
||||||
isRoot: widget.onBack != null,
|
isRoot: widget.onBack != null,
|
||||||
child: Scaffold(
|
child: AppScaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
leading: BackButton(
|
leading: BackButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
|
@ -13,6 +13,7 @@ import 'package:surface/providers/sn_network.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/navigation/app_scaffold.dart';
|
||||||
import 'package:surface/widgets/post/post_item.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';
|
||||||
@ -128,7 +129,7 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
return ListenableBuilder(
|
return ListenableBuilder(
|
||||||
listenable: _writeController,
|
listenable: _writeController,
|
||||||
builder: (context, _) {
|
builder: (context, _) {
|
||||||
return Scaffold(
|
return AppScaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
leading: BackButton(
|
leading: BackButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
|
@ -8,6 +8,7 @@ import 'package:styled_widget/styled_widget.dart';
|
|||||||
import 'package:surface/providers/post.dart';
|
import 'package:surface/providers/post.dart';
|
||||||
import 'package:surface/types/post.dart';
|
import 'package:surface/types/post.dart';
|
||||||
import 'package:surface/widgets/dialog.dart';
|
import 'package:surface/widgets/dialog.dart';
|
||||||
|
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
||||||
import 'package:surface/widgets/post/post_item.dart';
|
import 'package:surface/widgets/post/post_item.dart';
|
||||||
import 'package:surface/widgets/post/post_tags_field.dart';
|
import 'package:surface/widgets/post/post_tags_field.dart';
|
||||||
import 'package:very_good_infinite_list/very_good_infinite_list.dart';
|
import 'package:very_good_infinite_list/very_good_infinite_list.dart';
|
||||||
@ -119,7 +120,7 @@ class _PostSearchScreenState extends State<PostSearchScreen> {
|
|||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
return Scaffold(
|
return AppScaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text('screenPostSearch').tr(),
|
title: Text('screenPostSearch').tr(),
|
||||||
actions: [
|
actions: [
|
||||||
|
@ -17,6 +17,7 @@ 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/dialog.dart';
|
import 'package:surface/widgets/dialog.dart';
|
||||||
|
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
||||||
import 'package:surface/widgets/post/post_item.dart';
|
import 'package:surface/widgets/post/post_item.dart';
|
||||||
import 'package:surface/widgets/universal_image.dart';
|
import 'package:surface/widgets/universal_image.dart';
|
||||||
import 'package:very_good_infinite_list/very_good_infinite_list.dart';
|
import 'package:very_good_infinite_list/very_good_infinite_list.dart';
|
||||||
@ -274,7 +275,7 @@ class _PostPublisherScreenState extends State<PostPublisherScreen> with SingleTi
|
|||||||
|
|
||||||
final sn = context.read<SnNetworkProvider>();
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
|
||||||
return Scaffold(
|
return AppScaffold(
|
||||||
body: NestedScrollView(
|
body: NestedScrollView(
|
||||||
controller: _scrollController,
|
controller: _scrollController,
|
||||||
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
|
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
|
||||||
|
@ -12,6 +12,7 @@ import 'package:surface/widgets/account/account_image.dart';
|
|||||||
import 'package:surface/widgets/app_bar_leading.dart';
|
import 'package:surface/widgets/app_bar_leading.dart';
|
||||||
import 'package:surface/widgets/dialog.dart';
|
import 'package:surface/widgets/dialog.dart';
|
||||||
import 'package:surface/widgets/loading_indicator.dart';
|
import 'package:surface/widgets/loading_indicator.dart';
|
||||||
|
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
||||||
import 'package:surface/widgets/unauthorized_hint.dart';
|
import 'package:surface/widgets/unauthorized_hint.dart';
|
||||||
import 'package:surface/widgets/universal_image.dart';
|
import 'package:surface/widgets/universal_image.dart';
|
||||||
|
|
||||||
@ -83,7 +84,7 @@ class _RealmScreenState extends State<RealmScreen> {
|
|||||||
final ua = context.read<UserProvider>();
|
final ua = context.read<UserProvider>();
|
||||||
|
|
||||||
if (!ua.isAuthorized) {
|
if (!ua.isAuthorized) {
|
||||||
return Scaffold(
|
return AppScaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
leading: AutoAppBarLeading(),
|
leading: AutoAppBarLeading(),
|
||||||
title: Text('screenRealm').tr(),
|
title: Text('screenRealm').tr(),
|
||||||
@ -94,7 +95,7 @@ class _RealmScreenState extends State<RealmScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Scaffold(
|
return AppScaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
leading: AutoAppBarLeading(),
|
leading: AutoAppBarLeading(),
|
||||||
title: Text('screenRealm').tr(),
|
title: Text('screenRealm').tr(),
|
||||||
@ -118,6 +119,9 @@ class _RealmScreenState extends State<RealmScreen> {
|
|||||||
children: [
|
children: [
|
||||||
LoadingIndicator(isActive: _isBusy),
|
LoadingIndicator(isActive: _isBusy),
|
||||||
Expanded(
|
Expanded(
|
||||||
|
child: MediaQuery.removePadding(
|
||||||
|
context: context,
|
||||||
|
removeTop: true,
|
||||||
child: RefreshIndicator(
|
child: RefreshIndicator(
|
||||||
onRefresh: _fetchRealms,
|
onRefresh: _fetchRealms,
|
||||||
child: ListView.builder(
|
child: ListView.builder(
|
||||||
@ -196,7 +200,9 @@ class _RealmScreenState extends State<RealmScreen> {
|
|||||||
clipBehavior: Clip.none,
|
clipBehavior: Clip.none,
|
||||||
fit: StackFit.expand,
|
fit: StackFit.expand,
|
||||||
children: [
|
children: [
|
||||||
Container(
|
ClipRRect(
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
|
child: Container(
|
||||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||||
child: (realm.banner?.isEmpty ?? true)
|
child: (realm.banner?.isEmpty ?? true)
|
||||||
? const SizedBox.shrink()
|
? const SizedBox.shrink()
|
||||||
@ -205,6 +211,7 @@ class _RealmScreenState extends State<RealmScreen> {
|
|||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
Positioned(
|
Positioned(
|
||||||
bottom: -30,
|
bottom: -30,
|
||||||
left: 18,
|
left: 18,
|
||||||
@ -240,6 +247,7 @@ class _RealmScreenState extends State<RealmScreen> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -18,6 +18,7 @@ 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/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/universal_image.dart';
|
import 'package:surface/widgets/universal_image.dart';
|
||||||
import 'package:uuid/uuid.dart';
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
@ -179,7 +180,7 @@ class _RealmManageScreenState extends State<RealmManageScreen> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final sn = context.read<SnNetworkProvider>();
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
|
||||||
return Scaffold(
|
return AppScaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: widget.editingRealmAlias != null
|
title: widget.editingRealmAlias != null
|
||||||
? Text('screenRealmManage').tr()
|
? Text('screenRealmManage').tr()
|
||||||
|
@ -8,13 +8,13 @@ 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/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/dialog.dart';
|
import 'package:surface/widgets/dialog.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';
|
||||||
|
|
||||||
import '../../types/post.dart';
|
|
||||||
|
|
||||||
class RealmDetailScreen extends StatefulWidget {
|
class RealmDetailScreen extends StatefulWidget {
|
||||||
final String alias;
|
final String alias;
|
||||||
|
|
||||||
@ -70,19 +70,11 @@ class _RealmDetailScreenState extends State<RealmDetailScreen> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return DefaultTabController(
|
return DefaultTabController(
|
||||||
length: 3,
|
length: 3,
|
||||||
child: Scaffold(
|
child: AppScaffold(
|
||||||
body: NestedScrollView(
|
body: NestedScrollView(
|
||||||
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
|
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
|
||||||
// These are the slivers that show up in the "outer" scroll view.
|
|
||||||
return <Widget>[
|
return <Widget>[
|
||||||
SliverOverlapAbsorber(
|
SliverOverlapAbsorber(
|
||||||
// This widget takes the overlapping behavior of the SliverAppBar,
|
|
||||||
// and redirects it to the SliverOverlapInjector below. If it is
|
|
||||||
// missing, then it is possible for the nested "inner" scroll view
|
|
||||||
// below to end up under the SliverAppBar even when the inner
|
|
||||||
// scroll view thinks it has not been scrolled.
|
|
||||||
// This is not necessary if the "headerSliverBuilder" only builds
|
|
||||||
// widgets that do not overlap the next sliver.
|
|
||||||
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
||||||
sliver: SliverAppBar(
|
sliver: SliverAppBar(
|
||||||
title: Text(_realm?.name ?? 'loading'.tr()),
|
title: Text(_realm?.name ?? 'loading'.tr()),
|
||||||
@ -428,7 +420,7 @@ class _RealmSettingsWidgetState extends State<_RealmSettingsWidget> {
|
|||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
const Gap(16),
|
const Gap(8),
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Symbols.edit),
|
leading: const Icon(Symbols.edit),
|
||||||
trailing: const Icon(Symbols.chevron_right),
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
|
@ -18,6 +18,7 @@ import 'package:surface/providers/sn_network.dart';
|
|||||||
import 'package:surface/providers/theme.dart';
|
import 'package:surface/providers/theme.dart';
|
||||||
import 'package:surface/theme.dart';
|
import 'package:surface/theme.dart';
|
||||||
import 'package:surface/widgets/dialog.dart';
|
import 'package:surface/widgets/dialog.dart';
|
||||||
|
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
||||||
|
|
||||||
const Map<String, Color> kColorSchemes = {
|
const Map<String, Color> kColorSchemes = {
|
||||||
'colorSchemeIndigo': Colors.indigo,
|
'colorSchemeIndigo': Colors.indigo,
|
||||||
@ -67,7 +68,11 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final sn = context.read<SnNetworkProvider>();
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
|
||||||
return Scaffold(
|
return AppScaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
leading: const PageBackButton(),
|
||||||
|
title: Text('screenSettings').tr(),
|
||||||
|
),
|
||||||
body: SingleChildScrollView(
|
body: SingleChildScrollView(
|
||||||
child: Column(
|
child: Column(
|
||||||
spacing: 16,
|
spacing: 16,
|
||||||
@ -255,6 +260,48 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text('settingsFeatures').bold().fontSize(17).tr().padding(horizontal: 20, bottom: 4),
|
||||||
|
CheckboxListTile(
|
||||||
|
secondary: const Icon(Symbols.vibration),
|
||||||
|
contentPadding: const EdgeInsets.only(left: 24, right: 17),
|
||||||
|
title: Text('settingsNotifyWithHaptic').tr(),
|
||||||
|
subtitle: Text('settingsNotifyWithHapticDescription').tr(),
|
||||||
|
value: _prefs.getBool(kAppNotifyWithHaptic) ?? true,
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
_prefs.setBool(kAppNotifyWithHaptic, value ?? false);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
CheckboxListTile(
|
||||||
|
secondary: const Icon(Symbols.link),
|
||||||
|
title: Text('settingsExpandPostLink').tr(),
|
||||||
|
subtitle: Text('settingsExpandPostLinkDescription').tr(),
|
||||||
|
contentPadding: const EdgeInsets.only(left: 24, right: 17),
|
||||||
|
value: _prefs.getBool(kAppExpandPostLink) ?? true,
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
_prefs.setBool(kAppExpandPostLink, value ?? false);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
CheckboxListTile(
|
||||||
|
secondary: const Icon(Symbols.chat),
|
||||||
|
title: Text('settingsExpandChatLink').tr(),
|
||||||
|
subtitle: Text('settingsExpandChatLinkDescription').tr(),
|
||||||
|
contentPadding: const EdgeInsets.only(left: 24, right: 17),
|
||||||
|
value: _prefs.getBool(kAppExpandChatLink) ?? true,
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
_prefs.setBool(kAppExpandChatLink, value ?? false);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
Column(
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
|
@ -34,9 +34,10 @@ Future<ThemeData> createAppTheme(
|
|||||||
);
|
);
|
||||||
|
|
||||||
final hasAppBarBlurry = prefs.getBool(kAppbarTransparentStoreKey) ?? false;
|
final hasAppBarBlurry = prefs.getBool(kAppbarTransparentStoreKey) ?? false;
|
||||||
|
final useM3 = useMaterial3 ?? (prefs.getBool(kMaterialYouToggleStoreKey) ?? true);
|
||||||
|
|
||||||
return ThemeData(
|
return ThemeData(
|
||||||
useMaterial3: useMaterial3 ?? (prefs.getBool(kMaterialYouToggleStoreKey) ?? true),
|
useMaterial3: useM3,
|
||||||
colorScheme: colorScheme,
|
colorScheme: colorScheme,
|
||||||
brightness: brightness,
|
brightness: brightness,
|
||||||
iconTheme: IconThemeData(
|
iconTheme: IconThemeData(
|
||||||
@ -45,17 +46,19 @@ Future<ThemeData> createAppTheme(
|
|||||||
opticalSize: 20,
|
opticalSize: 20,
|
||||||
color: colorScheme.onSurface,
|
color: colorScheme.onSurface,
|
||||||
),
|
),
|
||||||
|
snackBarTheme: SnackBarThemeData(
|
||||||
|
behavior: useM3 ? SnackBarBehavior.floating : SnackBarBehavior.fixed,
|
||||||
|
),
|
||||||
appBarTheme: AppBarTheme(
|
appBarTheme: AppBarTheme(
|
||||||
centerTitle: true,
|
centerTitle: true,
|
||||||
elevation: hasAppBarBlurry ? 0 : null,
|
elevation: hasAppBarBlurry ? 0 : null,
|
||||||
backgroundColor: hasAppBarBlurry ? colorScheme.primary.withOpacity(0.3) : colorScheme.primary,
|
backgroundColor: hasAppBarBlurry ? colorScheme.primary.withOpacity(0.3) : colorScheme.primary,
|
||||||
foregroundColor: hasAppBarBlurry ? colorScheme.onSurface : colorScheme.onPrimary,
|
foregroundColor: hasAppBarBlurry ? colorScheme.onSurface : colorScheme.onPrimary,
|
||||||
),
|
),
|
||||||
scaffoldBackgroundColor: Colors.transparent,
|
|
||||||
pageTransitionsTheme: PageTransitionsTheme(
|
pageTransitionsTheme: PageTransitionsTheme(
|
||||||
builders: {
|
builders: {
|
||||||
TargetPlatform.android: PredictiveBackPageTransitionsBuilder(),
|
TargetPlatform.android: PredictiveBackPageTransitionsBuilder(),
|
||||||
TargetPlatform.iOS: ZoomPageTransitionsBuilder(),
|
TargetPlatform.iOS: CupertinoPageTransitionsBuilder(),
|
||||||
TargetPlatform.macOS: ZoomPageTransitionsBuilder(),
|
TargetPlatform.macOS: ZoomPageTransitionsBuilder(),
|
||||||
TargetPlatform.fuchsia: ZoomPageTransitionsBuilder(),
|
TargetPlatform.fuchsia: ZoomPageTransitionsBuilder(),
|
||||||
TargetPlatform.linux: ZoomPageTransitionsBuilder(),
|
TargetPlatform.linux: ZoomPageTransitionsBuilder(),
|
||||||
|
38
lib/types/news.dart
Normal file
38
lib/types/news.dart
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
|
part 'news.freezed.dart';
|
||||||
|
part 'news.g.dart';
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class SnNewsSource with _$SnNewsSource {
|
||||||
|
const factory SnNewsSource({
|
||||||
|
required String id,
|
||||||
|
required String label,
|
||||||
|
required String type,
|
||||||
|
required String source,
|
||||||
|
required int depth,
|
||||||
|
required bool enabled,
|
||||||
|
}) = _SnNewsSource;
|
||||||
|
|
||||||
|
factory SnNewsSource.fromJson(Map<String, dynamic> json) => _$SnNewsSourceFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class SnNewsArticle with _$SnNewsArticle {
|
||||||
|
const factory SnNewsArticle({
|
||||||
|
required int id,
|
||||||
|
required DateTime createdAt,
|
||||||
|
required DateTime updatedAt,
|
||||||
|
required dynamic deletedAt,
|
||||||
|
required String thumbnail,
|
||||||
|
required String title,
|
||||||
|
required String description,
|
||||||
|
required String content,
|
||||||
|
required String url,
|
||||||
|
required String hash,
|
||||||
|
required String source,
|
||||||
|
required DateTime? publishedAt,
|
||||||
|
}) = _SnNewsArticle;
|
||||||
|
|
||||||
|
factory SnNewsArticle.fromJson(Map<String, dynamic> json) => _$SnNewsArticleFromJson(json);
|
||||||
|
}
|
660
lib/types/news.freezed.dart
Normal file
660
lib/types/news.freezed.dart
Normal file
@ -0,0 +1,660 @@
|
|||||||
|
// coverage:ignore-file
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||||
|
|
||||||
|
part of 'news.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// FreezedGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
T _$identity<T>(T value) => value;
|
||||||
|
|
||||||
|
final _privateConstructorUsedError = UnsupportedError(
|
||||||
|
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
|
||||||
|
|
||||||
|
SnNewsSource _$SnNewsSourceFromJson(Map<String, dynamic> json) {
|
||||||
|
return _SnNewsSource.fromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$SnNewsSource {
|
||||||
|
String get id => throw _privateConstructorUsedError;
|
||||||
|
String get label => throw _privateConstructorUsedError;
|
||||||
|
String get type => throw _privateConstructorUsedError;
|
||||||
|
String get source => throw _privateConstructorUsedError;
|
||||||
|
int get depth => throw _privateConstructorUsedError;
|
||||||
|
bool get enabled => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
|
/// Serializes this SnNewsSource to a JSON map.
|
||||||
|
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
|
/// Create a copy of SnNewsSource
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
$SnNewsSourceCopyWith<SnNewsSource> get copyWith =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class $SnNewsSourceCopyWith<$Res> {
|
||||||
|
factory $SnNewsSourceCopyWith(
|
||||||
|
SnNewsSource value, $Res Function(SnNewsSource) then) =
|
||||||
|
_$SnNewsSourceCopyWithImpl<$Res, SnNewsSource>;
|
||||||
|
@useResult
|
||||||
|
$Res call(
|
||||||
|
{String id,
|
||||||
|
String label,
|
||||||
|
String type,
|
||||||
|
String source,
|
||||||
|
int depth,
|
||||||
|
bool enabled});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class _$SnNewsSourceCopyWithImpl<$Res, $Val extends SnNewsSource>
|
||||||
|
implements $SnNewsSourceCopyWith<$Res> {
|
||||||
|
_$SnNewsSourceCopyWithImpl(this._value, this._then);
|
||||||
|
|
||||||
|
// ignore: unused_field
|
||||||
|
final $Val _value;
|
||||||
|
// ignore: unused_field
|
||||||
|
final $Res Function($Val) _then;
|
||||||
|
|
||||||
|
/// Create a copy of SnNewsSource
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({
|
||||||
|
Object? id = null,
|
||||||
|
Object? label = null,
|
||||||
|
Object? type = null,
|
||||||
|
Object? source = null,
|
||||||
|
Object? depth = null,
|
||||||
|
Object? enabled = null,
|
||||||
|
}) {
|
||||||
|
return _then(_value.copyWith(
|
||||||
|
id: null == id
|
||||||
|
? _value.id
|
||||||
|
: id // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
label: null == label
|
||||||
|
? _value.label
|
||||||
|
: label // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
type: null == type
|
||||||
|
? _value.type
|
||||||
|
: type // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
source: null == source
|
||||||
|
? _value.source
|
||||||
|
: source // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
depth: null == depth
|
||||||
|
? _value.depth
|
||||||
|
: depth // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,
|
||||||
|
enabled: null == enabled
|
||||||
|
? _value.enabled
|
||||||
|
: enabled // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,
|
||||||
|
) as $Val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class _$$SnNewsSourceImplCopyWith<$Res>
|
||||||
|
implements $SnNewsSourceCopyWith<$Res> {
|
||||||
|
factory _$$SnNewsSourceImplCopyWith(
|
||||||
|
_$SnNewsSourceImpl value, $Res Function(_$SnNewsSourceImpl) then) =
|
||||||
|
__$$SnNewsSourceImplCopyWithImpl<$Res>;
|
||||||
|
@override
|
||||||
|
@useResult
|
||||||
|
$Res call(
|
||||||
|
{String id,
|
||||||
|
String label,
|
||||||
|
String type,
|
||||||
|
String source,
|
||||||
|
int depth,
|
||||||
|
bool enabled});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class __$$SnNewsSourceImplCopyWithImpl<$Res>
|
||||||
|
extends _$SnNewsSourceCopyWithImpl<$Res, _$SnNewsSourceImpl>
|
||||||
|
implements _$$SnNewsSourceImplCopyWith<$Res> {
|
||||||
|
__$$SnNewsSourceImplCopyWithImpl(
|
||||||
|
_$SnNewsSourceImpl _value, $Res Function(_$SnNewsSourceImpl) _then)
|
||||||
|
: super(_value, _then);
|
||||||
|
|
||||||
|
/// Create a copy of SnNewsSource
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({
|
||||||
|
Object? id = null,
|
||||||
|
Object? label = null,
|
||||||
|
Object? type = null,
|
||||||
|
Object? source = null,
|
||||||
|
Object? depth = null,
|
||||||
|
Object? enabled = null,
|
||||||
|
}) {
|
||||||
|
return _then(_$SnNewsSourceImpl(
|
||||||
|
id: null == id
|
||||||
|
? _value.id
|
||||||
|
: id // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
label: null == label
|
||||||
|
? _value.label
|
||||||
|
: label // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
type: null == type
|
||||||
|
? _value.type
|
||||||
|
: type // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
source: null == source
|
||||||
|
? _value.source
|
||||||
|
: source // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
depth: null == depth
|
||||||
|
? _value.depth
|
||||||
|
: depth // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,
|
||||||
|
enabled: null == enabled
|
||||||
|
? _value.enabled
|
||||||
|
: enabled // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
@JsonSerializable()
|
||||||
|
class _$SnNewsSourceImpl implements _SnNewsSource {
|
||||||
|
const _$SnNewsSourceImpl(
|
||||||
|
{required this.id,
|
||||||
|
required this.label,
|
||||||
|
required this.type,
|
||||||
|
required this.source,
|
||||||
|
required this.depth,
|
||||||
|
required this.enabled});
|
||||||
|
|
||||||
|
factory _$SnNewsSourceImpl.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$$SnNewsSourceImplFromJson(json);
|
||||||
|
|
||||||
|
@override
|
||||||
|
final String id;
|
||||||
|
@override
|
||||||
|
final String label;
|
||||||
|
@override
|
||||||
|
final String type;
|
||||||
|
@override
|
||||||
|
final String source;
|
||||||
|
@override
|
||||||
|
final int depth;
|
||||||
|
@override
|
||||||
|
final bool enabled;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'SnNewsSource(id: $id, label: $label, type: $type, source: $source, depth: $depth, enabled: $enabled)';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) ||
|
||||||
|
(other.runtimeType == runtimeType &&
|
||||||
|
other is _$SnNewsSourceImpl &&
|
||||||
|
(identical(other.id, id) || other.id == id) &&
|
||||||
|
(identical(other.label, label) || other.label == label) &&
|
||||||
|
(identical(other.type, type) || other.type == type) &&
|
||||||
|
(identical(other.source, source) || other.source == source) &&
|
||||||
|
(identical(other.depth, depth) || other.depth == depth) &&
|
||||||
|
(identical(other.enabled, enabled) || other.enabled == enabled));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode =>
|
||||||
|
Object.hash(runtimeType, id, label, type, source, depth, enabled);
|
||||||
|
|
||||||
|
/// Create a copy of SnNewsSource
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$$SnNewsSourceImplCopyWith<_$SnNewsSourceImpl> get copyWith =>
|
||||||
|
__$$SnNewsSourceImplCopyWithImpl<_$SnNewsSourceImpl>(this, _$identity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return _$$SnNewsSourceImplToJson(
|
||||||
|
this,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class _SnNewsSource implements SnNewsSource {
|
||||||
|
const factory _SnNewsSource(
|
||||||
|
{required final String id,
|
||||||
|
required final String label,
|
||||||
|
required final String type,
|
||||||
|
required final String source,
|
||||||
|
required final int depth,
|
||||||
|
required final bool enabled}) = _$SnNewsSourceImpl;
|
||||||
|
|
||||||
|
factory _SnNewsSource.fromJson(Map<String, dynamic> json) =
|
||||||
|
_$SnNewsSourceImpl.fromJson;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get id;
|
||||||
|
@override
|
||||||
|
String get label;
|
||||||
|
@override
|
||||||
|
String get type;
|
||||||
|
@override
|
||||||
|
String get source;
|
||||||
|
@override
|
||||||
|
int get depth;
|
||||||
|
@override
|
||||||
|
bool get enabled;
|
||||||
|
|
||||||
|
/// Create a copy of SnNewsSource
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
_$$SnNewsSourceImplCopyWith<_$SnNewsSourceImpl> get copyWith =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
}
|
||||||
|
|
||||||
|
SnNewsArticle _$SnNewsArticleFromJson(Map<String, dynamic> json) {
|
||||||
|
return _SnNewsArticle.fromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$SnNewsArticle {
|
||||||
|
int get id => throw _privateConstructorUsedError;
|
||||||
|
DateTime get createdAt => throw _privateConstructorUsedError;
|
||||||
|
DateTime get updatedAt => throw _privateConstructorUsedError;
|
||||||
|
dynamic get deletedAt => throw _privateConstructorUsedError;
|
||||||
|
String get thumbnail => throw _privateConstructorUsedError;
|
||||||
|
String get title => throw _privateConstructorUsedError;
|
||||||
|
String get description => throw _privateConstructorUsedError;
|
||||||
|
String get content => throw _privateConstructorUsedError;
|
||||||
|
String get url => throw _privateConstructorUsedError;
|
||||||
|
String get hash => throw _privateConstructorUsedError;
|
||||||
|
String get source => throw _privateConstructorUsedError;
|
||||||
|
DateTime? get publishedAt => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
|
/// Serializes this SnNewsArticle to a JSON map.
|
||||||
|
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
|
/// Create a copy of SnNewsArticle
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
$SnNewsArticleCopyWith<SnNewsArticle> get copyWith =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class $SnNewsArticleCopyWith<$Res> {
|
||||||
|
factory $SnNewsArticleCopyWith(
|
||||||
|
SnNewsArticle value, $Res Function(SnNewsArticle) then) =
|
||||||
|
_$SnNewsArticleCopyWithImpl<$Res, SnNewsArticle>;
|
||||||
|
@useResult
|
||||||
|
$Res call(
|
||||||
|
{int id,
|
||||||
|
DateTime createdAt,
|
||||||
|
DateTime updatedAt,
|
||||||
|
dynamic deletedAt,
|
||||||
|
String thumbnail,
|
||||||
|
String title,
|
||||||
|
String description,
|
||||||
|
String content,
|
||||||
|
String url,
|
||||||
|
String hash,
|
||||||
|
String source,
|
||||||
|
DateTime? publishedAt});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class _$SnNewsArticleCopyWithImpl<$Res, $Val extends SnNewsArticle>
|
||||||
|
implements $SnNewsArticleCopyWith<$Res> {
|
||||||
|
_$SnNewsArticleCopyWithImpl(this._value, this._then);
|
||||||
|
|
||||||
|
// ignore: unused_field
|
||||||
|
final $Val _value;
|
||||||
|
// ignore: unused_field
|
||||||
|
final $Res Function($Val) _then;
|
||||||
|
|
||||||
|
/// Create a copy of SnNewsArticle
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({
|
||||||
|
Object? id = null,
|
||||||
|
Object? createdAt = null,
|
||||||
|
Object? updatedAt = null,
|
||||||
|
Object? deletedAt = freezed,
|
||||||
|
Object? thumbnail = null,
|
||||||
|
Object? title = null,
|
||||||
|
Object? description = null,
|
||||||
|
Object? content = null,
|
||||||
|
Object? url = null,
|
||||||
|
Object? hash = null,
|
||||||
|
Object? source = null,
|
||||||
|
Object? publishedAt = freezed,
|
||||||
|
}) {
|
||||||
|
return _then(_value.copyWith(
|
||||||
|
id: null == id
|
||||||
|
? _value.id
|
||||||
|
: id // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,
|
||||||
|
createdAt: null == createdAt
|
||||||
|
? _value.createdAt
|
||||||
|
: createdAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,
|
||||||
|
updatedAt: null == updatedAt
|
||||||
|
? _value.updatedAt
|
||||||
|
: updatedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,
|
||||||
|
deletedAt: freezed == deletedAt
|
||||||
|
? _value.deletedAt
|
||||||
|
: deletedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as dynamic,
|
||||||
|
thumbnail: null == thumbnail
|
||||||
|
? _value.thumbnail
|
||||||
|
: thumbnail // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
title: null == title
|
||||||
|
? _value.title
|
||||||
|
: title // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
description: null == description
|
||||||
|
? _value.description
|
||||||
|
: description // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
content: null == content
|
||||||
|
? _value.content
|
||||||
|
: content // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
url: null == url
|
||||||
|
? _value.url
|
||||||
|
: url // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
hash: null == hash
|
||||||
|
? _value.hash
|
||||||
|
: hash // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
source: null == source
|
||||||
|
? _value.source
|
||||||
|
: source // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
publishedAt: freezed == publishedAt
|
||||||
|
? _value.publishedAt
|
||||||
|
: publishedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime?,
|
||||||
|
) as $Val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class _$$SnNewsArticleImplCopyWith<$Res>
|
||||||
|
implements $SnNewsArticleCopyWith<$Res> {
|
||||||
|
factory _$$SnNewsArticleImplCopyWith(
|
||||||
|
_$SnNewsArticleImpl value, $Res Function(_$SnNewsArticleImpl) then) =
|
||||||
|
__$$SnNewsArticleImplCopyWithImpl<$Res>;
|
||||||
|
@override
|
||||||
|
@useResult
|
||||||
|
$Res call(
|
||||||
|
{int id,
|
||||||
|
DateTime createdAt,
|
||||||
|
DateTime updatedAt,
|
||||||
|
dynamic deletedAt,
|
||||||
|
String thumbnail,
|
||||||
|
String title,
|
||||||
|
String description,
|
||||||
|
String content,
|
||||||
|
String url,
|
||||||
|
String hash,
|
||||||
|
String source,
|
||||||
|
DateTime? publishedAt});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class __$$SnNewsArticleImplCopyWithImpl<$Res>
|
||||||
|
extends _$SnNewsArticleCopyWithImpl<$Res, _$SnNewsArticleImpl>
|
||||||
|
implements _$$SnNewsArticleImplCopyWith<$Res> {
|
||||||
|
__$$SnNewsArticleImplCopyWithImpl(
|
||||||
|
_$SnNewsArticleImpl _value, $Res Function(_$SnNewsArticleImpl) _then)
|
||||||
|
: super(_value, _then);
|
||||||
|
|
||||||
|
/// Create a copy of SnNewsArticle
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({
|
||||||
|
Object? id = null,
|
||||||
|
Object? createdAt = null,
|
||||||
|
Object? updatedAt = null,
|
||||||
|
Object? deletedAt = freezed,
|
||||||
|
Object? thumbnail = null,
|
||||||
|
Object? title = null,
|
||||||
|
Object? description = null,
|
||||||
|
Object? content = null,
|
||||||
|
Object? url = null,
|
||||||
|
Object? hash = null,
|
||||||
|
Object? source = null,
|
||||||
|
Object? publishedAt = freezed,
|
||||||
|
}) {
|
||||||
|
return _then(_$SnNewsArticleImpl(
|
||||||
|
id: null == id
|
||||||
|
? _value.id
|
||||||
|
: id // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,
|
||||||
|
createdAt: null == createdAt
|
||||||
|
? _value.createdAt
|
||||||
|
: createdAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,
|
||||||
|
updatedAt: null == updatedAt
|
||||||
|
? _value.updatedAt
|
||||||
|
: updatedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,
|
||||||
|
deletedAt: freezed == deletedAt
|
||||||
|
? _value.deletedAt
|
||||||
|
: deletedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as dynamic,
|
||||||
|
thumbnail: null == thumbnail
|
||||||
|
? _value.thumbnail
|
||||||
|
: thumbnail // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
title: null == title
|
||||||
|
? _value.title
|
||||||
|
: title // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
description: null == description
|
||||||
|
? _value.description
|
||||||
|
: description // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
content: null == content
|
||||||
|
? _value.content
|
||||||
|
: content // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
url: null == url
|
||||||
|
? _value.url
|
||||||
|
: url // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
hash: null == hash
|
||||||
|
? _value.hash
|
||||||
|
: hash // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
source: null == source
|
||||||
|
? _value.source
|
||||||
|
: source // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
publishedAt: freezed == publishedAt
|
||||||
|
? _value.publishedAt
|
||||||
|
: publishedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime?,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
@JsonSerializable()
|
||||||
|
class _$SnNewsArticleImpl implements _SnNewsArticle {
|
||||||
|
const _$SnNewsArticleImpl(
|
||||||
|
{required this.id,
|
||||||
|
required this.createdAt,
|
||||||
|
required this.updatedAt,
|
||||||
|
required this.deletedAt,
|
||||||
|
required this.thumbnail,
|
||||||
|
required this.title,
|
||||||
|
required this.description,
|
||||||
|
required this.content,
|
||||||
|
required this.url,
|
||||||
|
required this.hash,
|
||||||
|
required this.source,
|
||||||
|
required this.publishedAt});
|
||||||
|
|
||||||
|
factory _$SnNewsArticleImpl.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$$SnNewsArticleImplFromJson(json);
|
||||||
|
|
||||||
|
@override
|
||||||
|
final int id;
|
||||||
|
@override
|
||||||
|
final DateTime createdAt;
|
||||||
|
@override
|
||||||
|
final DateTime updatedAt;
|
||||||
|
@override
|
||||||
|
final dynamic deletedAt;
|
||||||
|
@override
|
||||||
|
final String thumbnail;
|
||||||
|
@override
|
||||||
|
final String title;
|
||||||
|
@override
|
||||||
|
final String description;
|
||||||
|
@override
|
||||||
|
final String content;
|
||||||
|
@override
|
||||||
|
final String url;
|
||||||
|
@override
|
||||||
|
final String hash;
|
||||||
|
@override
|
||||||
|
final String source;
|
||||||
|
@override
|
||||||
|
final DateTime? publishedAt;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'SnNewsArticle(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, thumbnail: $thumbnail, title: $title, description: $description, content: $content, url: $url, hash: $hash, source: $source, publishedAt: $publishedAt)';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) ||
|
||||||
|
(other.runtimeType == runtimeType &&
|
||||||
|
other is _$SnNewsArticleImpl &&
|
||||||
|
(identical(other.id, id) || other.id == id) &&
|
||||||
|
(identical(other.createdAt, createdAt) ||
|
||||||
|
other.createdAt == createdAt) &&
|
||||||
|
(identical(other.updatedAt, updatedAt) ||
|
||||||
|
other.updatedAt == updatedAt) &&
|
||||||
|
const DeepCollectionEquality().equals(other.deletedAt, deletedAt) &&
|
||||||
|
(identical(other.thumbnail, thumbnail) ||
|
||||||
|
other.thumbnail == thumbnail) &&
|
||||||
|
(identical(other.title, title) || other.title == title) &&
|
||||||
|
(identical(other.description, description) ||
|
||||||
|
other.description == description) &&
|
||||||
|
(identical(other.content, content) || other.content == content) &&
|
||||||
|
(identical(other.url, url) || other.url == url) &&
|
||||||
|
(identical(other.hash, hash) || other.hash == hash) &&
|
||||||
|
(identical(other.source, source) || other.source == source) &&
|
||||||
|
(identical(other.publishedAt, publishedAt) ||
|
||||||
|
other.publishedAt == publishedAt));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(
|
||||||
|
runtimeType,
|
||||||
|
id,
|
||||||
|
createdAt,
|
||||||
|
updatedAt,
|
||||||
|
const DeepCollectionEquality().hash(deletedAt),
|
||||||
|
thumbnail,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
content,
|
||||||
|
url,
|
||||||
|
hash,
|
||||||
|
source,
|
||||||
|
publishedAt);
|
||||||
|
|
||||||
|
/// Create a copy of SnNewsArticle
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$$SnNewsArticleImplCopyWith<_$SnNewsArticleImpl> get copyWith =>
|
||||||
|
__$$SnNewsArticleImplCopyWithImpl<_$SnNewsArticleImpl>(this, _$identity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return _$$SnNewsArticleImplToJson(
|
||||||
|
this,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class _SnNewsArticle implements SnNewsArticle {
|
||||||
|
const factory _SnNewsArticle(
|
||||||
|
{required final int id,
|
||||||
|
required final DateTime createdAt,
|
||||||
|
required final DateTime updatedAt,
|
||||||
|
required final dynamic deletedAt,
|
||||||
|
required final String thumbnail,
|
||||||
|
required final String title,
|
||||||
|
required final String description,
|
||||||
|
required final String content,
|
||||||
|
required final String url,
|
||||||
|
required final String hash,
|
||||||
|
required final String source,
|
||||||
|
required final DateTime? publishedAt}) = _$SnNewsArticleImpl;
|
||||||
|
|
||||||
|
factory _SnNewsArticle.fromJson(Map<String, dynamic> json) =
|
||||||
|
_$SnNewsArticleImpl.fromJson;
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get id;
|
||||||
|
@override
|
||||||
|
DateTime get createdAt;
|
||||||
|
@override
|
||||||
|
DateTime get updatedAt;
|
||||||
|
@override
|
||||||
|
dynamic get deletedAt;
|
||||||
|
@override
|
||||||
|
String get thumbnail;
|
||||||
|
@override
|
||||||
|
String get title;
|
||||||
|
@override
|
||||||
|
String get description;
|
||||||
|
@override
|
||||||
|
String get content;
|
||||||
|
@override
|
||||||
|
String get url;
|
||||||
|
@override
|
||||||
|
String get hash;
|
||||||
|
@override
|
||||||
|
String get source;
|
||||||
|
@override
|
||||||
|
DateTime? get publishedAt;
|
||||||
|
|
||||||
|
/// Create a copy of SnNewsArticle
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
_$$SnNewsArticleImplCopyWith<_$SnNewsArticleImpl> get copyWith =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
}
|
61
lib/types/news.g.dart
Normal file
61
lib/types/news.g.dart
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'news.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// JsonSerializableGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
_$SnNewsSourceImpl _$$SnNewsSourceImplFromJson(Map<String, dynamic> json) =>
|
||||||
|
_$SnNewsSourceImpl(
|
||||||
|
id: json['id'] as String,
|
||||||
|
label: json['label'] as String,
|
||||||
|
type: json['type'] as String,
|
||||||
|
source: json['source'] as String,
|
||||||
|
depth: (json['depth'] as num).toInt(),
|
||||||
|
enabled: json['enabled'] as bool,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$$SnNewsSourceImplToJson(_$SnNewsSourceImpl instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'id': instance.id,
|
||||||
|
'label': instance.label,
|
||||||
|
'type': instance.type,
|
||||||
|
'source': instance.source,
|
||||||
|
'depth': instance.depth,
|
||||||
|
'enabled': instance.enabled,
|
||||||
|
};
|
||||||
|
|
||||||
|
_$SnNewsArticleImpl _$$SnNewsArticleImplFromJson(Map<String, dynamic> json) =>
|
||||||
|
_$SnNewsArticleImpl(
|
||||||
|
id: (json['id'] as num).toInt(),
|
||||||
|
createdAt: DateTime.parse(json['created_at'] as String),
|
||||||
|
updatedAt: DateTime.parse(json['updated_at'] as String),
|
||||||
|
deletedAt: json['deleted_at'],
|
||||||
|
thumbnail: json['thumbnail'] as String,
|
||||||
|
title: json['title'] as String,
|
||||||
|
description: json['description'] as String,
|
||||||
|
content: json['content'] as String,
|
||||||
|
url: json['url'] as String,
|
||||||
|
hash: json['hash'] as String,
|
||||||
|
source: json['source'] as String,
|
||||||
|
publishedAt: json['published_at'] == null
|
||||||
|
? null
|
||||||
|
: DateTime.parse(json['published_at'] as String),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$$SnNewsArticleImplToJson(_$SnNewsArticleImpl instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'id': instance.id,
|
||||||
|
'created_at': instance.createdAt.toIso8601String(),
|
||||||
|
'updated_at': instance.updatedAt.toIso8601String(),
|
||||||
|
'deleted_at': instance.deletedAt,
|
||||||
|
'thumbnail': instance.thumbnail,
|
||||||
|
'title': instance.title,
|
||||||
|
'description': instance.description,
|
||||||
|
'content': instance.content,
|
||||||
|
'url': instance.url,
|
||||||
|
'hash': instance.hash,
|
||||||
|
'source': instance.source,
|
||||||
|
'published_at': instance.publishedAt?.toIso8601String(),
|
||||||
|
};
|
@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
import 'package:package_info_plus/package_info_plus.dart';
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
||||||
import 'package:url_launcher/url_launcher_string.dart';
|
import 'package:url_launcher/url_launcher_string.dart';
|
||||||
|
|
||||||
class AboutScreen extends StatelessWidget {
|
class AboutScreen extends StatelessWidget {
|
||||||
@ -12,7 +13,12 @@ class AboutScreen extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
const denseButtonStyle = ButtonStyle(visualDensity: VisualDensity(vertical: -4));
|
const denseButtonStyle = ButtonStyle(visualDensity: VisualDensity(vertical: -4));
|
||||||
|
|
||||||
return SizedBox(
|
return AppScaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
leading: const PageBackButton(),
|
||||||
|
title: Text('screenAbout').tr(),
|
||||||
|
),
|
||||||
|
body: SizedBox(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
@ -104,6 +110,7 @@ class AboutScreen extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@ class AttachmentList extends StatefulWidget {
|
|||||||
final List<SnAttachment?> data;
|
final List<SnAttachment?> data;
|
||||||
final bool bordered;
|
final bool bordered;
|
||||||
final bool gridded;
|
final bool gridded;
|
||||||
|
final bool columned;
|
||||||
final BoxFit fit;
|
final BoxFit fit;
|
||||||
final double? maxHeight;
|
final double? maxHeight;
|
||||||
final double? minWidth;
|
final double? minWidth;
|
||||||
@ -26,6 +27,7 @@ class AttachmentList extends StatefulWidget {
|
|||||||
required this.data,
|
required this.data,
|
||||||
this.bordered = false,
|
this.bordered = false,
|
||||||
this.gridded = false,
|
this.gridded = false,
|
||||||
|
this.columned = false,
|
||||||
this.fit = BoxFit.cover,
|
this.fit = BoxFit.cover,
|
||||||
this.maxHeight,
|
this.maxHeight,
|
||||||
this.minWidth,
|
this.minWidth,
|
||||||
@ -105,45 +107,10 @@ class _AttachmentListState extends State<AttachmentList> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (widget.gridded) {
|
|
||||||
final fullOfImage =
|
final fullOfImage =
|
||||||
widget.data.where((ele) => ele?.mediaType == SnMediaType.image).length == widget.data.length;
|
widget.data.where((ele) => ele?.mediaType == SnMediaType.image).length == widget.data.length;
|
||||||
if(!fullOfImage) {
|
|
||||||
return Container(
|
if (widget.gridded && fullOfImage) {
|
||||||
margin: widget.padding ?? EdgeInsets.zero,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: backgroundColor,
|
|
||||||
border: Border(
|
|
||||||
top: borderSide,
|
|
||||||
bottom: borderSide,
|
|
||||||
),
|
|
||||||
borderRadius: AttachmentList.kDefaultRadius,
|
|
||||||
),
|
|
||||||
child: ClipRRect(
|
|
||||||
borderRadius: AttachmentList.kDefaultRadius,
|
|
||||||
child: Column(
|
|
||||||
spacing: 4,
|
|
||||||
children: widget.data
|
|
||||||
.mapIndexed(
|
|
||||||
(idx, ele) => GestureDetector(
|
|
||||||
child: AspectRatio(
|
|
||||||
aspectRatio: ele?.data['ratio']?.toDouble() ?? 1,
|
|
||||||
child: Container(
|
|
||||||
constraints: constraints,
|
|
||||||
child: AttachmentItem(
|
|
||||||
data: ele,
|
|
||||||
heroTag: heroTags[idx],
|
|
||||||
fit: BoxFit.cover,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return Container(
|
return Container(
|
||||||
margin: widget.padding ?? EdgeInsets.zero,
|
margin: widget.padding ?? EdgeInsets.zero,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
@ -191,6 +158,44 @@ class _AttachmentListState extends State<AttachmentList> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ((!fullOfImage && widget.gridded) || widget.columned) {
|
||||||
|
return Container(
|
||||||
|
margin: widget.padding ?? EdgeInsets.zero,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: backgroundColor,
|
||||||
|
border: Border(
|
||||||
|
top: borderSide,
|
||||||
|
bottom: borderSide,
|
||||||
|
),
|
||||||
|
borderRadius: AttachmentList.kDefaultRadius,
|
||||||
|
),
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: AttachmentList.kDefaultRadius,
|
||||||
|
child: Column(
|
||||||
|
children: widget.data
|
||||||
|
.mapIndexed(
|
||||||
|
(idx, ele) => GestureDetector(
|
||||||
|
child: AspectRatio(
|
||||||
|
aspectRatio: ele?.data['ratio']?.toDouble() ?? 1,
|
||||||
|
child: Container(
|
||||||
|
constraints: constraints,
|
||||||
|
child: AttachmentItem(
|
||||||
|
data: ele,
|
||||||
|
heroTag: heroTags[idx],
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.expand((ele) => [ele, const Divider(height: 1)])
|
||||||
|
.toList()
|
||||||
|
..removeLast(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
constraints: BoxConstraints(maxHeight: constraints.maxHeight),
|
constraints: BoxConstraints(maxHeight: constraints.maxHeight),
|
||||||
child: ScrollConfiguration(
|
child: ScrollConfiguration(
|
||||||
|
@ -365,7 +365,7 @@ class _AttachmentZoomViewState extends State<AttachmentZoomView> {
|
|||||||
),
|
),
|
||||||
onVerticalDragUpdate: (details) {
|
onVerticalDragUpdate: (details) {
|
||||||
if (_showDetail) return;
|
if (_showDetail) return;
|
||||||
if (details.delta.dy < 0) {
|
if (details.delta.dy <= -40) {
|
||||||
_showDetail = true;
|
_showDetail = true;
|
||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
context: context,
|
context: context,
|
||||||
@ -415,6 +415,7 @@ class _AttachmentZoomDetailPopup extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
).padding(horizontal: 20, top: 16, bottom: 12),
|
).padding(horizontal: 20, top: 16, bottom: 12),
|
||||||
Expanded(
|
Expanded(
|
||||||
|
child: SingleChildScrollView(
|
||||||
child: Table(
|
child: Table(
|
||||||
columnWidths: {
|
columnWidths: {
|
||||||
0: IntrinsicColumnWidth(),
|
0: IntrinsicColumnWidth(),
|
||||||
@ -487,6 +488,7 @@ class _AttachmentZoomDetailPopup extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
).padding(horizontal: 20, vertical: 8),
|
).padding(horizontal: 20, vertical: 8),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -8,6 +8,7 @@ import 'package:material_symbols_icons/symbols.dart';
|
|||||||
import 'package:popover/popover.dart';
|
import 'package:popover/popover.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
import 'package:surface/providers/config.dart';
|
||||||
import 'package:surface/providers/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/chat.dart';
|
import 'package:surface/types/chat.dart';
|
||||||
@ -53,6 +54,8 @@ class ChatMessage extends StatelessWidget {
|
|||||||
|
|
||||||
final dateFormatter = DateFormat('MM/dd HH:mm');
|
final dateFormatter = DateFormat('MM/dd HH:mm');
|
||||||
|
|
||||||
|
final cfg = context.read<ConfigProvider>();
|
||||||
|
|
||||||
return SwipeTo(
|
return SwipeTo(
|
||||||
key: Key('chat-message-${data.id}'),
|
key: Key('chat-message-${data.id}'),
|
||||||
iconOnLeftSwipe: Symbols.reply,
|
iconOnLeftSwipe: Symbols.reply,
|
||||||
@ -192,7 +195,10 @@ class ChatMessage extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
).opacity(isPending ? 0.5 : 1),
|
).opacity(isPending ? 0.5 : 1),
|
||||||
),
|
),
|
||||||
if (data.body['text'] != null && data.type == 'messages.new' && (data.body['text']?.isNotEmpty ?? false))
|
if (data.body['text'] != null &&
|
||||||
|
data.type == 'messages.new' &&
|
||||||
|
(data.body['text']?.isNotEmpty ?? false) &&
|
||||||
|
(cfg.prefs.getBool(kAppExpandChatLink) ?? true))
|
||||||
LinkPreviewWidget(text: data.body['text']!),
|
LinkPreviewWidget(text: data.body['text']!),
|
||||||
if (data.preload?.attachments?.isNotEmpty ?? false)
|
if (data.preload?.attachments?.isNotEmpty ?? false)
|
||||||
AttachmentList(
|
AttachmentList(
|
||||||
|
@ -18,8 +18,11 @@ class ConnectionIndicator extends StatelessWidget {
|
|||||||
listenable: ws,
|
listenable: ws,
|
||||||
builder: (context, _) {
|
builder: (context, _) {
|
||||||
final ua = context.read<UserProvider>();
|
final ua = context.read<UserProvider>();
|
||||||
|
final show = (ws.isBusy || !ws.isConnected) && ua.isAuthorized;
|
||||||
|
|
||||||
return GestureDetector(
|
return IgnorePointer(
|
||||||
|
ignoring: !show,
|
||||||
|
child: GestureDetector(
|
||||||
child: Material(
|
child: Material(
|
||||||
elevation: 2,
|
elevation: 2,
|
||||||
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(16))),
|
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(16))),
|
||||||
@ -48,7 +51,7 @@ class ConnectionIndicator extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
).padding(horizontal: 8, vertical: 4)
|
).padding(horizontal: 8, vertical: 4)
|
||||||
: const SizedBox.shrink(),
|
: const SizedBox.shrink(),
|
||||||
).opacity((ws.isBusy || !ws.isConnected) && ua.isAuthorized ? 1 : 0, animate: true).animate(
|
).opacity(show ? 1 : 0, animate: true).animate(
|
||||||
const Duration(milliseconds: 300),
|
const Duration(milliseconds: 300),
|
||||||
Curves.easeInOut,
|
Curves.easeInOut,
|
||||||
),
|
),
|
||||||
@ -57,6 +60,7 @@ class ConnectionIndicator extends StatelessWidget {
|
|||||||
ws.connect();
|
ws.connect();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -7,12 +7,11 @@ import 'package:marquee/marquee.dart';
|
|||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:responsive_framework/responsive_framework.dart';
|
import 'package:responsive_framework/responsive_framework.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
import 'package:surface/providers/link_preview.dart';
|
||||||
import 'package:surface/types/link.dart';
|
import 'package:surface/types/link.dart';
|
||||||
import 'package:surface/widgets/universal_image.dart';
|
import 'package:surface/widgets/universal_image.dart';
|
||||||
import 'package:url_launcher/url_launcher_string.dart';
|
import 'package:url_launcher/url_launcher_string.dart';
|
||||||
|
|
||||||
import '../providers/link_preview.dart';
|
|
||||||
|
|
||||||
class LinkPreviewWidget extends StatefulWidget {
|
class LinkPreviewWidget extends StatefulWidget {
|
||||||
final String text;
|
final String text;
|
||||||
|
|
||||||
@ -81,8 +80,9 @@ class _LinkPreviewEntry extends StatelessWidget {
|
|||||||
child: AspectRatio(
|
child: AspectRatio(
|
||||||
aspectRatio: 16 / 9,
|
aspectRatio: 16 / 9,
|
||||||
child: ClipRRect(
|
child: ClipRRect(
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
child: AutoResizeUniversalImage(
|
child: AutoResizeUniversalImage(
|
||||||
meta.image!,
|
meta.image!.startsWith('//') ? 'https:${meta.image}' : meta.image!,
|
||||||
fit: BoxFit.contain,
|
fit: BoxFit.contain,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -94,11 +94,14 @@ class _LinkPreviewEntry extends StatelessWidget {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
if (meta.icon?.isNotEmpty ?? false)
|
if (meta.icon?.isNotEmpty ?? false)
|
||||||
StyledWidget(
|
SizedBox(
|
||||||
meta.icon!.endsWith('.svg')
|
width: 36,
|
||||||
? SvgPicture.network(meta.icon!)
|
height: 36,
|
||||||
|
child: meta.icon!.endsWith('.svg')
|
||||||
|
? SvgPicture.network(meta.icon!, width: 36, height: 36)
|
||||||
: UniversalImage(
|
: UniversalImage(
|
||||||
meta.icon!,
|
meta.icon!,
|
||||||
|
noErrorWidget: true,
|
||||||
width: 36,
|
width: 36,
|
||||||
height: 36,
|
height: 36,
|
||||||
cacheHeight: 36,
|
cacheHeight: 36,
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:bitsdojo_window/bitsdojo_window.dart';
|
import 'package:bitsdojo_window/bitsdojo_window.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
@ -12,7 +11,6 @@ import 'package:styled_widget/styled_widget.dart';
|
|||||||
import 'package:surface/providers/config.dart';
|
import 'package:surface/providers/config.dart';
|
||||||
import 'package:surface/providers/navigation.dart';
|
import 'package:surface/providers/navigation.dart';
|
||||||
import 'package:surface/widgets/connection_indicator.dart';
|
import 'package:surface/widgets/connection_indicator.dart';
|
||||||
import 'package:surface/widgets/dialog.dart';
|
|
||||||
import 'package:surface/widgets/navigation/app_background.dart';
|
import 'package:surface/widgets/navigation/app_background.dart';
|
||||||
import 'package:surface/widgets/navigation/app_bottom_navigation.dart';
|
import 'package:surface/widgets/navigation/app_bottom_navigation.dart';
|
||||||
import 'package:surface/widgets/navigation/app_drawer_navigation.dart';
|
import 'package:surface/widgets/navigation/app_drawer_navigation.dart';
|
||||||
@ -21,34 +19,76 @@ import 'package:surface/widgets/notify_indicator.dart';
|
|||||||
|
|
||||||
final globalRootScaffoldKey = GlobalKey<ScaffoldState>();
|
final globalRootScaffoldKey = GlobalKey<ScaffoldState>();
|
||||||
|
|
||||||
class AppPageScaffold extends StatelessWidget {
|
class AppScaffold extends StatelessWidget {
|
||||||
final String? title;
|
|
||||||
final Widget? body;
|
final Widget? body;
|
||||||
final bool showAppBar;
|
final PreferredSizeWidget? bottomNavigationBar;
|
||||||
final bool showBottomNavigation;
|
final PreferredSizeWidget? bottomSheet;
|
||||||
|
final Drawer? drawer;
|
||||||
|
final Widget? endDrawer;
|
||||||
|
final FloatingActionButtonAnimator? floatingActionButtonAnimator;
|
||||||
|
final FloatingActionButtonLocation? floatingActionButtonLocation;
|
||||||
|
final Widget? floatingActionButton;
|
||||||
|
final AppBar? appBar;
|
||||||
|
final DrawerCallback? onDrawerChanged;
|
||||||
|
final DrawerCallback? onEndDrawerChanged;
|
||||||
|
|
||||||
const AppPageScaffold({
|
const AppScaffold({
|
||||||
super.key,
|
super.key,
|
||||||
this.title,
|
this.appBar,
|
||||||
this.body,
|
this.body,
|
||||||
this.showAppBar = true,
|
this.floatingActionButton,
|
||||||
this.showBottomNavigation = false,
|
this.floatingActionButtonLocation,
|
||||||
|
this.floatingActionButtonAnimator,
|
||||||
|
this.bottomNavigationBar,
|
||||||
|
this.bottomSheet,
|
||||||
|
this.drawer,
|
||||||
|
this.endDrawer,
|
||||||
|
this.onDrawerChanged,
|
||||||
|
this.onEndDrawerChanged,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final state = GoRouter.maybeOf(context);
|
final appBarHeight = appBar?.preferredSize.height ?? 0;
|
||||||
final routeName = state?.routerDelegate.currentConfiguration.last.route.name;
|
final safeTop = MediaQuery.of(context).padding.top;
|
||||||
|
|
||||||
final autoTitle = state != null ? 'screen${routeName?.capitalize()}' : 'screen';
|
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: showAppBar
|
extendBody: true,
|
||||||
? AppBar(
|
extendBodyBehindAppBar: true,
|
||||||
title: Text(title ?? autoTitle.tr()),
|
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||||
)
|
body: SizedBox.expand(
|
||||||
: null,
|
child: AppBackground(
|
||||||
body: body,
|
child: Column(
|
||||||
|
children: [
|
||||||
|
IgnorePointer(child: SizedBox(height: appBar != null ? appBarHeight + safeTop : 0)),
|
||||||
|
if (body != null) Expanded(child: body!),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
appBar: appBar,
|
||||||
|
bottomNavigationBar: bottomNavigationBar,
|
||||||
|
bottomSheet: bottomSheet,
|
||||||
|
drawer: drawer,
|
||||||
|
endDrawer: endDrawer,
|
||||||
|
floatingActionButton: floatingActionButton,
|
||||||
|
floatingActionButtonAnimator: floatingActionButtonAnimator,
|
||||||
|
floatingActionButtonLocation: floatingActionButtonLocation,
|
||||||
|
onDrawerChanged: onDrawerChanged,
|
||||||
|
onEndDrawerChanged: onEndDrawerChanged,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PageBackButton extends StatelessWidget {
|
||||||
|
const PageBackButton({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BackButton(
|
||||||
|
onPressed: () {
|
||||||
|
GoRouter.of(context).pop();
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -101,16 +141,16 @@ class AppRootScaffold extends StatelessWidget {
|
|||||||
|
|
||||||
final safeTop = MediaQuery.of(context).padding.top;
|
final safeTop = MediaQuery.of(context).padding.top;
|
||||||
|
|
||||||
return AppBackground(
|
return Scaffold(
|
||||||
isRoot: true,
|
|
||||||
child: Scaffold(
|
|
||||||
key: globalRootScaffoldKey,
|
key: globalRootScaffoldKey,
|
||||||
|
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||||
body: Stack(
|
body: Stack(
|
||||||
children: [
|
children: [
|
||||||
Column(
|
Column(
|
||||||
children: [
|
children: [
|
||||||
if (!kIsWeb && (Platform.isWindows || Platform.isLinux || Platform.isMacOS))
|
if (!kIsWeb && (Platform.isWindows || Platform.isLinux || Platform.isMacOS))
|
||||||
Container(
|
WindowTitleBarBox(
|
||||||
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
border: Border(
|
border: Border(
|
||||||
bottom: BorderSide(
|
bottom: BorderSide(
|
||||||
@ -119,22 +159,18 @@ class AppRootScaffold extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
child: MoveWindow(
|
||||||
child: Row(
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
mainAxisAlignment: Platform.isMacOS ? MainAxisAlignment.center : MainAxisAlignment.start,
|
mainAxisAlignment: Platform.isMacOS ? MainAxisAlignment.center : MainAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
WindowTitleBarBox(
|
Text(
|
||||||
child: MoveWindow(
|
|
||||||
child: Text(
|
|
||||||
'Solar Network',
|
'Solar Network',
|
||||||
style: GoogleFonts.spaceGrotesk(),
|
style: GoogleFonts.spaceGrotesk(),
|
||||||
).padding(horizontal: 12, vertical: 5),
|
).padding(horizontal: 12, vertical: 5),
|
||||||
),
|
|
||||||
),
|
|
||||||
if (!Platform.isMacOS)
|
if (!Platform.isMacOS)
|
||||||
Expanded(
|
Row(
|
||||||
child: WindowTitleBarBox(
|
mainAxisSize: MainAxisSize.min,
|
||||||
child: Row(
|
|
||||||
children: [
|
children: [
|
||||||
Expanded(child: MoveWindow()),
|
Expanded(child: MoveWindow()),
|
||||||
Row(
|
Row(
|
||||||
@ -146,11 +182,11 @@ class AppRootScaffold extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
Expanded(child: innerWidget),
|
Expanded(child: innerWidget),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -161,7 +197,6 @@ class AppRootScaffold extends StatelessWidget {
|
|||||||
drawer: !isExpandedDrawer ? AppNavigationDrawer() : null,
|
drawer: !isExpandedDrawer ? AppNavigationDrawer() : null,
|
||||||
drawerEdgeDragWidth: isPopable ? 0 : null,
|
drawerEdgeDragWidth: isPopable ? 0 : null,
|
||||||
bottomNavigationBar: isShowBottomNavigation ? AppBottomNavigationBar() : null,
|
bottomNavigationBar: isShowBottomNavigation ? AppBottomNavigationBar() : null,
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,10 +15,14 @@ class NotifyIndicator extends StatelessWidget {
|
|||||||
final ua = context.read<UserProvider>();
|
final ua = context.read<UserProvider>();
|
||||||
final nty = context.watch<NotificationProvider>();
|
final nty = context.watch<NotificationProvider>();
|
||||||
|
|
||||||
|
final show = nty.notifications.isNotEmpty && ua.isAuthorized;
|
||||||
|
|
||||||
return ListenableBuilder(
|
return ListenableBuilder(
|
||||||
listenable: nty,
|
listenable: nty,
|
||||||
builder: (context, _) {
|
builder: (context, _) {
|
||||||
return GestureDetector(
|
return IgnorePointer(
|
||||||
|
ignoring: !show,
|
||||||
|
child: GestureDetector(
|
||||||
child: Material(
|
child: Material(
|
||||||
elevation: 2,
|
elevation: 2,
|
||||||
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(16))),
|
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(16))),
|
||||||
@ -45,13 +49,14 @@ class NotifyIndicator extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
).padding(horizontal: 8, vertical: 4)
|
).padding(horizontal: 8, vertical: 4)
|
||||||
: const SizedBox.shrink(),
|
: const SizedBox.shrink(),
|
||||||
).opacity(nty.notifications.isNotEmpty && ua.isAuthorized ? 1 : 0, animate: true).animate(
|
).opacity(show ? 1 : 0, animate: true).animate(
|
||||||
const Duration(milliseconds: 300),
|
const Duration(milliseconds: 300),
|
||||||
Curves.easeInOut,
|
Curves.easeInOut,
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
nty.clear();
|
nty.clear();
|
||||||
},
|
},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -203,6 +203,8 @@ class PostItem extends StatelessWidget {
|
|||||||
?.where((ele) => ele?.mediaType != SnMediaType.image || data.type != 'article')
|
?.where((ele) => ele?.mediaType != SnMediaType.image || data.type != 'article')
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
|
final cfg = context.read<ConfigProvider>();
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
@ -256,13 +258,12 @@ class PostItem extends StatelessWidget {
|
|||||||
AttachmentList(
|
AttachmentList(
|
||||||
data: displayableAttachments!,
|
data: displayableAttachments!,
|
||||||
bordered: true,
|
bordered: true,
|
||||||
gridded: true,
|
|
||||||
maxHeight: showFullPost ? null : 480,
|
maxHeight: showFullPost ? null : 480,
|
||||||
minWidth: 640,
|
maxWidth: MediaQuery.of(context).size.width - 20,
|
||||||
fit: showFullPost ? BoxFit.cover : BoxFit.contain,
|
fit: showFullPost ? BoxFit.cover : BoxFit.contain,
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||||
),
|
),
|
||||||
if (data.body['content'] != null)
|
if (data.body['content'] != null && (cfg.prefs.getBool(kAppExpandPostLink) ?? true))
|
||||||
LinkPreviewWidget(
|
LinkPreviewWidget(
|
||||||
text: data.body['content'],
|
text: data.body['content'],
|
||||||
).padding(horizontal: 4),
|
).padding(horizontal: 4),
|
||||||
@ -344,7 +345,7 @@ class PostShareImageWidget extends StatelessWidget {
|
|||||||
if (data.type != 'article' && (data.preload?.attachments?.isNotEmpty ?? false))
|
if (data.type != 'article' && (data.preload?.attachments?.isNotEmpty ?? false))
|
||||||
StyledWidget(AttachmentList(
|
StyledWidget(AttachmentList(
|
||||||
data: data.preload!.attachments!,
|
data: data.preload!.attachments!,
|
||||||
gridded: true,
|
columned: true,
|
||||||
)).padding(horizontal: 16, bottom: 8),
|
)).padding(horizontal: 16, bottom: 8),
|
||||||
Column(
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
@ -12,6 +13,7 @@ import 'package:surface/widgets/dialog.dart';
|
|||||||
class PostReactionPopup extends StatefulWidget {
|
class PostReactionPopup extends StatefulWidget {
|
||||||
final SnPost data;
|
final SnPost data;
|
||||||
final Function(Map<String, int> value, int attr, int delta)? onChanged;
|
final Function(Map<String, int> value, int attr, int delta)? onChanged;
|
||||||
|
|
||||||
const PostReactionPopup({super.key, required this.data, this.onChanged});
|
const PostReactionPopup({super.key, required this.data, this.onChanged});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -59,6 +61,7 @@ class _PostReactionPopupState extends State<PostReactionPopup> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
HapticFeedback.mediumImpact();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// ignore: use_build_context_synchronously
|
// ignore: use_build_context_synchronously
|
||||||
if (context.mounted) context.showErrorDialog(err);
|
if (context.mounted) context.showErrorDialog(err);
|
||||||
@ -84,9 +87,7 @@ class _PostReactionPopupState extends State<PostReactionPopup> {
|
|||||||
children: [
|
children: [
|
||||||
const Icon(Symbols.mood, size: 24),
|
const Icon(Symbols.mood, size: 24),
|
||||||
const Gap(16),
|
const Gap(16),
|
||||||
Text('postReactions')
|
Text('postReactions').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),
|
||||||
Container(
|
Container(
|
||||||
@ -102,9 +103,7 @@ class _PostReactionPopupState extends State<PostReactionPopup> {
|
|||||||
Text('postReactionDownvote').plural(widget.data.totalDownvote),
|
Text('postReactionDownvote').plural(widget.data.totalDownvote),
|
||||||
const Gap(24),
|
const Gap(24),
|
||||||
Icon(
|
Icon(
|
||||||
widget.data.totalUpvote >= widget.data.totalDownvote
|
widget.data.totalUpvote >= widget.data.totalDownvote ? Symbols.trending_up : Symbols.trending_down,
|
||||||
? Symbols.trending_up
|
|
||||||
: Symbols.trending_down,
|
|
||||||
size: 16,
|
size: 16,
|
||||||
),
|
),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
|
@ -13,6 +13,7 @@ import file_selector_macos
|
|||||||
import firebase_analytics
|
import firebase_analytics
|
||||||
import firebase_core
|
import firebase_core
|
||||||
import firebase_messaging
|
import firebase_messaging
|
||||||
|
import flutter_inappwebview_macos
|
||||||
import flutter_udid
|
import flutter_udid
|
||||||
import flutter_webrtc
|
import flutter_webrtc
|
||||||
import gal
|
import gal
|
||||||
@ -40,6 +41,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
|||||||
FLTFirebaseAnalyticsPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAnalyticsPlugin"))
|
FLTFirebaseAnalyticsPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAnalyticsPlugin"))
|
||||||
FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin"))
|
FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin"))
|
||||||
FLTFirebaseMessagingPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseMessagingPlugin"))
|
FLTFirebaseMessagingPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseMessagingPlugin"))
|
||||||
|
InAppWebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "InAppWebViewFlutterPlugin"))
|
||||||
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"))
|
||||||
|
@ -72,6 +72,9 @@ PODS:
|
|||||||
- GoogleUtilities/Reachability (~> 8.0)
|
- GoogleUtilities/Reachability (~> 8.0)
|
||||||
- GoogleUtilities/UserDefaults (~> 8.0)
|
- GoogleUtilities/UserDefaults (~> 8.0)
|
||||||
- nanopb (~> 3.30910.0)
|
- nanopb (~> 3.30910.0)
|
||||||
|
- flutter_inappwebview_macos (0.0.1):
|
||||||
|
- FlutterMacOS
|
||||||
|
- OrderedSet (~> 6.0.3)
|
||||||
- flutter_udid (0.0.1):
|
- flutter_udid (0.0.1):
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- SAMKeychain
|
- SAMKeychain
|
||||||
@ -149,6 +152,7 @@ PODS:
|
|||||||
- nanopb/encode (= 3.30910.0)
|
- nanopb/encode (= 3.30910.0)
|
||||||
- nanopb/decode (3.30910.0)
|
- nanopb/decode (3.30910.0)
|
||||||
- nanopb/encode (3.30910.0)
|
- nanopb/encode (3.30910.0)
|
||||||
|
- OrderedSet (6.0.3)
|
||||||
- package_info_plus (0.0.1):
|
- package_info_plus (0.0.1):
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- pasteboard (0.0.1):
|
- pasteboard (0.0.1):
|
||||||
@ -186,6 +190,7 @@ DEPENDENCIES:
|
|||||||
- firebase_analytics (from `Flutter/ephemeral/.symlinks/plugins/firebase_analytics/macos`)
|
- firebase_analytics (from `Flutter/ephemeral/.symlinks/plugins/firebase_analytics/macos`)
|
||||||
- firebase_core (from `Flutter/ephemeral/.symlinks/plugins/firebase_core/macos`)
|
- firebase_core (from `Flutter/ephemeral/.symlinks/plugins/firebase_core/macos`)
|
||||||
- firebase_messaging (from `Flutter/ephemeral/.symlinks/plugins/firebase_messaging/macos`)
|
- firebase_messaging (from `Flutter/ephemeral/.symlinks/plugins/firebase_messaging/macos`)
|
||||||
|
- flutter_inappwebview_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_inappwebview_macos/macos`)
|
||||||
- flutter_udid (from `Flutter/ephemeral/.symlinks/plugins/flutter_udid/macos`)
|
- flutter_udid (from `Flutter/ephemeral/.symlinks/plugins/flutter_udid/macos`)
|
||||||
- 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`)
|
||||||
@ -218,6 +223,7 @@ SPEC REPOS:
|
|||||||
- GoogleDataTransport
|
- GoogleDataTransport
|
||||||
- GoogleUtilities
|
- GoogleUtilities
|
||||||
- nanopb
|
- nanopb
|
||||||
|
- OrderedSet
|
||||||
- PromisesObjC
|
- PromisesObjC
|
||||||
- SAMKeychain
|
- SAMKeychain
|
||||||
- WebRTC-SDK
|
- WebRTC-SDK
|
||||||
@ -241,6 +247,8 @@ EXTERNAL SOURCES:
|
|||||||
:path: Flutter/ephemeral/.symlinks/plugins/firebase_core/macos
|
:path: Flutter/ephemeral/.symlinks/plugins/firebase_core/macos
|
||||||
firebase_messaging:
|
firebase_messaging:
|
||||||
:path: Flutter/ephemeral/.symlinks/plugins/firebase_messaging/macos
|
:path: Flutter/ephemeral/.symlinks/plugins/firebase_messaging/macos
|
||||||
|
flutter_inappwebview_macos:
|
||||||
|
:path: Flutter/ephemeral/.symlinks/plugins/flutter_inappwebview_macos/macos
|
||||||
flutter_udid:
|
flutter_udid:
|
||||||
:path: Flutter/ephemeral/.symlinks/plugins/flutter_udid/macos
|
:path: Flutter/ephemeral/.symlinks/plugins/flutter_udid/macos
|
||||||
flutter_webrtc:
|
flutter_webrtc:
|
||||||
@ -296,6 +304,7 @@ SPEC CHECKSUMS:
|
|||||||
FirebaseCoreInternal: d98ab91e2d80a56d7b246856a8885443b302c0c2
|
FirebaseCoreInternal: d98ab91e2d80a56d7b246856a8885443b302c0c2
|
||||||
FirebaseInstallations: efc0946fc756e4d22d8113f7c761948120322e8c
|
FirebaseInstallations: efc0946fc756e4d22d8113f7c761948120322e8c
|
||||||
FirebaseMessaging: e1aca1fcc23e8b9eddb0e33f375ff90944623021
|
FirebaseMessaging: e1aca1fcc23e8b9eddb0e33f375ff90944623021
|
||||||
|
flutter_inappwebview_macos: bdf207b8f4ebd58e86ae06cd96b147de99a67c9b
|
||||||
flutter_udid: 2e7b3da4b5fdfba86a396b97898f5fe8f4ec1a52
|
flutter_udid: 2e7b3da4b5fdfba86a396b97898f5fe8f4ec1a52
|
||||||
flutter_webrtc: d55fd3f5c75b42940b6b4b2cf376a5797398d1f8
|
flutter_webrtc: d55fd3f5c75b42940b6b4b2cf376a5797398d1f8
|
||||||
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
|
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
|
||||||
@ -309,6 +318,7 @@ SPEC CHECKSUMS:
|
|||||||
media_kit_native_event_loop: 81fd5b45192b72f8b5b69eaf5b540f45777eb8d5
|
media_kit_native_event_loop: 81fd5b45192b72f8b5b69eaf5b540f45777eb8d5
|
||||||
media_kit_video: c75b07f14d59706c775778e4dd47dd027de8d1e5
|
media_kit_video: c75b07f14d59706c775778e4dd47dd027de8d1e5
|
||||||
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
|
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
|
||||||
|
OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94
|
||||||
package_info_plus: 12f1c5c2cfe8727ca46cbd0b26677728972d9a5b
|
package_info_plus: 12f1c5c2cfe8727ca46cbd0b26677728972d9a5b
|
||||||
pasteboard: 9b69dba6fedbb04866be632205d532fe2f6b1d99
|
pasteboard: 9b69dba6fedbb04866be632205d532fe2f6b1d99
|
||||||
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
|
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
|
||||||
|
66
pubspec.lock
66
pubspec.lock
@ -675,6 +675,70 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.0"
|
version: "2.3.0"
|
||||||
|
flutter_inappwebview:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: flutter_inappwebview
|
||||||
|
sha256: "80092d13d3e29b6227e25b67973c67c7210bd5e35c4b747ca908e31eb71a46d5"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.1.5"
|
||||||
|
flutter_inappwebview_android:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_inappwebview_android
|
||||||
|
sha256: "62557c15a5c2db5d195cb3892aab74fcaec266d7b86d59a6f0027abd672cddba"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.3"
|
||||||
|
flutter_inappwebview_internal_annotations:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_inappwebview_internal_annotations
|
||||||
|
sha256: "787171d43f8af67864740b6f04166c13190aa74a1468a1f1f1e9ee5b90c359cd"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.0"
|
||||||
|
flutter_inappwebview_ios:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_inappwebview_ios
|
||||||
|
sha256: "5818cf9b26cf0cbb0f62ff50772217d41ea8d3d9cc00279c45f8aabaa1b4025d"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.2"
|
||||||
|
flutter_inappwebview_macos:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_inappwebview_macos
|
||||||
|
sha256: c1fbb86af1a3738e3541364d7d1866315ffb0468a1a77e34198c9be571287da1
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.2"
|
||||||
|
flutter_inappwebview_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_inappwebview_platform_interface
|
||||||
|
sha256: cf5323e194096b6ede7a1ca808c3e0a078e4b33cc3f6338977d75b4024ba2500
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.3.0+1"
|
||||||
|
flutter_inappwebview_web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_inappwebview_web
|
||||||
|
sha256: "55f89c83b0a0d3b7893306b3bb545ba4770a4df018204917148ebb42dc14a598"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.2"
|
||||||
|
flutter_inappwebview_windows:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_inappwebview_windows
|
||||||
|
sha256: "8b4d3a46078a2cdc636c4a3d10d10f2a16882f6be607962dbfff8874d1642055"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.6.0"
|
||||||
flutter_launcher_icons:
|
flutter_launcher_icons:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
@ -875,7 +939,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.0"
|
version: "0.7.0"
|
||||||
html:
|
html:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: html
|
name: html
|
||||||
sha256: "1fc58edeaec4307368c60d59b7e15b9d658b57d7f3125098b6294153c75337ec"
|
sha256: "1fc58edeaec4307368c60d59b7e15b9d658b57d7f3125098b6294153c75337ec"
|
||||||
|
@ -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+54
|
version: 2.2.2+57
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.5.4
|
sdk: ^3.5.4
|
||||||
@ -115,6 +115,8 @@ dependencies:
|
|||||||
slide_countdown: ^2.0.2
|
slide_countdown: ^2.0.2
|
||||||
video_compress: ^3.1.3
|
video_compress: ^3.1.3
|
||||||
cached_network_image: ^3.4.1
|
cached_network_image: ^3.4.1
|
||||||
|
flutter_inappwebview: ^6.1.5
|
||||||
|
html: ^0.15.5
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
@ -16,6 +16,8 @@
|
|||||||
-->
|
-->
|
||||||
<base href="$FLUTTER_BASE_HREF">
|
<base href="$FLUTTER_BASE_HREF">
|
||||||
|
|
||||||
|
<script type="application/javascript" src="/assets/packages/flutter_inappwebview_web/assets/web/web_support.js" defer></script>
|
||||||
|
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
|
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
|
||||||
<meta name="description" content="A new Flutter project.">
|
<meta name="description" content="A new Flutter project.">
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
#include <file_saver/file_saver_plugin.h>
|
#include <file_saver/file_saver_plugin.h>
|
||||||
#include <file_selector_windows/file_selector_windows.h>
|
#include <file_selector_windows/file_selector_windows.h>
|
||||||
#include <firebase_core/firebase_core_plugin_c_api.h>
|
#include <firebase_core/firebase_core_plugin_c_api.h>
|
||||||
|
#include <flutter_inappwebview_windows/flutter_inappwebview_windows_plugin_c_api.h>
|
||||||
#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>
|
||||||
@ -34,6 +35,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
|
|||||||
registry->GetRegistrarForPlugin("FileSelectorWindows"));
|
registry->GetRegistrarForPlugin("FileSelectorWindows"));
|
||||||
FirebaseCorePluginCApiRegisterWithRegistrar(
|
FirebaseCorePluginCApiRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("FirebaseCorePluginCApi"));
|
registry->GetRegistrarForPlugin("FirebaseCorePluginCApi"));
|
||||||
|
FlutterInappwebviewWindowsPluginCApiRegisterWithRegistrar(
|
||||||
|
registry->GetRegistrarForPlugin("FlutterInappwebviewWindowsPluginCApi"));
|
||||||
FlutterUdidPluginCApiRegisterWithRegistrar(
|
FlutterUdidPluginCApiRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("FlutterUdidPluginCApi"));
|
registry->GetRegistrarForPlugin("FlutterUdidPluginCApi"));
|
||||||
FlutterWebRTCPluginRegisterWithRegistrar(
|
FlutterWebRTCPluginRegisterWithRegistrar(
|
||||||
|
@ -8,6 +8,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
|||||||
file_saver
|
file_saver
|
||||||
file_selector_windows
|
file_selector_windows
|
||||||
firebase_core
|
firebase_core
|
||||||
|
flutter_inappwebview_windows
|
||||||
flutter_udid
|
flutter_udid
|
||||||
flutter_webrtc
|
flutter_webrtc
|
||||||
gal
|
gal
|
||||||
|
Reference in New Issue
Block a user