Compare commits
3 Commits
619c90cdd9
...
00fd58fb97
Author | SHA1 | Date | |
---|---|---|---|
00fd58fb97 | |||
ee7d0ddd25 | |||
7656c08832 |
@ -15,6 +15,7 @@ analyzer:
|
||||
- "**/*.freezed.dart"
|
||||
errors:
|
||||
invalid_annotation_target: ignore # Due to freezed + json_serializable issue, ref https://github.com/rrousselGit/freezed/issues/488#issuecomment-894358980
|
||||
deprecated_member_use: ignore
|
||||
|
||||
linter:
|
||||
# The lint rules applied to this project can be customized in the
|
||||
|
@ -279,16 +279,22 @@
|
||||
"one": "{} 個附件",
|
||||
"other": "{} 個附件"
|
||||
},
|
||||
"fieldAttachmentRandomId": "訪問 ID",
|
||||
"addAttachmentFromAlbum": "從相冊中添加附件",
|
||||
"addAttachmentFromClipboard": "粘貼附件",
|
||||
"addAttachmentFromCameraPhoto": "拍攝照片",
|
||||
"addAttachmentFromCameraVideo": "拍攝視頻",
|
||||
"addAttachmentFromRandomId": "通過訪問 ID 鏈接",
|
||||
"attachmentPastedImage": "粘貼的圖片",
|
||||
"attachmentInsertLink": "插入連接",
|
||||
"attachmentSetAsPostThumbnail": "設置為帖子縮略圖",
|
||||
"attachmentUnsetAsPostThumbnail": "取消設置為帖子縮略圖",
|
||||
"attachmentSetThumbnail": "設置縮略圖",
|
||||
"attachmentCopyRandomId": "複製訪問 ID",
|
||||
"attachmentUpload": "上傳",
|
||||
"attachmentInputDialog": "上傳附件",
|
||||
"attachmentInputUseRandomId": "使用訪問 ID",
|
||||
"attachmentInputNew": "新上傳附件",
|
||||
"notification": "通知",
|
||||
"notificationUnreadCount": {
|
||||
"zero": "無未讀通知",
|
||||
@ -504,5 +510,6 @@
|
||||
"postCategoryKnowledge": "知識",
|
||||
"postCategoryLiterature": "文學",
|
||||
"postCategoryFunny": "搞笑",
|
||||
"postCategoryUncategorized": "未分類"
|
||||
"postCategoryUncategorized": "未分類",
|
||||
"waitingForUpload": "等待上傳"
|
||||
}
|
||||
|
@ -7,15 +7,15 @@
|
||||
"screenAuthLogin": "登陸",
|
||||
"screenAuthLoginSubtitle": "使用 Solarpass 登陸 Solar Network",
|
||||
"screenAuthLoginGreeting": "歡迎回來",
|
||||
"screenAuthRegister": "建立賬號",
|
||||
"screenAuthRegisterSubtitle": "建立一個 Solarpass 賬號",
|
||||
"screenAccountPublishers": "釋出者",
|
||||
"screenAccountPublisherNew": "新建釋出者",
|
||||
"screenAccountPublisherEdit": "編輯釋出者",
|
||||
"screenAuthRegister": "創建賬號",
|
||||
"screenAuthRegisterSubtitle": "創建一個 Solarpass 賬號",
|
||||
"screenAccountPublishers": "發佈者",
|
||||
"screenAccountPublisherNew": "新建發佈者",
|
||||
"screenAccountPublisherEdit": "編輯發佈者",
|
||||
"screenAccountProfileEdit": "編輯資料",
|
||||
"screenAbuseReport": "濫用檢舉",
|
||||
"screenSettings": "設定",
|
||||
"screenAlbum": "相簿",
|
||||
"screenSettings": "設置",
|
||||
"screenAlbum": "相冊",
|
||||
"screenChat": "聊天",
|
||||
"screenChatManage": "編輯聊天頻道",
|
||||
"screenChatNew": "新建聊天頻道",
|
||||
@ -23,37 +23,37 @@
|
||||
"screenRealmManage": "編輯領域",
|
||||
"screenRealmNew": "新建領域",
|
||||
"screenNotification": "通知",
|
||||
"screenPostSearch": "搜尋帖子",
|
||||
"screenPostSearch": "搜索帖子",
|
||||
"screenFriend": "好友",
|
||||
"dialogOkay": "好的",
|
||||
"dialogCancel": "取消",
|
||||
"dialogConfirm": "確認",
|
||||
"dialogDismiss": "忽略",
|
||||
"dialogError": "出了點問題",
|
||||
"errorRequestBad": "伺服器拒絕了您的請求,請檢查您的輸入。",
|
||||
"errorRequestUnauthorized": "未授權的請求,請登入或者嘗試重新登陸。",
|
||||
"errorRequestForbidden": "被禁止的請求,您沒有足夠的許可權去做那件事。",
|
||||
"errorRequestNotFound": "您正查詢的資源無法被找到。",
|
||||
"errorRequestConnection": "網路連線錯誤,請檢查您的網路狀態或者檢查我們的服務狀態。",
|
||||
"errorRequestUnknown": "未知請求錯誤,您可能想將此對話方塊截圖併發送給我們。",
|
||||
"errorRequestBad": "服務器拒絕了您的請求,請檢查您的輸入。",
|
||||
"errorRequestUnauthorized": "未授權的請求,請登錄或者嘗試重新登陸。",
|
||||
"errorRequestForbidden": "被禁止的請求,您沒有足夠的權限去做那件事。",
|
||||
"errorRequestNotFound": "您正查找的資源無法被找到。",
|
||||
"errorRequestConnection": "網絡連接錯誤,請檢查您的網絡狀態或者檢查我們的服務狀態。",
|
||||
"errorRequestUnknown": "未知請求錯誤,您可能想將此對話框截圖併發送給我們。",
|
||||
"unknown": "未知",
|
||||
"loading": "載入中…",
|
||||
"loading": "加載中…",
|
||||
"prev": "上一步",
|
||||
"next": "下一步",
|
||||
"edit": "編輯",
|
||||
"apply": "應用",
|
||||
"cancel": "取消",
|
||||
"create": "建立",
|
||||
"create": "創建",
|
||||
"preview": "預覽",
|
||||
"delete": "刪除",
|
||||
"unlink": "解除連結",
|
||||
"unlink": "解除鏈接",
|
||||
"crop": "裁剪",
|
||||
"compress": "壓縮",
|
||||
"report": "檢舉",
|
||||
"repost": "轉帖",
|
||||
"replyPost": "回貼",
|
||||
"reply": "回覆",
|
||||
"unset": "未設定",
|
||||
"unset": "未設置",
|
||||
"untitled": "無題",
|
||||
"postDetail": "帖子詳情",
|
||||
"postNoun": "帖子",
|
||||
@ -64,20 +64,20 @@
|
||||
"one": "總計 {} 字",
|
||||
"other": "總計 {} 字"
|
||||
},
|
||||
"fieldUsername": "使用者名稱",
|
||||
"fieldUsername": "用戶名",
|
||||
"fieldNickname": "顯示名",
|
||||
"fieldEmail": "電子郵箱地址",
|
||||
"fieldPassword": "密碼",
|
||||
"fieldUsernameAlphanumOnly": "使用者名稱只能包含英文大小寫字母和數字。",
|
||||
"fieldUsernameLengthLimit": "使用者名稱必須在 {} 和 {} 之間。",
|
||||
"fieldUsernameCannotEditHint": "使用者名稱在建立後無法修改",
|
||||
"fieldUsernameLookupHint": "支援使用者名稱、電話號碼或郵箱地址",
|
||||
"fieldUsernameAlphanumOnly": "用戶名只能包含英文大小寫字母和數字。",
|
||||
"fieldUsernameLengthLimit": "用戶名必須在 {} 和 {} 之間。",
|
||||
"fieldUsernameCannotEditHint": "用戶名在創建後無法修改",
|
||||
"fieldUsernameLookupHint": "支持用戶名、電話號碼或郵箱地址",
|
||||
"fieldNicknameLengthLimit": "暱稱必須在 {} 和 {} 之間。",
|
||||
"fieldEmailAddressMustBeValid": "電子郵箱地址必須是一個電子郵箱地址。",
|
||||
"fieldFirstName": "名",
|
||||
"fieldLastName": "姓",
|
||||
"fieldBirthday": "生日",
|
||||
"fieldImageHint": "你可以點選這些個人頭像來編輯它們。",
|
||||
"fieldImageHint": "你可以點擊這些個人頭像來編輯它們。",
|
||||
"fieldDescription": "簡介",
|
||||
"forgotPassword": "忘記密碼",
|
||||
"loginPickFactor": "選擇方式驗證",
|
||||
@ -85,24 +85,24 @@
|
||||
"one": "{} 步驗證",
|
||||
"other": "{} 步驗證"
|
||||
},
|
||||
"loginEnterPassword": "驗證程式碼",
|
||||
"loginSuccess": "登入為 {}",
|
||||
"loginEnterPassword": "驗證代碼",
|
||||
"loginSuccess": "登錄為 {}",
|
||||
"authFactorPassword": "密碼",
|
||||
"authFactorEmail": "電郵一次性驗證碼",
|
||||
"accountIntroTitle": "喜歡您來!",
|
||||
"accountIntroSubtitle": "登陸以探索更廣大的世界。",
|
||||
"accountLogout": "退出登入",
|
||||
"accountLogoutSubtitle": "登出當前賬戶的登陸狀態。",
|
||||
"accountLogoutConfirmTitle": "您確定要退出登入嗎?",
|
||||
"accountLogout": "退出登錄",
|
||||
"accountLogoutSubtitle": "註銷當前賬戶的登陸狀態。",
|
||||
"accountLogoutConfirmTitle": "您確定要退出登錄嗎?",
|
||||
"accountLogoutConfirm": "您需要重新輸入賬號密碼,甚至可能需要多步驗證來再次登陸。",
|
||||
"accountPublishers": "你的釋出者",
|
||||
"accountPublishers": "你的發佈者",
|
||||
"accountPublishersSubtitle": "管理你的公共形象。",
|
||||
"accountProfileEdit": "編輯資料",
|
||||
"accountProfileEditSubtitle": "使你的 Solarpass 賬戶更像你。",
|
||||
"accountProfileEditApplied": "個人資料修改已被應用。",
|
||||
"publishersNew": "新發布者",
|
||||
"publisherNewSubtitle": "建立一個新的公共身份。",
|
||||
"publisherSyncWithAccount": "同步賬戶資訊",
|
||||
"publisherNewSubtitle": "創建一個新的公共身份。",
|
||||
"publisherSyncWithAccount": "同步賬戶信息",
|
||||
"publisherTotalUpvote": "總頂數",
|
||||
"publisherTotalDownvote": "總踩數",
|
||||
"publisherSocialPoint": "社會信用點",
|
||||
@ -115,10 +115,10 @@
|
||||
"publisherAffiliatedBy": "隸屬於 {}",
|
||||
"publisherRunBy": "由 {} 管理",
|
||||
"fieldPublisherBelongToRealm": "所屬領域",
|
||||
"fieldPublisherBelongToRealmUnset": "未設定釋出者所屬領域",
|
||||
"fieldPublisherBelongToRealmUnset": "未設置發佈者所屬領域",
|
||||
"writePostTypeStory": "發動態",
|
||||
"writePostTypeArticle": "寫文章",
|
||||
"fieldPostPublisher": "帖子釋出者",
|
||||
"fieldPostPublisher": "帖子發佈者",
|
||||
"fieldPostContent": "發生什麼事了?!",
|
||||
"fieldPostTitle": "標題",
|
||||
"fieldPostDescription": "描述",
|
||||
@ -126,26 +126,26 @@
|
||||
"fieldPostCategories": "分類",
|
||||
"fieldPostAlias": "別名",
|
||||
"fieldPostAliasHint": "可選項,用於在 URL 中表示該帖子,應遵循 URL-Safe 的原則。",
|
||||
"postPublish": "釋出",
|
||||
"postPublishedAt": "釋出於",
|
||||
"postPublishedUntil": "取消釋出於",
|
||||
"postPublish": "發佈",
|
||||
"postPublishedAt": "發佈於",
|
||||
"postPublishedUntil": "取消發佈於",
|
||||
"postVisibility": "可見性",
|
||||
"postVisibilityDescription": "帖子可見性決定了誰能檢視該篇帖子。",
|
||||
"postVisibilityDescription": "帖子可見性決定了誰能查看該篇帖子。",
|
||||
"postVisibilityAll": "所有人可見",
|
||||
"postVisibilityFriends": "僅限好友可見",
|
||||
"postVisibilitySelected": "選定的使用者可見",
|
||||
"postVisibilityFiltered": "選定使用者不可見",
|
||||
"postVisibilitySelected": "選定的用戶可見",
|
||||
"postVisibilityFiltered": "選定用戶不可見",
|
||||
"postVisibilityNone": "僅自己可見",
|
||||
"postVisibleUsers": "可見的使用者",
|
||||
"postInvisibleUsers": "不可見的使用者",
|
||||
"postVisibleUsers": "可見的用戶",
|
||||
"postInvisibleUsers": "不可見的用戶",
|
||||
"postSelectedUsers": {
|
||||
"zero": "未選擇使用者",
|
||||
"one": "選擇了 {} 個使用者",
|
||||
"other": "選擇了 {} 個使用者"
|
||||
"zero": "未選擇用戶",
|
||||
"one": "選擇了 {} 個用戶",
|
||||
"other": "選擇了 {} 個用戶"
|
||||
},
|
||||
"postEditingNotice": "你正在修改由 {} 釋出的帖子。",
|
||||
"postReplyingNotice": "你正在回覆由 {} 釋出的帖子。",
|
||||
"postRepostingNotice": "你正在轉發由 {} 釋出的帖子。",
|
||||
"postEditingNotice": "你正在修改由 {} 發佈的帖子。",
|
||||
"postReplyingNotice": "你正在回覆由 {} 發佈的帖子。",
|
||||
"postRepostingNotice": "你正在轉發由 {} 發佈的帖子。",
|
||||
"postReact": "反應",
|
||||
"postPosted": "帖子已經發表。",
|
||||
"postReactions": "帖子的反應",
|
||||
@ -164,7 +164,7 @@
|
||||
"one": "{} 點社會信用點變更",
|
||||
"other": "{} 點社會信用點變更"
|
||||
},
|
||||
"postReactCompleted": "反應已被新增。",
|
||||
"postReactCompleted": "反應已被添加。",
|
||||
"postReactUncompleted": "反應已被移除。",
|
||||
"postComments": {
|
||||
"zero": "評論",
|
||||
@ -178,76 +178,76 @@
|
||||
},
|
||||
"settingsAppearance": "外觀",
|
||||
"settingsBackgroundImage": "背景圖片",
|
||||
"settingsBackgroundImageDescription": "設定應用全域性生效的的背景圖片。",
|
||||
"settingsBackgroundImageDescription": "設置應用全局生效的的背景圖片。",
|
||||
"settingsBackgroundImageClear": "清除現存背景圖",
|
||||
"settingsBackgroundImageClearDescription": "將應用背景圖重置為空白。",
|
||||
"settingsThemeMaterial3": "使用 Material You 設計正規化",
|
||||
"settingsThemeMaterial3Description": "將應用主題設定為 Material 3 設計正規化的主題。",
|
||||
"settingsThemeMaterial3": "使用 Material You 設計範式",
|
||||
"settingsThemeMaterial3Description": "將應用主題設置為 Material 3 設計範式的主題。",
|
||||
"settingsAppBarTransparent": "透明頂欄",
|
||||
"settingsAppBarTransparentDescription": "為頂欄啟用透明效果。",
|
||||
"settingsColorScheme": "主題色",
|
||||
"settingsColorSchemeDescription": "設定應用主題色。",
|
||||
"settingsColorSchemeDescription": "設置應用主題色。",
|
||||
"settingsColorSeed": "預設色彩主題",
|
||||
"settingsColorSeedDescription": "選擇一個預設色彩主題。",
|
||||
"settingsNetwork": "網路",
|
||||
"settingsNetworkServer": "HyperNet 伺服器",
|
||||
"settingsNetworkServerDescription": "設定 HyperNet 伺服器地址,選擇我們提供的,或者自己搭建。",
|
||||
"settingsNetworkServerReset": "重設為官方伺服器",
|
||||
"settingsNetworkServerResetDescription": "重設為 Solar Network 的伺服器地址。",
|
||||
"settingsNetworkServerPreset": "預設的 HyperNet 伺服器",
|
||||
"settingsNetworkServerPresetDescription": "你可以在旁邊的列表中選擇我們提供的預設 HyperNet 伺服器地址。",
|
||||
"settingsNetworkServerSaved": "伺服器地址已儲存。",
|
||||
"settingsPerformance": "效能",
|
||||
"settingsNetwork": "網絡",
|
||||
"settingsNetworkServer": "HyperNet 服務器",
|
||||
"settingsNetworkServerDescription": "設置 HyperNet 服務器地址,選擇我們提供的,或者自己搭建。",
|
||||
"settingsNetworkServerReset": "重設為官方服務器",
|
||||
"settingsNetworkServerResetDescription": "重設為 Solar Network 的服務器地址。",
|
||||
"settingsNetworkServerPreset": "預設的 HyperNet 服務器",
|
||||
"settingsNetworkServerPresetDescription": "你可以在旁邊的列表中選擇我們提供的預設 HyperNet 服務器地址。",
|
||||
"settingsNetworkServerSaved": "服務器地址已保存。",
|
||||
"settingsPerformance": "性能",
|
||||
"settingsImageQuality": "圖片預覽質量",
|
||||
"settingsImageQualityDescription": "設定圖片預覽質量,會影響圖片解碼速度。",
|
||||
"settingsImageQualityDescription": "設置圖片預覽質量,會影響圖片解碼速度。",
|
||||
"settingsImageQualityLowest": "極低",
|
||||
"settingsImageQualityLow": "低",
|
||||
"settingsImageQualityMedium": "中",
|
||||
"settingsImageQualityHigh": "高",
|
||||
"settingsMisc": "雜項",
|
||||
"settingsMiscAbout": "關於",
|
||||
"settingsMiscAboutDescription": "檢視 Solian 的版本資訊。",
|
||||
"settingsMiscAboutDescription": "查看 Solian 的版本信息。",
|
||||
"sensitiveContent": "敏感內容",
|
||||
"sensitiveContentCollapsed": "敏感內容已摺疊。",
|
||||
"sensitiveContentDescription": "此內容已被標記,可能不適合所有人檢視。",
|
||||
"sensitiveContentDescription": "此內容已被標記,可能不適合所有人查看。",
|
||||
"sensitiveContentReveal": "顯示內容",
|
||||
"serverConnecting": "正在連線伺服器…",
|
||||
"serverDisconnected": "已與伺服器斷開連線",
|
||||
"serverConnecting": "正在連接服務器…",
|
||||
"serverDisconnected": "已與服務器斷開連接",
|
||||
"fieldChatAlias": "頻道別名",
|
||||
"fieldChatAliasHint": "全站範圍內唯一的頻道別名,用於在 URL 中表示該頻道,留空則自動生成。應遵循 URL-Safe 的原則。",
|
||||
"fieldChatName": "名稱",
|
||||
"fieldChatDescription": "描述",
|
||||
"fieldChatBelongToRealm": "所屬領域",
|
||||
"fieldChatBelongToRealmUnset": "未設定頻道所屬領域",
|
||||
"fieldChatBelongToRealmUnset": "未設置頻道所屬領域",
|
||||
"channelEditingNotice": "您正在編輯頻道 {}",
|
||||
"channelDeleted": "聊天頻道 {} 已被刪除",
|
||||
"channelDelete": "刪除聊天頻道 {}",
|
||||
"channelDeleteDescription": "你確定要刪除這個聊天頻道嗎?該操作不可撤銷,其頻道內的所有訊息將被永久刪除。",
|
||||
"channelDeleteDescription": "你確定要刪除這個聊天頻道嗎?該操作不可撤銷,其頻道內的所有消息將被永久刪除。",
|
||||
"channelDetailPersonalRegion": "個人區域",
|
||||
"channelDetailMemberRegion": "成員管理",
|
||||
"channelMemberManage": "管理成員",
|
||||
"channelMemberManageDescription": "管理頻道內現有成員。",
|
||||
"channelMemberAdd": "新增成員",
|
||||
"channelMemberAddDescription": "給當前頻道新增新成員。",
|
||||
"channelMemberAdded": "頻道成員已新增。",
|
||||
"channelMemberAdd": "添加成員",
|
||||
"channelMemberAddDescription": "給當前頻道添加新成員。",
|
||||
"channelMemberAdded": "頻道成員已添加。",
|
||||
"fieldMemberRelatedName": "成員名 / 賬戶 ID",
|
||||
"channelDetailAdminRegion": "管理區域",
|
||||
"channelEditProfile": "更改頻道身份",
|
||||
"channelEdit": "編輯頻道",
|
||||
"channelEditDescription": "更改頻道基本資訊,元資料等。",
|
||||
"channelEditDescription": "更改頻道基本信息,元數據等。",
|
||||
"channelProfileEdit": "編輯頻道身份",
|
||||
"channelActionDelete": "刪除頻道",
|
||||
"channelActionDeleteDescription": "刪除整個頻道,並且刪除頻道里的所有資訊。",
|
||||
"channelActionDeleteDescription": "刪除整個頻道,並且刪除頻道里的所有信息。",
|
||||
"channelLeave": "退出頻道 {}",
|
||||
"channelLeaveDescription": "退出該頻道,但是你頻道內的資訊不會被移除。",
|
||||
"channelLeaveDescription": "退出該頻道,但是你頻道內的信息不會被移除。",
|
||||
"channelActionLeave": "退出頻道",
|
||||
"channelActionLeaveDescription": "刪除你在這個頻道的身份。",
|
||||
"channelNotifyLevel": "通知級別",
|
||||
"channelNotifyLevelDescription": "有您決定要接受多少來自這個頻道的訊息。",
|
||||
"channelNotifyLevelDescription": "有您決定要接受多少來自這個頻道的消息。",
|
||||
"channelNotifyLevelAll": "全部通知",
|
||||
"channelNotifyLevelMentioned": "僅提及",
|
||||
"channelNotifyLevelNone": "全部靜音",
|
||||
"channelNotifyLevelApplied": "已經儲存並應用頻道通知級別配置。",
|
||||
"channelNotifyLevelApplied": "已經保存並應用頻道通知級別配置。",
|
||||
"fieldChannelProfileNick": "頻道內顯示名",
|
||||
"fieldChannelProfileNickHint": "在頻道內顯示的暱稱,留空則使用賬號顯示名。",
|
||||
"fieldRealmAlias": "領域別名",
|
||||
@ -257,38 +257,44 @@
|
||||
"realmEditingNotice": "您正在編輯領域 {}",
|
||||
"realmDeleted": "領域 {} 已被刪除",
|
||||
"realmDelete": "刪除領域 {}",
|
||||
"realmDeleteDescription": "你確定要刪除這個領域嗎?該操作不可撤銷,其隸屬於該領域的所有資源(帖子、聊天頻道、釋出者、製品等)都將被永久刪除。三思而後行!",
|
||||
"realmDeleteDescription": "你確定要刪除這個領域嗎?該操作不可撤銷,其隸屬於該領域的所有資源(帖子、聊天頻道、發佈者、製品等)都將被永久刪除。三思而後行!",
|
||||
"realmActionDelete": "刪除領域",
|
||||
"realmActionDeleteDescription": "刪除整個領域及其附屬的資源。",
|
||||
"realmEdit": "編輯領域",
|
||||
"realmEditDescription": "更改領域基本資訊,元資料等。",
|
||||
"realmMemberAdd": "新增成員",
|
||||
"realmMemberAddDescription": "給當前領域新增新成員。",
|
||||
"realmMemberAdded": "領域成員已新增。",
|
||||
"fieldChatMessage": "在 {} 中發訊息",
|
||||
"fieldChatMessageDirect": "給 {} 發訊息",
|
||||
"eventResourceTag": "訊息 {}",
|
||||
"messageDelete": "刪除訊息 {}",
|
||||
"messageDeleteDescription": "你確定要刪除這個訊息嗎?該操作不可撤銷。同時您將留下一條刪除訊息的記錄。",
|
||||
"messageDeleted": "訊息 {} 已被刪除",
|
||||
"messageEdited": "訊息 {} 已被編輯",
|
||||
"realmEditDescription": "更改領域基本信息,元數據等。",
|
||||
"realmMemberAdd": "添加成員",
|
||||
"realmMemberAddDescription": "給當前領域添加新成員。",
|
||||
"realmMemberAdded": "領域成員已添加。",
|
||||
"fieldChatMessage": "在 {} 中發消息",
|
||||
"fieldChatMessageDirect": "給 {} 發消息",
|
||||
"eventResourceTag": "消息 {}",
|
||||
"messageDelete": "刪除消息 {}",
|
||||
"messageDeleteDescription": "你確定要刪除這個消息嗎?該操作不可撤銷。同時您將留下一條刪除消息的記錄。",
|
||||
"messageDeleted": "消息 {} 已被刪除",
|
||||
"messageEdited": "消息 {} 已被編輯",
|
||||
"messageEditedHint": "已編輯",
|
||||
"messageUnsupported": "不支援的訊息 {}",
|
||||
"messageUnsupported": "不支持的消息 {}",
|
||||
"messageFileHint": {
|
||||
"zero": "沒有附件",
|
||||
"one": "{} 個附件",
|
||||
"other": "{} 個附件"
|
||||
},
|
||||
"addAttachmentFromAlbum": "從相簿中新增附件",
|
||||
"addAttachmentFromClipboard": "貼上附件",
|
||||
"fieldAttachmentRandomId": "訪問 ID",
|
||||
"addAttachmentFromAlbum": "從相冊中添加附件",
|
||||
"addAttachmentFromClipboard": "粘貼附件",
|
||||
"addAttachmentFromCameraPhoto": "拍攝照片",
|
||||
"addAttachmentFromCameraVideo": "拍攝影片",
|
||||
"attachmentPastedImage": "貼上的圖片",
|
||||
"attachmentInsertLink": "插入連線",
|
||||
"attachmentSetAsPostThumbnail": "設定為帖子縮圖",
|
||||
"attachmentUnsetAsPostThumbnail": "取消設定為帖子縮圖",
|
||||
"attachmentSetThumbnail": "設定縮圖",
|
||||
"addAttachmentFromCameraVideo": "拍攝視頻",
|
||||
"addAttachmentFromRandomId": "通過訪問 ID 鏈接",
|
||||
"attachmentPastedImage": "粘貼的圖片",
|
||||
"attachmentInsertLink": "插入連接",
|
||||
"attachmentSetAsPostThumbnail": "設置為帖子縮略圖",
|
||||
"attachmentUnsetAsPostThumbnail": "取消設置為帖子縮略圖",
|
||||
"attachmentSetThumbnail": "設置縮略圖",
|
||||
"attachmentCopyRandomId": "複製訪問 ID",
|
||||
"attachmentUpload": "上傳",
|
||||
"attachmentInputDialog": "上傳附件",
|
||||
"attachmentInputUseRandomId": "使用訪問 ID",
|
||||
"attachmentInputNew": "新上傳附件",
|
||||
"notification": "通知",
|
||||
"notificationUnreadCount": {
|
||||
"zero": "無未讀通知",
|
||||
@ -298,18 +304,18 @@
|
||||
"notificationUnread": "未讀",
|
||||
"notificationRead": "已讀",
|
||||
"notificationMarkAllRead": "已讀所有通知",
|
||||
"notificationMarkAllReadDescription": "您確定要將所有通知設定為已讀嗎?該操作不可撤銷。",
|
||||
"notificationMarkAllReadDescription": "您確定要將所有通知設置為已讀嗎?該操作不可撤銷。",
|
||||
"notificationMarkAllReadPrompt": {
|
||||
"zero": "已將 0 個通知標記為已讀。",
|
||||
"one": "已將 {} 個通知標記為已讀。",
|
||||
"other": "已將 {} 個通知標記為已讀。"
|
||||
},
|
||||
"notificationMarkOneReadPrompt": "已將通知 {} 標記為已讀。",
|
||||
"search": "搜尋",
|
||||
"search": "搜索",
|
||||
"postSearchResult": {
|
||||
"zero": "沒有搜尋到結果",
|
||||
"one": "搜尋到 {} 個結果",
|
||||
"other": "搜尋到 {} 個結果"
|
||||
"zero": "沒有搜索到結果",
|
||||
"one": "搜索到 {} 個結果",
|
||||
"other": "搜索到 {} 個結果"
|
||||
},
|
||||
"postSearchTook": "耗時 {}",
|
||||
"postDelete": "刪除帖子 {}",
|
||||
@ -321,26 +327,26 @@
|
||||
"callResume": "恢復",
|
||||
"callMicrophone": "麥克風",
|
||||
"callCamera": "攝像頭",
|
||||
"callMicrophoneDisabled": "麥克風已停用",
|
||||
"callMicrophoneDisabled": "麥克風已禁用",
|
||||
"callMicrophoneSelect": "選擇麥克風",
|
||||
"callCameraDisabled": "攝像頭已停用",
|
||||
"callCameraDisabled": "攝像頭已禁用",
|
||||
"callCameraSelect": "選擇攝像頭",
|
||||
"callDisconnected": "通話已斷開",
|
||||
"callEnded": "通話已結束",
|
||||
"callStatusConnected": "已連線",
|
||||
"callStatusDisconnected": "未連線",
|
||||
"callStatusConnecting": "正在連線",
|
||||
"callStatusConnected": "已連接",
|
||||
"callStatusDisconnected": "未連接",
|
||||
"callStatusConnecting": "正在連接",
|
||||
"callStatusReconnecting": "正在重連",
|
||||
"callDisconnect": "斷開連線",
|
||||
"callDisconnectDescription": "您確定要與通話斷開連線嗎?",
|
||||
"callDisconnect": "斷開連接",
|
||||
"callDisconnectDescription": "您確定要與通話斷開連接嗎?",
|
||||
"callMicrophoneOff": "關閉麥克風",
|
||||
"callMicrophoneOn": "開啟麥克風",
|
||||
"callMicrophoneOn": "打開麥克風",
|
||||
"callCameraOff": "關閉攝像頭",
|
||||
"callCameraOn": "開啟攝像頭",
|
||||
"callVideoFlip": "映象畫面",
|
||||
"callCameraOn": "打開攝像頭",
|
||||
"callVideoFlip": "鏡像畫面",
|
||||
"callSpeakerphoneToggle": "切換揚聲器",
|
||||
"callScreenOff": "關閉螢幕共享",
|
||||
"callScreenOn": "開啟螢幕共享",
|
||||
"callScreenOff": "關閉屏幕共享",
|
||||
"callScreenOn": "開啟屏幕共享",
|
||||
"callMessageEnded": "通話持續了 {}",
|
||||
"callMessageStarted": "通話開始了",
|
||||
"dailyCheckIn": "每日簽到",
|
||||
@ -396,27 +402,27 @@
|
||||
"pendingFatherDay": "{} 過父親節",
|
||||
"pendingHalloween": "{} 過聖誕節",
|
||||
"pendingThanksgiving": "{} 過感恩節",
|
||||
"friendNew": "新增好友",
|
||||
"friendNew": "添加好友",
|
||||
"friendRequests": "好友請求",
|
||||
"friendRequestsDescription": {
|
||||
"zero": "你沒有好友請求",
|
||||
"one": "你有 {} 個好友請求",
|
||||
"other": "你有 {} 個好友請求"
|
||||
},
|
||||
"friendBlocklist": "遮蔽列表",
|
||||
"friendBlocklist": "屏蔽列表",
|
||||
"friendBlocklistDescription": {
|
||||
"zero": "你沒有遮蔽任何人",
|
||||
"one": "你遮蔽了 {} 個使用者",
|
||||
"other": "你遮蔽了 {} 個使用者"
|
||||
"zero": "你沒有屏蔽任何人",
|
||||
"one": "你屏蔽了 {} 個用戶",
|
||||
"other": "你屏蔽了 {} 個用戶"
|
||||
},
|
||||
"friendStatusPending": "待處理",
|
||||
"friendStatusWaiting": "等待中",
|
||||
"friendStatusActive": "正活躍",
|
||||
"friendStatusBlocked": "已遮蔽",
|
||||
"friendRequestSent": "好友請求已傳送。",
|
||||
"friendStatusBlocked": "已屏蔽",
|
||||
"friendRequestSent": "好友請求已發送。",
|
||||
"fieldFriendRelatedName": "好友名 / 賬戶 ID",
|
||||
"friendBlock": "遮蔽",
|
||||
"friendUnblock": "解除遮蔽",
|
||||
"friendBlock": "屏蔽",
|
||||
"friendUnblock": "解除屏蔽",
|
||||
"friendDeleteAction": "遺忘",
|
||||
"friendDelete": "遺忘跟 {} 的關係",
|
||||
"friendDeleteDescription": "你確定要遺忘跟 {} 的關係嗎?這個操作無法撤銷。",
|
||||
@ -432,20 +438,20 @@
|
||||
"badgeCompanyStaff": "索爾辛茨士大夫 · 員工",
|
||||
"badgeSiteMigration": "Solar Network 原住民",
|
||||
"accountStatus": "狀態",
|
||||
"accountStatusOnline": "線上",
|
||||
"accountStatusOnline": "在線",
|
||||
"accountStatusOffline": "離線",
|
||||
"accountStatusLastSeen": "最後一次上線於 {}",
|
||||
"postArticle": "Solar Network 上的文章",
|
||||
"postStory": "Solar Network 上的故事",
|
||||
"articleWrittenAt": "發表於 {}",
|
||||
"articleEditedAt": "編輯於 {}",
|
||||
"attachmentSaved": "已儲存到相簿",
|
||||
"attachmentSavedDesktop": "已儲存到下載目錄",
|
||||
"openInAlbum": "在相簿中開啟",
|
||||
"attachmentSaved": "已保存到相冊",
|
||||
"attachmentSavedDesktop": "已保存到下載目錄",
|
||||
"openInAlbum": "在相冊中打開",
|
||||
"postAbuseReport": "檢舉帖子",
|
||||
"postAbuseReportDescription": "檢舉不符合我們使用者協議以及社群準則的帖子,來幫助我們更好的維護 Solar Network 上的內容。請在下面描述該帖子如何違反我麼的相關規定。請勿填寫任何敏感資訊。我們將會在 24 小時內處理您的檢舉。",
|
||||
"postAbuseReportDescription": "檢舉不符合我們用戶協議以及社區準則的帖子,來幫助我們更好的維護 Solar Network 上的內容。請在下面描述該帖子如何違反我麼的相關規定。請勿填寫任何敏感信息。我們將會在 24 小時內處理您的檢舉。",
|
||||
"abuseReport": "檢舉",
|
||||
"abuseReportDescription": "檢舉不符合我們使用者協議以及社群準則的任何資源,來幫助我們更好的維護 Solar Network 上的內容。請在下面描述資源的位置(提供資源 ID 為佳)以及如何違反我麼的相關規定。請勿填寫任何敏感資訊。我們將會在 24 小時內處理您的檢舉。",
|
||||
"abuseReportDescription": "檢舉不符合我們用戶協議以及社區準則的任何資源,來幫助我們更好的維護 Solar Network 上的內容。請在下面描述資源的位置(提供資源 ID 為佳)以及如何違反我麼的相關規定。請勿填寫任何敏感信息。我們將會在 24 小時內處理您的檢舉。",
|
||||
"abuseReportAction": "提交檢舉",
|
||||
"abuseReportActionDescription": "檢舉不合規行為。",
|
||||
"abuseReportResource": "資源位置 / ID",
|
||||
@ -453,35 +459,35 @@
|
||||
"abuseReportSubmitted": "檢舉已提交,感謝你的貢獻。",
|
||||
"submit": "提交",
|
||||
"accountDeletion": "刪除帳戶",
|
||||
"accountDeletionDescription": "你確定要刪除這個帳戶嗎?該操作不可撤銷,其隸屬於該帳戶的所有資源(帖子、聊天頻道、釋出者、製品等)都將被永久刪除。三思而後行!",
|
||||
"accountDeletionDescription": "你確定要刪除這個帳戶嗎?該操作不可撤銷,其隸屬於該帳戶的所有資源(帖子、聊天頻道、發佈者、製品等)都將被永久刪除。三思而後行!",
|
||||
"accountDeletionActionDescription": "刪除你的 Solarpass 帳戶。",
|
||||
"accountDeletionSubmitted": "帳戶刪除申請已發出,你可以檢查你的收件箱並根據郵件內的指示完成刪除操作。",
|
||||
"channelNewChannel": "新建頻道",
|
||||
"channelNewDirectMessage": "發起私信",
|
||||
"channelDirectMessageDescription": "與 {} 的私聊",
|
||||
"fieldCannotBeEmpty": "此欄位不能為空。",
|
||||
"fieldCannotBeEmpty": "此字段不能為空。",
|
||||
"termAcceptLink": "瀏覽條款",
|
||||
"termAcceptNextWithAgree": "點選 “下一步”,即表示你同意我們的各項條款,包括其之後的更新。",
|
||||
"termAcceptNextWithAgree": "點擊 “下一步”,即表示你同意我們的各項條款,包括其之後的更新。",
|
||||
"unauthorized": "未登陸",
|
||||
"unauthorizedDescription": "登陸以探索整個 Solar Network。",
|
||||
"serviceStatus": "服務狀態",
|
||||
"termRelated": "相關條款",
|
||||
"appDetails": "應用程式詳情",
|
||||
"appDetails": "應用程序詳情",
|
||||
"postRecommendation": "推薦帖子",
|
||||
"publisherBlockHint": "遮蔽 {}",
|
||||
"publisherBlockHintDescription": "你正要遮蔽此釋出者的運營者,該操作也將遮蔽由同一使用者運營的釋出者。",
|
||||
"userUnblocked": "已解除遮蔽使用者 {}",
|
||||
"userBlocked": "已遮蔽使用者 {}",
|
||||
"publisherBlockHint": "屏蔽 {}",
|
||||
"publisherBlockHintDescription": "你正要屏蔽此發佈者的運營者,該操作也將屏蔽由同一用戶運營的發佈者。",
|
||||
"userUnblocked": "已解除屏蔽用戶 {}",
|
||||
"userBlocked": "已屏蔽用戶 {}",
|
||||
"postSharingViaPicture": "正在生成帖子截圖,請稍等片刻……",
|
||||
"postImageShareReadMore": "掃描右側 QRCode 檢視全文",
|
||||
"postImageShareReadMore": "掃描右側 QRCode 查看全文",
|
||||
"postImageShareAds": "來 Solar Network 探索更多有趣帖子",
|
||||
"postShare": "分享",
|
||||
"postShareImage": "分享帖圖",
|
||||
"appInitializing": "正在初始化",
|
||||
"poweredBy": "由 {} 提供支援",
|
||||
"poweredBy": "由 {} 提供支持",
|
||||
"shareIntent": "分享",
|
||||
"shareIntentDescription": "您想對您分享的內容做些什麼?",
|
||||
"shareIntentPostStory": "釋出動態",
|
||||
"shareIntentPostStory": "發佈動態",
|
||||
"updateAvailable": "檢測到更新可用",
|
||||
"updateOngoing": "正在更新,請稍後……",
|
||||
"custom": "自定義",
|
||||
@ -504,5 +510,6 @@
|
||||
"postCategoryKnowledge": "知識",
|
||||
"postCategoryLiterature": "文學",
|
||||
"postCategoryFunny": "搞笑",
|
||||
"postCategoryUncategorized": "未分類"
|
||||
"postCategoryUncategorized": "未分類",
|
||||
"waitingForUpload": "等待上傳"
|
||||
}
|
||||
|
@ -15,16 +15,9 @@ import 'package:surface/types/post.dart';
|
||||
import 'package:surface/widgets/dialog.dart';
|
||||
import 'package:surface/widgets/universal_image.dart';
|
||||
|
||||
enum PostWriteMediaType {
|
||||
image,
|
||||
video,
|
||||
audio,
|
||||
file,
|
||||
}
|
||||
|
||||
class PostWriteMedia {
|
||||
late String name;
|
||||
late PostWriteMediaType type;
|
||||
late SnMediaType type;
|
||||
final SnAttachment? attachment;
|
||||
final XFile? file;
|
||||
final Uint8List? raw;
|
||||
@ -36,16 +29,16 @@ class PostWriteMedia {
|
||||
|
||||
switch (attachment?.mimetype.split('/').firstOrNull) {
|
||||
case 'image':
|
||||
type = PostWriteMediaType.image;
|
||||
type = SnMediaType.image;
|
||||
break;
|
||||
case 'video':
|
||||
type = PostWriteMediaType.video;
|
||||
type = SnMediaType.video;
|
||||
break;
|
||||
case 'audio':
|
||||
type = PostWriteMediaType.audio;
|
||||
type = SnMediaType.audio;
|
||||
break;
|
||||
default:
|
||||
type = PostWriteMediaType.file;
|
||||
type = SnMediaType.file;
|
||||
}
|
||||
}
|
||||
|
||||
@ -57,16 +50,16 @@ class PostWriteMedia {
|
||||
|
||||
switch (mimetype?.split('/').firstOrNull) {
|
||||
case 'image':
|
||||
type = PostWriteMediaType.image;
|
||||
type = SnMediaType.image;
|
||||
break;
|
||||
case 'video':
|
||||
type = PostWriteMediaType.video;
|
||||
type = SnMediaType.video;
|
||||
break;
|
||||
case 'audio':
|
||||
type = PostWriteMediaType.audio;
|
||||
type = SnMediaType.audio;
|
||||
break;
|
||||
default:
|
||||
type = PostWriteMediaType.file;
|
||||
type = SnMediaType.file;
|
||||
}
|
||||
}
|
||||
|
||||
@ -244,7 +237,7 @@ class PostWriteController extends ChangeNotifier {
|
||||
media.name,
|
||||
'interactive',
|
||||
null,
|
||||
mimetype: media.raw != null && media.type == PostWriteMediaType.image ? 'image/png' : null,
|
||||
mimetype: media.raw != null && media.type == SnMediaType.image ? 'image/png' : null,
|
||||
);
|
||||
|
||||
final item = await attach.chunkedUploadParts(
|
||||
@ -301,7 +294,7 @@ class PostWriteController extends ChangeNotifier {
|
||||
media.name,
|
||||
'interactive',
|
||||
null,
|
||||
mimetype: media.raw != null && media.type == PostWriteMediaType.image ? 'image/png' : null,
|
||||
mimetype: media.raw != null && media.type == SnMediaType.image ? 'image/png' : null,
|
||||
);
|
||||
|
||||
final item = await attach.chunkedUploadParts(
|
||||
|
@ -217,15 +217,17 @@ class SnAttachmentProvider {
|
||||
}
|
||||
|
||||
Future<SnAttachment> updateOne(
|
||||
int id,
|
||||
String alt, {
|
||||
required Map<String, dynamic> metadata,
|
||||
bool isMature = false,
|
||||
int id, {
|
||||
String? alt,
|
||||
String? thumbnail,
|
||||
Map<String, dynamic>? metadata,
|
||||
bool? isIndexable,
|
||||
}) async {
|
||||
final resp = await _sn.client.put('/cgi/uc/attachments/$id', data: {
|
||||
'alt': alt,
|
||||
'thumbnail': thumbnail,
|
||||
'metadata': metadata,
|
||||
'is_mature': isMature,
|
||||
'is_indexable': isIndexable,
|
||||
});
|
||||
return SnAttachment.fromJson(resp.data);
|
||||
}
|
||||
|
@ -129,7 +129,7 @@ class SpecialDayProvider {
|
||||
}
|
||||
|
||||
double getSpecialDayProgress(DateTime last, DateTime next) {
|
||||
final totalDuration = next.difference(last).inSeconds.toDouble();
|
||||
final totalDuration = next.add(-const Duration(days: 1)).difference(last).inSeconds.toDouble();
|
||||
final elapsedDuration = DateTime.now().difference(last).inSeconds.toDouble();
|
||||
return (elapsedDuration / totalDuration).clamp(0.0, 1.0);
|
||||
}
|
||||
|
@ -5,7 +5,6 @@ import 'package:provider/provider.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:surface/providers/config.dart';
|
||||
import 'package:surface/providers/sn_network.dart';
|
||||
import 'package:surface/providers/widget.dart';
|
||||
import 'package:surface/types/account.dart';
|
||||
|
||||
class UserProvider extends ChangeNotifier {
|
||||
@ -13,12 +12,10 @@ class UserProvider extends ChangeNotifier {
|
||||
SnAccount? user;
|
||||
|
||||
late final SnNetworkProvider _sn;
|
||||
late final HomeWidgetProvider _home;
|
||||
late final ConfigProvider _config;
|
||||
|
||||
UserProvider(BuildContext context) {
|
||||
_sn = context.read<SnNetworkProvider>();
|
||||
_home = context.read<HomeWidgetProvider>();
|
||||
_config = context.read<ConfigProvider>();
|
||||
}
|
||||
|
||||
|
@ -22,15 +22,14 @@ import 'package:surface/widgets/universal_image.dart';
|
||||
|
||||
class AccountPublisherEditScreen extends StatefulWidget {
|
||||
final String name;
|
||||
|
||||
const AccountPublisherEditScreen({super.key, required this.name});
|
||||
|
||||
@override
|
||||
State<AccountPublisherEditScreen> createState() =>
|
||||
_AccountPublisherEditScreenState();
|
||||
State<AccountPublisherEditScreen> createState() => _AccountPublisherEditScreenState();
|
||||
}
|
||||
|
||||
class _AccountPublisherEditScreenState
|
||||
extends State<AccountPublisherEditScreen> {
|
||||
class _AccountPublisherEditScreenState extends State<AccountPublisherEditScreen> {
|
||||
bool _isBusy = false;
|
||||
|
||||
SnPublisher? _publisher;
|
||||
@ -54,7 +53,7 @@ class _AccountPublisherEditScreenState
|
||||
_publisher = SnPublisher.fromJson(resp.data);
|
||||
_syncWidget();
|
||||
} catch (err) {
|
||||
context.showErrorDialog(err);
|
||||
if (mounted) context.showErrorDialog(err);
|
||||
} finally {
|
||||
setState(() => _isBusy = false);
|
||||
}
|
||||
@ -75,9 +74,9 @@ class _AccountPublisherEditScreenState
|
||||
'name': _nameController.text,
|
||||
'description': _descriptionController.text,
|
||||
});
|
||||
Navigator.pop(context, true);
|
||||
if (mounted) Navigator.pop(context, true);
|
||||
} catch (err) {
|
||||
context.showErrorDialog(err);
|
||||
if(mounted) context.showErrorDialog(err);
|
||||
} finally {
|
||||
setState(() => _isBusy = false);
|
||||
}
|
||||
@ -108,11 +107,9 @@ class _AccountPublisherEditScreenState
|
||||
if (image == null) return;
|
||||
if (!mounted) return;
|
||||
|
||||
final ImageProvider imageProvider =
|
||||
kIsWeb ? NetworkImage(image.path) : FileImage(File(image.path));
|
||||
final aspectRatios = place == 'banner'
|
||||
? [CropAspectRatio(width: 16, height: 7)]
|
||||
: [CropAspectRatio(width: 1, height: 1)];
|
||||
final ImageProvider imageProvider = kIsWeb ? NetworkImage(image.path) : FileImage(File(image.path));
|
||||
final aspectRatios =
|
||||
place == 'banner' ? [CropAspectRatio(width: 16, height: 7)] : [CropAspectRatio(width: 1, height: 1)];
|
||||
final result = (!kIsWeb && (Platform.isIOS || Platform.isMacOS))
|
||||
? await showCupertinoImageCropper(
|
||||
// ignore: use_build_context_synchronously
|
||||
@ -134,10 +131,7 @@ class _AccountPublisherEditScreenState
|
||||
|
||||
setState(() => _isBusy = true);
|
||||
|
||||
final rawBytes =
|
||||
(await result.uiImage.toByteData(format: ImageByteFormat.png))!
|
||||
.buffer
|
||||
.asUint8List();
|
||||
final rawBytes = (await result.uiImage.toByteData(format: ImageByteFormat.png))!.buffer.asUint8List();
|
||||
|
||||
try {
|
||||
final attachment = await attach.directUploadOne(
|
||||
@ -199,9 +193,7 @@ class _AccountPublisherEditScreenState
|
||||
child: AspectRatio(
|
||||
aspectRatio: 16 / 9,
|
||||
child: Container(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.surfaceContainerHigh,
|
||||
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||
child: _banner != null
|
||||
? AutoResizeUniversalImage(
|
||||
sn.getAttachmentUrl(_banner!),
|
||||
@ -240,8 +232,7 @@ class _AccountPublisherEditScreenState
|
||||
labelText: 'fieldUsername'.tr(),
|
||||
helperText: 'fieldUsernameCannotEditHint'.tr(),
|
||||
),
|
||||
onTapOutside: (_) =>
|
||||
FocusManager.instance.primaryFocus?.unfocus(),
|
||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||
),
|
||||
const Gap(4),
|
||||
TextField(
|
||||
@ -249,8 +240,7 @@ class _AccountPublisherEditScreenState
|
||||
decoration: InputDecoration(
|
||||
labelText: 'fieldNickname'.tr(),
|
||||
),
|
||||
onTapOutside: (_) =>
|
||||
FocusManager.instance.primaryFocus?.unfocus(),
|
||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||
),
|
||||
const Gap(4),
|
||||
TextField(
|
||||
@ -260,8 +250,7 @@ class _AccountPublisherEditScreenState
|
||||
decoration: InputDecoration(
|
||||
labelText: 'fieldDescription'.tr(),
|
||||
),
|
||||
onTapOutside: (_) =>
|
||||
FocusManager.instance.primaryFocus?.unfocus(),
|
||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||
),
|
||||
const Gap(12),
|
||||
Row(
|
||||
|
@ -201,7 +201,7 @@ class _PublisherNewPersonalState extends State<_PublisherNewPersonal> {
|
||||
}
|
||||
|
||||
class _PublisherNewOrganization extends StatefulWidget {
|
||||
const _PublisherNewOrganization({super.key});
|
||||
const _PublisherNewOrganization();
|
||||
|
||||
@override
|
||||
State<_PublisherNewOrganization> createState() =>
|
||||
|
@ -105,6 +105,7 @@ class _LoginCheckScreen extends StatefulWidget {
|
||||
final SnAuthFactor? factor;
|
||||
final Function(SnAuthTicket?) onTicket;
|
||||
final Function onNext;
|
||||
|
||||
const _LoginCheckScreen({
|
||||
super.key,
|
||||
required this.ticket,
|
||||
@ -204,9 +205,7 @@ class _LoginCheckScreenState extends State<_LoginCheckScreen> {
|
||||
controller: _passwordController,
|
||||
obscureText: true,
|
||||
autofillHints: [
|
||||
(_factorLabelMap[widget.factor!.type]?.$3 ?? true)
|
||||
? AutofillHints.password
|
||||
: AutofillHints.oneTimeCode
|
||||
(_factorLabelMap[widget.factor!.type]?.$3 ?? true) ? AutofillHints.password : AutofillHints.oneTimeCode
|
||||
],
|
||||
decoration: InputDecoration(
|
||||
isDense: true,
|
||||
@ -243,6 +242,7 @@ class _LoginPickerScreen extends StatefulWidget {
|
||||
final Function(SnAuthTicket?) onTicket;
|
||||
final Function(SnAuthFactor) onPickFactor;
|
||||
final Function onNext;
|
||||
|
||||
const _LoginPickerScreen({
|
||||
super.key,
|
||||
required this.ticket,
|
||||
@ -260,8 +260,7 @@ class _LoginPickerScreenState extends State<_LoginPickerScreen> {
|
||||
bool _isBusy = false;
|
||||
int? _factorPicked;
|
||||
|
||||
Color get _unFocusColor =>
|
||||
Theme.of(context).colorScheme.onSurface.withAlpha((255 * 0.75).round());
|
||||
Color get _unFocusColor => Theme.of(context).colorScheme.onSurface.withAlpha((255 * 0.75).round());
|
||||
|
||||
void _performGetFactorCode() async {
|
||||
if (_factorPicked == null) return;
|
||||
@ -373,6 +372,7 @@ class _LoginLookupScreen extends StatefulWidget {
|
||||
final Function(SnAuthTicket?) onTicket;
|
||||
final Function(List<SnAuthFactor>?) onFactor;
|
||||
final Function onNext;
|
||||
|
||||
const _LoginLookupScreen({
|
||||
super.key,
|
||||
required this.ticket,
|
||||
@ -401,14 +401,13 @@ class _LoginLookupScreenState extends State<_LoginLookupScreen> {
|
||||
|
||||
try {
|
||||
final sn = context.read<SnNetworkProvider>();
|
||||
final lookupResp =
|
||||
await sn.client.get('/cgi/id/users/lookup?probe=$username');
|
||||
final lookupResp = await sn.client.get('/cgi/id/users/lookup?probe=$username');
|
||||
await sn.client.post('/cgi/id/users/me/password-reset', data: {
|
||||
'user_id': lookupResp.data['id'],
|
||||
});
|
||||
context.showModalDialog('done'.tr(), 'signinResetPasswordSent'.tr());
|
||||
if (mounted) context.showModalDialog('done'.tr(), 'signinResetPasswordSent'.tr());
|
||||
} catch (err) {
|
||||
context.showErrorDialog(err);
|
||||
if (mounted) context.showErrorDialog(err);
|
||||
} finally {
|
||||
setState(() => _isBusy = false);
|
||||
}
|
||||
@ -431,8 +430,7 @@ class _LoginLookupScreenState extends State<_LoginLookupScreen> {
|
||||
widget.onTicket(result.ticket);
|
||||
|
||||
// Pull factors
|
||||
final factorResp =
|
||||
await sn.client.get('/cgi/id/auth/factors', queryParameters: {
|
||||
final factorResp = await sn.client.get('/cgi/id/auth/factors', queryParameters: {
|
||||
'ticketId': result.ticket!.id.toString(),
|
||||
});
|
||||
widget.onFactor(
|
||||
@ -443,7 +441,7 @@ class _LoginLookupScreenState extends State<_LoginLookupScreen> {
|
||||
|
||||
widget.onNext();
|
||||
} catch (err) {
|
||||
context.showErrorDialog(err);
|
||||
if(mounted) context.showErrorDialog(err);
|
||||
return;
|
||||
} finally {
|
||||
setState(() => _isBusy = false);
|
||||
@ -526,10 +524,7 @@ class _LoginLookupScreenState extends State<_LoginLookupScreen> {
|
||||
'termAcceptNextWithAgree'.tr(),
|
||||
textAlign: TextAlign.end,
|
||||
style: Theme.of(context).textTheme.bodySmall!.copyWith(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSurface
|
||||
.withAlpha((255 * 0.75).round()),
|
||||
color: Theme.of(context).colorScheme.onSurface.withAlpha((255 * 0.75).round()),
|
||||
),
|
||||
),
|
||||
Material(
|
||||
|
@ -443,7 +443,7 @@ class _ChannelProfileDetailDialogState
|
||||
|
||||
class _ChannelMemberListWidget extends StatefulWidget {
|
||||
final SnChannel channel;
|
||||
const _ChannelMemberListWidget({super.key, required this.channel});
|
||||
const _ChannelMemberListWidget({required this.channel});
|
||||
|
||||
@override
|
||||
State<_ChannelMemberListWidget> createState() =>
|
||||
@ -580,7 +580,7 @@ class _ChannelMemberListWidgetState extends State<_ChannelMemberListWidget> {
|
||||
|
||||
class _NewChannelMemberWidget extends StatefulWidget {
|
||||
final SnChannel channel;
|
||||
const _NewChannelMemberWidget({super.key, required this.channel});
|
||||
const _NewChannelMemberWidget({required this.channel});
|
||||
|
||||
@override
|
||||
State<_NewChannelMemberWidget> createState() =>
|
||||
|
@ -97,7 +97,6 @@ class _ChatRoomScreenState extends State<ChatRoomScreen> {
|
||||
}
|
||||
} catch (err) {
|
||||
if (!mounted) return;
|
||||
print((err as DioException).response?.data);
|
||||
context.showErrorDialog(err);
|
||||
} finally {
|
||||
setState(() => _isCalling = false);
|
||||
|
@ -289,7 +289,7 @@ class _FriendScreenState extends State<FriendScreen> {
|
||||
}
|
||||
|
||||
class _NewFriendWidget extends StatefulWidget {
|
||||
const _NewFriendWidget({super.key});
|
||||
const _NewFriendWidget();
|
||||
|
||||
@override
|
||||
State<_NewFriendWidget> createState() => _NewFriendWidgetState();
|
||||
@ -365,7 +365,7 @@ class _NewFriendWidgetState extends State<_NewFriendWidget> {
|
||||
|
||||
class _FriendshipListWidget extends StatefulWidget {
|
||||
final List<SnRelationship> relations;
|
||||
const _FriendshipListWidget({super.key, required this.relations});
|
||||
const _FriendshipListWidget({required this.relations});
|
||||
|
||||
@override
|
||||
State<_FriendshipListWidget> createState() => _FriendshipListWidgetState();
|
||||
|
@ -12,6 +12,7 @@ import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:relative_time/relative_time.dart';
|
||||
import 'package:slide_countdown/slide_countdown.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:surface/providers/config.dart';
|
||||
@ -109,7 +110,7 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
class _HomeDashUpdateWidget extends StatelessWidget {
|
||||
final EdgeInsets? padding;
|
||||
|
||||
const _HomeDashUpdateWidget({super.key, this.padding});
|
||||
const _HomeDashUpdateWidget({this.padding});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -153,7 +154,7 @@ class _HomeDashUpdateWidget extends StatelessWidget {
|
||||
}
|
||||
|
||||
class _HomeDashSpecialDayWidget extends StatelessWidget {
|
||||
const _HomeDashSpecialDayWidget({super.key});
|
||||
const _HomeDashSpecialDayWidget();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -188,7 +189,7 @@ class _HomeDashSpecialDayWidget extends StatelessWidget {
|
||||
var (name, date) = nextOne;
|
||||
date = date.add(Duration(days: 1));
|
||||
final progress = dayz.getSpecialDayProgress(lastOne.$2, date);
|
||||
final diff = nextOne.$2.add(-const Duration(days: 1)).difference(lastOne.$2);
|
||||
final diff = nextOne.$2.difference(DateTime.now());
|
||||
return Card(
|
||||
child: ListTile(
|
||||
leading: Text(kSpecialDaysSymbol[name] ?? '🎉').fontSize(24),
|
||||
@ -196,8 +197,15 @@ class _HomeDashSpecialDayWidget extends StatelessWidget {
|
||||
subtitle: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Text('${diff.inDays}d · ${(progress * 100).toStringAsFixed(2)}%'),
|
||||
const Gap(8),
|
||||
SlideCountdown(
|
||||
duration: diff,
|
||||
style: GoogleFonts.robotoMono(fontSize: 13),
|
||||
separatorStyle: GoogleFonts.robotoMono(fontSize: 13),
|
||||
separatorType: SeparatorType.symbol,
|
||||
decoration: BoxDecoration(),
|
||||
padding: EdgeInsets.zero,
|
||||
),
|
||||
const Gap(12),
|
||||
Expanded(
|
||||
child: LinearProgressIndicator(
|
||||
value: progress,
|
||||
@ -215,7 +223,7 @@ class _HomeDashSpecialDayWidget extends StatelessWidget {
|
||||
}
|
||||
|
||||
class _HomeDashCheckInWidget extends StatefulWidget {
|
||||
const _HomeDashCheckInWidget({super.key});
|
||||
const _HomeDashCheckInWidget();
|
||||
|
||||
@override
|
||||
State<_HomeDashCheckInWidget> createState() => _HomeDashCheckInWidgetState();
|
||||
@ -433,7 +441,7 @@ class _HomeDashCheckInWidgetState extends State<_HomeDashCheckInWidget> {
|
||||
}
|
||||
|
||||
class _HomeDashNotificationWidget extends StatefulWidget {
|
||||
const _HomeDashNotificationWidget({super.key});
|
||||
const _HomeDashNotificationWidget();
|
||||
|
||||
@override
|
||||
State<_HomeDashNotificationWidget> createState() => _HomeDashNotificationWidgetState();
|
||||
@ -504,7 +512,7 @@ class _HomeDashNotificationWidgetState extends State<_HomeDashNotificationWidget
|
||||
}
|
||||
|
||||
class _HomeDashRecommendationPostWidget extends StatefulWidget {
|
||||
const _HomeDashRecommendationPostWidget({super.key});
|
||||
const _HomeDashRecommendationPostWidget();
|
||||
|
||||
@override
|
||||
State<_HomeDashRecommendationPostWidget> createState() => _HomeDashRecommendationPostWidgetState();
|
||||
|
@ -1,16 +1,11 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:dropdown_button2/dropdown_button2.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:pasteboard/pasteboard.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
import 'package:surface/controllers/post_write_controller.dart';
|
||||
import 'package:surface/providers/config.dart';
|
||||
|
@ -580,7 +580,6 @@ class _PublisherPostList extends StatelessWidget {
|
||||
final void Function() onDeleted;
|
||||
|
||||
const _PublisherPostList({
|
||||
super.key,
|
||||
required this.isBusy,
|
||||
required this.postCount,
|
||||
required this.posts,
|
||||
|
@ -119,7 +119,7 @@ class _RealmDetailHomeWidget extends StatelessWidget {
|
||||
final SnRealm? realm;
|
||||
final List<SnPublisher>? publishers;
|
||||
|
||||
const _RealmDetailHomeWidget({super.key, required this.realm, this.publishers});
|
||||
const _RealmDetailHomeWidget({required this.realm, this.publishers});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -175,7 +175,7 @@ class _RealmDetailHomeWidget extends StatelessWidget {
|
||||
class _RealmMemberListWidget extends StatefulWidget {
|
||||
final SnRealm? realm;
|
||||
|
||||
const _RealmMemberListWidget({super.key, this.realm});
|
||||
const _RealmMemberListWidget({this.realm});
|
||||
|
||||
@override
|
||||
State<_RealmMemberListWidget> createState() => _RealmMemberListWidgetState();
|
||||
@ -304,7 +304,7 @@ class _RealmMemberListWidgetState extends State<_RealmMemberListWidget> {
|
||||
class _NewRealmMemberWidget extends StatefulWidget {
|
||||
final SnRealm realm;
|
||||
|
||||
const _NewRealmMemberWidget({super.key, required this.realm});
|
||||
const _NewRealmMemberWidget({required this.realm});
|
||||
|
||||
@override
|
||||
State<_NewRealmMemberWidget> createState() => _NewRealmMemberWidgetState();
|
||||
@ -384,7 +384,7 @@ class _RealmSettingsWidget extends StatefulWidget {
|
||||
final SnRealm? realm;
|
||||
final Function() onUpdate;
|
||||
|
||||
const _RealmSettingsWidget({super.key, required this.realm, required this.onUpdate});
|
||||
const _RealmSettingsWidget({required this.realm, required this.onUpdate});
|
||||
|
||||
@override
|
||||
State<_RealmSettingsWidget> createState() => _RealmSettingsWidgetState();
|
||||
|
@ -33,7 +33,6 @@ Future<ThemeData> createAppTheme(
|
||||
brightness: brightness,
|
||||
);
|
||||
|
||||
final hasBackground = prefs.getBool(kAppBackgroundStoreKey) ?? false;
|
||||
final hasAppBarBlurry = prefs.getBool(kAppbarTransparentStoreKey) ?? false;
|
||||
|
||||
return ThemeData(
|
||||
|
@ -1,10 +1,20 @@
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'attachment.freezed.dart';
|
||||
|
||||
part 'attachment.g.dart';
|
||||
|
||||
enum SnMediaType {
|
||||
image,
|
||||
video,
|
||||
audio,
|
||||
file,
|
||||
}
|
||||
|
||||
@freezed
|
||||
class SnAttachment with _$SnAttachment {
|
||||
const SnAttachment._();
|
||||
|
||||
const factory SnAttachment({
|
||||
required int id,
|
||||
required DateTime createdAt,
|
||||
@ -19,9 +29,10 @@ class SnAttachment with _$SnAttachment {
|
||||
required String hash,
|
||||
required int destination,
|
||||
required int refCount,
|
||||
@Default(0) int contentRating,
|
||||
@Default(0) int qualityRating,
|
||||
required dynamic fileChunks,
|
||||
required dynamic cleanedAt,
|
||||
required bool isMature,
|
||||
required bool isAnalyzed,
|
||||
required bool isUploaded,
|
||||
required bool isSelfRef,
|
||||
@ -30,11 +41,24 @@ class SnAttachment with _$SnAttachment {
|
||||
required SnAttachmentPool? pool,
|
||||
required int poolId,
|
||||
required int accountId,
|
||||
@Default({}) Map<String, dynamic> usermeta,
|
||||
@Default({}) Map<String, dynamic> metadata,
|
||||
String? thumbnail,
|
||||
}) = _SnAttachment;
|
||||
|
||||
factory SnAttachment.fromJson(Map<String, Object?> json) =>
|
||||
_$SnAttachmentFromJson(json);
|
||||
factory SnAttachment.fromJson(Map<String, Object?> json) => _$SnAttachmentFromJson(json);
|
||||
|
||||
Map<String, dynamic> get data => {
|
||||
...metadata,
|
||||
...usermeta,
|
||||
};
|
||||
|
||||
SnMediaType get mediaType => switch (mimetype.split('/').firstOrNull) {
|
||||
'image' => SnMediaType.image,
|
||||
'video' => SnMediaType.video,
|
||||
'audio' => SnMediaType.audio,
|
||||
_ => SnMediaType.file,
|
||||
};
|
||||
}
|
||||
|
||||
@freezed
|
||||
@ -51,6 +75,5 @@ class SnAttachmentPool with _$SnAttachmentPool {
|
||||
required int? accountId,
|
||||
}) = _SnAttachmentPool;
|
||||
|
||||
factory SnAttachmentPool.fromJson(Map<String, Object?> json) =>
|
||||
_$SnAttachmentPoolFromJson(json);
|
||||
factory SnAttachmentPool.fromJson(Map<String, Object?> json) => _$SnAttachmentPoolFromJson(json);
|
||||
}
|
||||
|
@ -33,9 +33,10 @@ mixin _$SnAttachment {
|
||||
String get hash => throw _privateConstructorUsedError;
|
||||
int get destination => throw _privateConstructorUsedError;
|
||||
int get refCount => throw _privateConstructorUsedError;
|
||||
int get contentRating => throw _privateConstructorUsedError;
|
||||
int get qualityRating => throw _privateConstructorUsedError;
|
||||
dynamic get fileChunks => throw _privateConstructorUsedError;
|
||||
dynamic get cleanedAt => throw _privateConstructorUsedError;
|
||||
bool get isMature => throw _privateConstructorUsedError;
|
||||
bool get isAnalyzed => throw _privateConstructorUsedError;
|
||||
bool get isUploaded => throw _privateConstructorUsedError;
|
||||
bool get isSelfRef => throw _privateConstructorUsedError;
|
||||
@ -44,7 +45,9 @@ mixin _$SnAttachment {
|
||||
SnAttachmentPool? get pool => throw _privateConstructorUsedError;
|
||||
int get poolId => throw _privateConstructorUsedError;
|
||||
int get accountId => throw _privateConstructorUsedError;
|
||||
Map<String, dynamic> get usermeta => throw _privateConstructorUsedError;
|
||||
Map<String, dynamic> get metadata => throw _privateConstructorUsedError;
|
||||
String? get thumbnail => throw _privateConstructorUsedError;
|
||||
|
||||
/// Serializes this SnAttachment to a JSON map.
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@ -76,9 +79,10 @@ abstract class $SnAttachmentCopyWith<$Res> {
|
||||
String hash,
|
||||
int destination,
|
||||
int refCount,
|
||||
int contentRating,
|
||||
int qualityRating,
|
||||
dynamic fileChunks,
|
||||
dynamic cleanedAt,
|
||||
bool isMature,
|
||||
bool isAnalyzed,
|
||||
bool isUploaded,
|
||||
bool isSelfRef,
|
||||
@ -87,7 +91,9 @@ abstract class $SnAttachmentCopyWith<$Res> {
|
||||
SnAttachmentPool? pool,
|
||||
int poolId,
|
||||
int accountId,
|
||||
Map<String, dynamic> metadata});
|
||||
Map<String, dynamic> usermeta,
|
||||
Map<String, dynamic> metadata,
|
||||
String? thumbnail});
|
||||
|
||||
$SnAttachmentPoolCopyWith<$Res>? get pool;
|
||||
}
|
||||
@ -120,9 +126,10 @@ class _$SnAttachmentCopyWithImpl<$Res, $Val extends SnAttachment>
|
||||
Object? hash = null,
|
||||
Object? destination = null,
|
||||
Object? refCount = null,
|
||||
Object? contentRating = null,
|
||||
Object? qualityRating = null,
|
||||
Object? fileChunks = freezed,
|
||||
Object? cleanedAt = freezed,
|
||||
Object? isMature = null,
|
||||
Object? isAnalyzed = null,
|
||||
Object? isUploaded = null,
|
||||
Object? isSelfRef = null,
|
||||
@ -131,7 +138,9 @@ class _$SnAttachmentCopyWithImpl<$Res, $Val extends SnAttachment>
|
||||
Object? pool = freezed,
|
||||
Object? poolId = null,
|
||||
Object? accountId = null,
|
||||
Object? usermeta = null,
|
||||
Object? metadata = null,
|
||||
Object? thumbnail = freezed,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
id: null == id
|
||||
@ -186,6 +195,14 @@ class _$SnAttachmentCopyWithImpl<$Res, $Val extends SnAttachment>
|
||||
? _value.refCount
|
||||
: refCount // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
contentRating: null == contentRating
|
||||
? _value.contentRating
|
||||
: contentRating // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
qualityRating: null == qualityRating
|
||||
? _value.qualityRating
|
||||
: qualityRating // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
fileChunks: freezed == fileChunks
|
||||
? _value.fileChunks
|
||||
: fileChunks // ignore: cast_nullable_to_non_nullable
|
||||
@ -194,10 +211,6 @@ class _$SnAttachmentCopyWithImpl<$Res, $Val extends SnAttachment>
|
||||
? _value.cleanedAt
|
||||
: cleanedAt // ignore: cast_nullable_to_non_nullable
|
||||
as dynamic,
|
||||
isMature: null == isMature
|
||||
? _value.isMature
|
||||
: isMature // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
isAnalyzed: null == isAnalyzed
|
||||
? _value.isAnalyzed
|
||||
: isAnalyzed // ignore: cast_nullable_to_non_nullable
|
||||
@ -230,10 +243,18 @@ class _$SnAttachmentCopyWithImpl<$Res, $Val extends SnAttachment>
|
||||
? _value.accountId
|
||||
: accountId // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
usermeta: null == usermeta
|
||||
? _value.usermeta
|
||||
: usermeta // ignore: cast_nullable_to_non_nullable
|
||||
as Map<String, dynamic>,
|
||||
metadata: null == metadata
|
||||
? _value.metadata
|
||||
: metadata // ignore: cast_nullable_to_non_nullable
|
||||
as Map<String, dynamic>,
|
||||
thumbnail: freezed == thumbnail
|
||||
? _value.thumbnail
|
||||
: thumbnail // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
) as $Val);
|
||||
}
|
||||
|
||||
@ -274,9 +295,10 @@ abstract class _$$SnAttachmentImplCopyWith<$Res>
|
||||
String hash,
|
||||
int destination,
|
||||
int refCount,
|
||||
int contentRating,
|
||||
int qualityRating,
|
||||
dynamic fileChunks,
|
||||
dynamic cleanedAt,
|
||||
bool isMature,
|
||||
bool isAnalyzed,
|
||||
bool isUploaded,
|
||||
bool isSelfRef,
|
||||
@ -285,7 +307,9 @@ abstract class _$$SnAttachmentImplCopyWith<$Res>
|
||||
SnAttachmentPool? pool,
|
||||
int poolId,
|
||||
int accountId,
|
||||
Map<String, dynamic> metadata});
|
||||
Map<String, dynamic> usermeta,
|
||||
Map<String, dynamic> metadata,
|
||||
String? thumbnail});
|
||||
|
||||
@override
|
||||
$SnAttachmentPoolCopyWith<$Res>? get pool;
|
||||
@ -317,9 +341,10 @@ class __$$SnAttachmentImplCopyWithImpl<$Res>
|
||||
Object? hash = null,
|
||||
Object? destination = null,
|
||||
Object? refCount = null,
|
||||
Object? contentRating = null,
|
||||
Object? qualityRating = null,
|
||||
Object? fileChunks = freezed,
|
||||
Object? cleanedAt = freezed,
|
||||
Object? isMature = null,
|
||||
Object? isAnalyzed = null,
|
||||
Object? isUploaded = null,
|
||||
Object? isSelfRef = null,
|
||||
@ -328,7 +353,9 @@ class __$$SnAttachmentImplCopyWithImpl<$Res>
|
||||
Object? pool = freezed,
|
||||
Object? poolId = null,
|
||||
Object? accountId = null,
|
||||
Object? usermeta = null,
|
||||
Object? metadata = null,
|
||||
Object? thumbnail = freezed,
|
||||
}) {
|
||||
return _then(_$SnAttachmentImpl(
|
||||
id: null == id
|
||||
@ -383,6 +410,14 @@ class __$$SnAttachmentImplCopyWithImpl<$Res>
|
||||
? _value.refCount
|
||||
: refCount // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
contentRating: null == contentRating
|
||||
? _value.contentRating
|
||||
: contentRating // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
qualityRating: null == qualityRating
|
||||
? _value.qualityRating
|
||||
: qualityRating // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
fileChunks: freezed == fileChunks
|
||||
? _value.fileChunks
|
||||
: fileChunks // ignore: cast_nullable_to_non_nullable
|
||||
@ -391,10 +426,6 @@ class __$$SnAttachmentImplCopyWithImpl<$Res>
|
||||
? _value.cleanedAt
|
||||
: cleanedAt // ignore: cast_nullable_to_non_nullable
|
||||
as dynamic,
|
||||
isMature: null == isMature
|
||||
? _value.isMature
|
||||
: isMature // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
isAnalyzed: null == isAnalyzed
|
||||
? _value.isAnalyzed
|
||||
: isAnalyzed // ignore: cast_nullable_to_non_nullable
|
||||
@ -427,17 +458,25 @@ class __$$SnAttachmentImplCopyWithImpl<$Res>
|
||||
? _value.accountId
|
||||
: accountId // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
usermeta: null == usermeta
|
||||
? _value._usermeta
|
||||
: usermeta // ignore: cast_nullable_to_non_nullable
|
||||
as Map<String, dynamic>,
|
||||
metadata: null == metadata
|
||||
? _value._metadata
|
||||
: metadata // ignore: cast_nullable_to_non_nullable
|
||||
as Map<String, dynamic>,
|
||||
thumbnail: freezed == thumbnail
|
||||
? _value.thumbnail
|
||||
: thumbnail // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _$SnAttachmentImpl implements _SnAttachment {
|
||||
class _$SnAttachmentImpl extends _SnAttachment {
|
||||
const _$SnAttachmentImpl(
|
||||
{required this.id,
|
||||
required this.createdAt,
|
||||
@ -452,9 +491,10 @@ class _$SnAttachmentImpl implements _SnAttachment {
|
||||
required this.hash,
|
||||
required this.destination,
|
||||
required this.refCount,
|
||||
this.contentRating = 0,
|
||||
this.qualityRating = 0,
|
||||
required this.fileChunks,
|
||||
required this.cleanedAt,
|
||||
required this.isMature,
|
||||
required this.isAnalyzed,
|
||||
required this.isUploaded,
|
||||
required this.isSelfRef,
|
||||
@ -463,8 +503,12 @@ class _$SnAttachmentImpl implements _SnAttachment {
|
||||
required this.pool,
|
||||
required this.poolId,
|
||||
required this.accountId,
|
||||
final Map<String, dynamic> metadata = const {}})
|
||||
: _metadata = metadata;
|
||||
final Map<String, dynamic> usermeta = const {},
|
||||
final Map<String, dynamic> metadata = const {},
|
||||
this.thumbnail})
|
||||
: _usermeta = usermeta,
|
||||
_metadata = metadata,
|
||||
super._();
|
||||
|
||||
factory _$SnAttachmentImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$SnAttachmentImplFromJson(json);
|
||||
@ -496,12 +540,16 @@ class _$SnAttachmentImpl implements _SnAttachment {
|
||||
@override
|
||||
final int refCount;
|
||||
@override
|
||||
@JsonKey()
|
||||
final int contentRating;
|
||||
@override
|
||||
@JsonKey()
|
||||
final int qualityRating;
|
||||
@override
|
||||
final dynamic fileChunks;
|
||||
@override
|
||||
final dynamic cleanedAt;
|
||||
@override
|
||||
final bool isMature;
|
||||
@override
|
||||
final bool isAnalyzed;
|
||||
@override
|
||||
final bool isUploaded;
|
||||
@ -517,6 +565,15 @@ class _$SnAttachmentImpl implements _SnAttachment {
|
||||
final int poolId;
|
||||
@override
|
||||
final int accountId;
|
||||
final Map<String, dynamic> _usermeta;
|
||||
@override
|
||||
@JsonKey()
|
||||
Map<String, dynamic> get usermeta {
|
||||
if (_usermeta is EqualUnmodifiableMapView) return _usermeta;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableMapView(_usermeta);
|
||||
}
|
||||
|
||||
final Map<String, dynamic> _metadata;
|
||||
@override
|
||||
@JsonKey()
|
||||
@ -526,9 +583,12 @@ class _$SnAttachmentImpl implements _SnAttachment {
|
||||
return EqualUnmodifiableMapView(_metadata);
|
||||
}
|
||||
|
||||
@override
|
||||
final String? thumbnail;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SnAttachment(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, rid: $rid, uuid: $uuid, size: $size, name: $name, alt: $alt, mimetype: $mimetype, hash: $hash, destination: $destination, refCount: $refCount, fileChunks: $fileChunks, cleanedAt: $cleanedAt, isMature: $isMature, isAnalyzed: $isAnalyzed, isUploaded: $isUploaded, isSelfRef: $isSelfRef, ref: $ref, refId: $refId, pool: $pool, poolId: $poolId, accountId: $accountId, metadata: $metadata)';
|
||||
return 'SnAttachment(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, rid: $rid, uuid: $uuid, size: $size, name: $name, alt: $alt, mimetype: $mimetype, hash: $hash, destination: $destination, refCount: $refCount, contentRating: $contentRating, qualityRating: $qualityRating, fileChunks: $fileChunks, cleanedAt: $cleanedAt, isAnalyzed: $isAnalyzed, isUploaded: $isUploaded, isSelfRef: $isSelfRef, ref: $ref, refId: $refId, pool: $pool, poolId: $poolId, accountId: $accountId, usermeta: $usermeta, metadata: $metadata, thumbnail: $thumbnail)';
|
||||
}
|
||||
|
||||
@override
|
||||
@ -554,11 +614,13 @@ class _$SnAttachmentImpl implements _SnAttachment {
|
||||
other.destination == destination) &&
|
||||
(identical(other.refCount, refCount) ||
|
||||
other.refCount == refCount) &&
|
||||
(identical(other.contentRating, contentRating) ||
|
||||
other.contentRating == contentRating) &&
|
||||
(identical(other.qualityRating, qualityRating) ||
|
||||
other.qualityRating == qualityRating) &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other.fileChunks, fileChunks) &&
|
||||
const DeepCollectionEquality().equals(other.cleanedAt, cleanedAt) &&
|
||||
(identical(other.isMature, isMature) ||
|
||||
other.isMature == isMature) &&
|
||||
(identical(other.isAnalyzed, isAnalyzed) ||
|
||||
other.isAnalyzed == isAnalyzed) &&
|
||||
(identical(other.isUploaded, isUploaded) ||
|
||||
@ -571,7 +633,10 @@ class _$SnAttachmentImpl implements _SnAttachment {
|
||||
(identical(other.poolId, poolId) || other.poolId == poolId) &&
|
||||
(identical(other.accountId, accountId) ||
|
||||
other.accountId == accountId) &&
|
||||
const DeepCollectionEquality().equals(other._metadata, _metadata));
|
||||
const DeepCollectionEquality().equals(other._usermeta, _usermeta) &&
|
||||
const DeepCollectionEquality().equals(other._metadata, _metadata) &&
|
||||
(identical(other.thumbnail, thumbnail) ||
|
||||
other.thumbnail == thumbnail));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@ -591,9 +656,10 @@ class _$SnAttachmentImpl implements _SnAttachment {
|
||||
hash,
|
||||
destination,
|
||||
refCount,
|
||||
contentRating,
|
||||
qualityRating,
|
||||
const DeepCollectionEquality().hash(fileChunks),
|
||||
const DeepCollectionEquality().hash(cleanedAt),
|
||||
isMature,
|
||||
isAnalyzed,
|
||||
isUploaded,
|
||||
isSelfRef,
|
||||
@ -602,7 +668,9 @@ class _$SnAttachmentImpl implements _SnAttachment {
|
||||
pool,
|
||||
poolId,
|
||||
accountId,
|
||||
const DeepCollectionEquality().hash(_metadata)
|
||||
const DeepCollectionEquality().hash(_usermeta),
|
||||
const DeepCollectionEquality().hash(_metadata),
|
||||
thumbnail
|
||||
]);
|
||||
|
||||
/// Create a copy of SnAttachment
|
||||
@ -621,7 +689,7 @@ class _$SnAttachmentImpl implements _SnAttachment {
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _SnAttachment implements SnAttachment {
|
||||
abstract class _SnAttachment extends SnAttachment {
|
||||
const factory _SnAttachment(
|
||||
{required final int id,
|
||||
required final DateTime createdAt,
|
||||
@ -636,9 +704,10 @@ abstract class _SnAttachment implements SnAttachment {
|
||||
required final String hash,
|
||||
required final int destination,
|
||||
required final int refCount,
|
||||
final int contentRating,
|
||||
final int qualityRating,
|
||||
required final dynamic fileChunks,
|
||||
required final dynamic cleanedAt,
|
||||
required final bool isMature,
|
||||
required final bool isAnalyzed,
|
||||
required final bool isUploaded,
|
||||
required final bool isSelfRef,
|
||||
@ -647,7 +716,10 @@ abstract class _SnAttachment implements SnAttachment {
|
||||
required final SnAttachmentPool? pool,
|
||||
required final int poolId,
|
||||
required final int accountId,
|
||||
final Map<String, dynamic> metadata}) = _$SnAttachmentImpl;
|
||||
final Map<String, dynamic> usermeta,
|
||||
final Map<String, dynamic> metadata,
|
||||
final String? thumbnail}) = _$SnAttachmentImpl;
|
||||
const _SnAttachment._() : super._();
|
||||
|
||||
factory _SnAttachment.fromJson(Map<String, dynamic> json) =
|
||||
_$SnAttachmentImpl.fromJson;
|
||||
@ -679,12 +751,14 @@ abstract class _SnAttachment implements SnAttachment {
|
||||
@override
|
||||
int get refCount;
|
||||
@override
|
||||
int get contentRating;
|
||||
@override
|
||||
int get qualityRating;
|
||||
@override
|
||||
dynamic get fileChunks;
|
||||
@override
|
||||
dynamic get cleanedAt;
|
||||
@override
|
||||
bool get isMature;
|
||||
@override
|
||||
bool get isAnalyzed;
|
||||
@override
|
||||
bool get isUploaded;
|
||||
@ -701,7 +775,11 @@ abstract class _SnAttachment implements SnAttachment {
|
||||
@override
|
||||
int get accountId;
|
||||
@override
|
||||
Map<String, dynamic> get usermeta;
|
||||
@override
|
||||
Map<String, dynamic> get metadata;
|
||||
@override
|
||||
String? get thumbnail;
|
||||
|
||||
/// Create a copy of SnAttachment
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
|
@ -21,9 +21,10 @@ _$SnAttachmentImpl _$$SnAttachmentImplFromJson(Map<String, dynamic> json) =>
|
||||
hash: json['hash'] as String,
|
||||
destination: (json['destination'] as num).toInt(),
|
||||
refCount: (json['ref_count'] as num).toInt(),
|
||||
contentRating: (json['content_rating'] as num?)?.toInt() ?? 0,
|
||||
qualityRating: (json['quality_rating'] as num?)?.toInt() ?? 0,
|
||||
fileChunks: json['file_chunks'],
|
||||
cleanedAt: json['cleaned_at'],
|
||||
isMature: json['is_mature'] as bool,
|
||||
isAnalyzed: json['is_analyzed'] as bool,
|
||||
isUploaded: json['is_uploaded'] as bool,
|
||||
isSelfRef: json['is_self_ref'] as bool,
|
||||
@ -34,7 +35,9 @@ _$SnAttachmentImpl _$$SnAttachmentImplFromJson(Map<String, dynamic> json) =>
|
||||
: SnAttachmentPool.fromJson(json['pool'] as Map<String, dynamic>),
|
||||
poolId: (json['pool_id'] as num).toInt(),
|
||||
accountId: (json['account_id'] as num).toInt(),
|
||||
usermeta: json['usermeta'] as Map<String, dynamic>? ?? const {},
|
||||
metadata: json['metadata'] as Map<String, dynamic>? ?? const {},
|
||||
thumbnail: json['thumbnail'] as String?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$SnAttachmentImplToJson(_$SnAttachmentImpl instance) =>
|
||||
@ -52,9 +55,10 @@ Map<String, dynamic> _$$SnAttachmentImplToJson(_$SnAttachmentImpl instance) =>
|
||||
'hash': instance.hash,
|
||||
'destination': instance.destination,
|
||||
'ref_count': instance.refCount,
|
||||
'content_rating': instance.contentRating,
|
||||
'quality_rating': instance.qualityRating,
|
||||
'file_chunks': instance.fileChunks,
|
||||
'cleaned_at': instance.cleanedAt,
|
||||
'is_mature': instance.isMature,
|
||||
'is_analyzed': instance.isAnalyzed,
|
||||
'is_uploaded': instance.isUploaded,
|
||||
'is_self_ref': instance.isSelfRef,
|
||||
@ -63,7 +67,9 @@ Map<String, dynamic> _$$SnAttachmentImplToJson(_$SnAttachmentImpl instance) =>
|
||||
'pool': instance.pool?.toJson(),
|
||||
'pool_id': instance.poolId,
|
||||
'account_id': instance.accountId,
|
||||
'usermeta': instance.usermeta,
|
||||
'metadata': instance.metadata,
|
||||
'thumbnail': instance.thumbnail,
|
||||
};
|
||||
|
||||
_$SnAttachmentPoolImpl _$$SnAttachmentPoolImplFromJson(
|
||||
|
@ -32,7 +32,7 @@ class _AccountSelectState extends State<AccountSelect> {
|
||||
final List<SnAccount> _pendingUsers = List.empty(growable: true);
|
||||
final List<SnAccount> _selectedUsers = List.empty(growable: true);
|
||||
|
||||
int _accountId = 0;
|
||||
final int _accountId = 0;
|
||||
|
||||
Future<void> _revertSelectedUsers() async {
|
||||
if (widget.initialSelection?.isEmpty ?? true) return;
|
||||
|
@ -99,10 +99,10 @@ class _AttachmentInputDialogState extends State<AttachmentInputDialog> {
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: Text('dialogDismiss').tr(),
|
||||
onPressed: _isBusy ? null : () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: Text('dialogDismiss').tr(),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: _isBusy ? null : () => _finishUp(),
|
||||
|
@ -18,6 +18,7 @@ import 'package:uuid/uuid.dart';
|
||||
class AttachmentItem extends StatelessWidget {
|
||||
final SnAttachment? data;
|
||||
final String? heroTag;
|
||||
|
||||
const AttachmentItem({
|
||||
super.key,
|
||||
required this.data,
|
||||
@ -60,9 +61,14 @@ class AttachmentItem extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (data!.isMature) {
|
||||
return _AttachmentItemSensitiveBlur(
|
||||
child: _buildContent(context),
|
||||
if (data!.contentRating > 0) {
|
||||
return LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
return _AttachmentItemSensitiveBlur(
|
||||
isCompact: constraints.maxHeight < 360,
|
||||
child: _buildContent(context),
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@ -72,15 +78,15 @@ class AttachmentItem extends StatelessWidget {
|
||||
|
||||
class _AttachmentItemSensitiveBlur extends StatefulWidget {
|
||||
final Widget child;
|
||||
const _AttachmentItemSensitiveBlur({super.key, required this.child});
|
||||
final bool isCompact;
|
||||
|
||||
const _AttachmentItemSensitiveBlur({required this.child, this.isCompact = false});
|
||||
|
||||
@override
|
||||
State<_AttachmentItemSensitiveBlur> createState() =>
|
||||
_AttachmentItemSensitiveBlurState();
|
||||
State<_AttachmentItemSensitiveBlur> createState() => _AttachmentItemSensitiveBlurState();
|
||||
}
|
||||
|
||||
class _AttachmentItemSensitiveBlurState
|
||||
extends State<_AttachmentItemSensitiveBlur> {
|
||||
class _AttachmentItemSensitiveBlurState extends State<_AttachmentItemSensitiveBlur> {
|
||||
bool _doesShow = false;
|
||||
|
||||
@override
|
||||
@ -104,24 +110,21 @@ class _AttachmentItemSensitiveBlurState
|
||||
color: Colors.white,
|
||||
size: 32,
|
||||
),
|
||||
const Gap(8),
|
||||
Text('sensitiveContent', textAlign: TextAlign.center)
|
||||
.tr()
|
||||
.fontSize(20)
|
||||
.textColor(Colors.white)
|
||||
.bold(),
|
||||
Text(
|
||||
'sensitiveContentDescription',
|
||||
textAlign: TextAlign.center,
|
||||
)
|
||||
.tr()
|
||||
.fontSize(14)
|
||||
.textColor(Colors.white.withOpacity(0.8)),
|
||||
const Gap(16),
|
||||
InkWell(
|
||||
child: Text('sensitiveContentReveal')
|
||||
if (!widget.isCompact) const Gap(8),
|
||||
if (!widget.isCompact)
|
||||
Text('sensitiveContent', textAlign: TextAlign.center)
|
||||
.tr()
|
||||
.textColor(Colors.white),
|
||||
.fontSize(20)
|
||||
.textColor(Colors.white)
|
||||
.bold(),
|
||||
if (!widget.isCompact)
|
||||
Text(
|
||||
'sensitiveContentDescription',
|
||||
textAlign: TextAlign.center,
|
||||
).tr().fontSize(14).textColor(Colors.white.withOpacity(0.8)),
|
||||
if (!widget.isCompact) const Gap(16),
|
||||
InkWell(
|
||||
child: Text('sensitiveContentReveal').tr().textColor(Colors.white),
|
||||
onTap: () {
|
||||
setState(() => _doesShow = !_doesShow);
|
||||
},
|
||||
@ -131,9 +134,7 @@ class _AttachmentItemSensitiveBlurState
|
||||
).center(),
|
||||
),
|
||||
),
|
||||
)
|
||||
.opacity(_doesShow ? 0 : 1, animate: true)
|
||||
.animate(const Duration(milliseconds: 300), Curves.easeInOut),
|
||||
).opacity(_doesShow ? 0 : 1, animate: true).animate(const Duration(milliseconds: 300), Curves.easeInOut),
|
||||
if (_doesShow)
|
||||
Positioned(
|
||||
top: 0,
|
||||
@ -163,19 +164,17 @@ class _AttachmentItemSensitiveBlurState
|
||||
class _AttachmentItemContentVideo extends StatefulWidget {
|
||||
final SnAttachment data;
|
||||
final bool isAutoload;
|
||||
|
||||
const _AttachmentItemContentVideo({
|
||||
super.key,
|
||||
required this.data,
|
||||
this.isAutoload = false,
|
||||
});
|
||||
|
||||
@override
|
||||
State<_AttachmentItemContentVideo> createState() =>
|
||||
_AttachmentItemContentVideoState();
|
||||
State<_AttachmentItemContentVideo> createState() => _AttachmentItemContentVideoState();
|
||||
}
|
||||
|
||||
class _AttachmentItemContentVideoState
|
||||
extends State<_AttachmentItemContentVideo> {
|
||||
class _AttachmentItemContentVideoState extends State<_AttachmentItemContentVideo> {
|
||||
bool _showContent = false;
|
||||
|
||||
Player? _videoPlayer;
|
||||
@ -207,7 +206,7 @@ class _AttachmentItemContentVideoState
|
||||
),
|
||||
];
|
||||
|
||||
final ratio = widget.data.metadata['ratio'] ?? 16 / 9;
|
||||
final ratio = widget.data.data['ratio'] ?? 16 / 9;
|
||||
|
||||
final sn = context.read<SnNetworkProvider>();
|
||||
|
||||
@ -216,9 +215,9 @@ class _AttachmentItemContentVideoState
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child: Stack(
|
||||
children: [
|
||||
if (widget.data.metadata['thumbnail'] != null)
|
||||
if (widget.data.thumbnail != null)
|
||||
AutoResizeUniversalImage(
|
||||
sn.getAttachmentUrl(widget.data.metadata['thumbnail']),
|
||||
sn.getAttachmentUrl(widget.data.thumbnail!),
|
||||
fit: BoxFit.cover,
|
||||
)
|
||||
else
|
||||
@ -266,10 +265,7 @@ class _AttachmentItemContentVideoState
|
||||
),
|
||||
Text(
|
||||
Duration(
|
||||
milliseconds:
|
||||
(widget.data.metadata['duration'] ?? 0)
|
||||
.toInt() *
|
||||
1000,
|
||||
milliseconds: (widget.data.data['duration'] ?? 0).toInt() * 1000,
|
||||
).toString(),
|
||||
style: GoogleFonts.robotoMono(
|
||||
fontSize: 12,
|
||||
@ -317,19 +313,17 @@ class _AttachmentItemContentVideoState
|
||||
class _AttachmentItemContentAudio extends StatefulWidget {
|
||||
final SnAttachment data;
|
||||
final bool isAutoload;
|
||||
|
||||
const _AttachmentItemContentAudio({
|
||||
super.key,
|
||||
required this.data,
|
||||
this.isAutoload = false,
|
||||
});
|
||||
|
||||
@override
|
||||
State<_AttachmentItemContentAudio> createState() =>
|
||||
_AttachmentItemContentAudioState();
|
||||
State<_AttachmentItemContentAudio> createState() => _AttachmentItemContentAudioState();
|
||||
}
|
||||
|
||||
class _AttachmentItemContentAudioState
|
||||
extends State<_AttachmentItemContentAudio> {
|
||||
class _AttachmentItemContentAudioState extends State<_AttachmentItemContentAudio> {
|
||||
bool _showContent = false;
|
||||
|
||||
double? _draggingValue;
|
||||
@ -378,11 +372,11 @@ class _AttachmentItemContentAudioState
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child: Stack(
|
||||
children: [
|
||||
if (widget.data.metadata['thumbnail'] != null)
|
||||
if (widget.data.thumbnail != null)
|
||||
AspectRatio(
|
||||
aspectRatio: 16 / 9,
|
||||
child: AutoResizeUniversalImage(
|
||||
sn.getAttachmentUrl(widget.data.metadata['thumbnail']),
|
||||
sn.getAttachmentUrl(widget.data.data['thumbnail']),
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
)
|
||||
@ -463,11 +457,11 @@ class _AttachmentItemContentAudioState
|
||||
|
||||
return Stack(
|
||||
children: [
|
||||
if (widget.data.metadata['thumbnail'] != null)
|
||||
if (widget.data.data['thumbnail'] != null)
|
||||
AspectRatio(
|
||||
aspectRatio: 16 / 9,
|
||||
child: AutoResizeUniversalImage(
|
||||
sn.getAttachmentUrl(widget.data.metadata['thumbnail']),
|
||||
sn.getAttachmentUrl(widget.data.data['thumbnail']),
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
@ -499,12 +493,8 @@ class _AttachmentItemContentAudioState
|
||||
overlayShape: SliderComponentShape.noOverlay,
|
||||
),
|
||||
child: Slider(
|
||||
secondaryTrackValue: _bufferedPosition
|
||||
.inMilliseconds
|
||||
.abs()
|
||||
.toDouble(),
|
||||
value: _draggingValue?.abs() ??
|
||||
_position.inMilliseconds.toDouble().abs(),
|
||||
secondaryTrackValue: _bufferedPosition.inMilliseconds.abs().toDouble(),
|
||||
value: _draggingValue?.abs() ?? _position.inMilliseconds.toDouble().abs(),
|
||||
min: 0,
|
||||
max: math
|
||||
.max(
|
||||
@ -544,9 +534,7 @@ class _AttachmentItemContentAudioState
|
||||
),
|
||||
const Gap(16),
|
||||
IconButton.filled(
|
||||
icon: _isPlaying
|
||||
? const Icon(Symbols.pause)
|
||||
: const Icon(Symbols.play_arrow),
|
||||
icon: _isPlaying ? const Icon(Symbols.pause) : const Icon(Symbols.play_arrow),
|
||||
onPressed: () {
|
||||
_audioPlayer!.playOrPause();
|
||||
},
|
||||
|
@ -58,7 +58,7 @@ class _AttachmentListState extends State<AttachmentList> {
|
||||
|
||||
if (widget.data.isEmpty) return const SizedBox.shrink();
|
||||
if (widget.data.length == 1) {
|
||||
final singleAspectRatio = widget.data[0]?.metadata['ratio']?.toDouble() ??
|
||||
final singleAspectRatio = widget.data[0]?.data['ratio']?.toDouble() ??
|
||||
switch (widget.data[0]?.mimetype.split('/').firstOrNull) {
|
||||
'audio' => 16 / 9,
|
||||
'video' => 16 / 9,
|
||||
@ -114,6 +114,7 @@ class _AttachmentListState extends State<AttachmentList> {
|
||||
},
|
||||
),
|
||||
onTap: () {
|
||||
if (widget.data.firstOrNull?.mediaType != SnMediaType.image) return;
|
||||
context.pushTransparentRoute(
|
||||
AttachmentZoomView(
|
||||
data: widget.data.where((ele) => ele != null).cast(),
|
||||
@ -136,7 +137,7 @@ class _AttachmentListState extends State<AttachmentList> {
|
||||
children: widget.data
|
||||
.mapIndexed(
|
||||
(idx, ele) => AspectRatio(
|
||||
aspectRatio: (ele?.metadata['ratio'] ?? 1).toDouble(),
|
||||
aspectRatio: (ele?.data['ratio'] ?? 1).toDouble(),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: backgroundColor,
|
||||
@ -161,7 +162,7 @@ class _AttachmentListState extends State<AttachmentList> {
|
||||
}
|
||||
|
||||
return AspectRatio(
|
||||
aspectRatio: (widget.data.firstOrNull?.metadata['ratio'] ?? 1).toDouble(),
|
||||
aspectRatio: (widget.data.firstOrNull?.data['ratio'] ?? 1).toDouble(),
|
||||
child: Container(
|
||||
constraints: BoxConstraints(maxHeight: constraints.maxHeight),
|
||||
child: ScrollConfiguration(
|
||||
@ -173,12 +174,14 @@ class _AttachmentListState extends State<AttachmentList> {
|
||||
return Container(
|
||||
constraints: constraints,
|
||||
child: AspectRatio(
|
||||
aspectRatio: (widget.data[idx]?.metadata['ratio'] ?? 1).toDouble(),
|
||||
aspectRatio: (widget.data[idx]?.data['ratio'] ?? 1).toDouble(),
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
if (widget.data[idx]?.mediaType != SnMediaType.image) return;
|
||||
context.pushTransparentRoute(
|
||||
AttachmentZoomView(
|
||||
data: widget.data.where((ele) => ele != null).cast(),
|
||||
data:
|
||||
widget.data.where((ele) => ele != null && ele.mediaType == SnMediaType.image).cast(),
|
||||
initialIndex: idx,
|
||||
heroTags: heroTags,
|
||||
),
|
||||
|
@ -174,7 +174,7 @@ class ChatMessage extends StatelessWidget {
|
||||
class _ChatMessageText extends StatelessWidget {
|
||||
final SnChatMessage data;
|
||||
|
||||
const _ChatMessageText({super.key, required this.data});
|
||||
const _ChatMessageText({required this.data});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -214,7 +214,7 @@ class _ChatMessageText extends StatelessWidget {
|
||||
class _ChatMessageSystemNotify extends StatelessWidget {
|
||||
final SnChatMessage data;
|
||||
|
||||
const _ChatMessageSystemNotify({super.key, required this.data});
|
||||
const _ChatMessageSystemNotify({required this.data});
|
||||
|
||||
String _formatDuration(Duration duration) {
|
||||
String negativeSign = duration.isNegative ? '-' : '';
|
||||
|
@ -1,18 +1,14 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:pasteboard/pasteboard.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
import 'package:surface/controllers/chat_message_controller.dart';
|
||||
import 'package:surface/controllers/post_write_controller.dart';
|
||||
import 'package:surface/providers/sn_attachment.dart';
|
||||
import 'package:surface/providers/user_directory.dart';
|
||||
import 'package:surface/types/attachment.dart';
|
||||
import 'package:surface/types/chat.dart';
|
||||
import 'package:surface/widgets/dialog.dart';
|
||||
import 'package:surface/widgets/markdown_content.dart';
|
||||
@ -80,7 +76,7 @@ class ChatMessageInputState extends State<ChatMessageInput> {
|
||||
media.name,
|
||||
'messaging',
|
||||
null,
|
||||
mimetype: media.raw != null && media.type == PostWriteMediaType.image ? 'image/png' : null,
|
||||
mimetype: media.raw != null && media.type == SnMediaType.image ? 'image/png' : null,
|
||||
);
|
||||
|
||||
final item = await attach.chunkedUploadParts(
|
||||
|
@ -60,7 +60,6 @@ class _LinkPreviewEntry extends StatelessWidget {
|
||||
final SnLinkMeta meta;
|
||||
|
||||
const _LinkPreviewEntry({
|
||||
super.key,
|
||||
required this.meta,
|
||||
});
|
||||
|
||||
|
@ -549,7 +549,6 @@ class _PostHeadline extends StatelessWidget {
|
||||
final bool isEnlarge;
|
||||
|
||||
const _PostHeadline({
|
||||
super.key,
|
||||
required this.data,
|
||||
this.isEnlarge = false,
|
||||
});
|
||||
@ -894,7 +893,6 @@ class _PostQuoteContent extends StatelessWidget {
|
||||
final bool isFlatted;
|
||||
|
||||
const _PostQuoteContent({
|
||||
super.key,
|
||||
this.isRelativeDate = true,
|
||||
this.isFlatted = false,
|
||||
required this.child,
|
||||
@ -962,7 +960,7 @@ class _PostQuoteContent extends StatelessWidget {
|
||||
class _PostTagsList extends StatelessWidget {
|
||||
final SnPost data;
|
||||
|
||||
const _PostTagsList({super.key, required this.data});
|
||||
const _PostTagsList({required this.data});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -1035,7 +1033,7 @@ class _PostTagsList extends StatelessWidget {
|
||||
class _PostVisibilityHint extends StatelessWidget {
|
||||
final SnPost data;
|
||||
|
||||
const _PostVisibilityHint({super.key, required this.data});
|
||||
const _PostVisibilityHint({required this.data});
|
||||
|
||||
static const List<IconData> kVisibilityIcons = [
|
||||
Symbols.public,
|
||||
@ -1060,7 +1058,7 @@ class _PostVisibilityHint extends StatelessWidget {
|
||||
class _PostTruncatedHint extends StatelessWidget {
|
||||
final SnPost data;
|
||||
|
||||
const _PostTruncatedHint({super.key, required this.data});
|
||||
const _PostTruncatedHint({required this.data});
|
||||
|
||||
static const int kHumanReadSpeed = 238;
|
||||
|
||||
@ -1102,7 +1100,7 @@ class _PostTruncatedHint extends StatelessWidget {
|
||||
class _PostAbuseReportDialog extends StatefulWidget {
|
||||
final SnPost data;
|
||||
|
||||
const _PostAbuseReportDialog({super.key, required this.data});
|
||||
const _PostAbuseReportDialog({required this.data});
|
||||
|
||||
@override
|
||||
State<_PostAbuseReportDialog> createState() => _PostAbuseReportDialogState();
|
||||
|
@ -96,10 +96,7 @@ class PostMediaPendingList extends StatelessWidget {
|
||||
if (!context.mounted) return;
|
||||
|
||||
final attach = context.read<SnAttachmentProvider>();
|
||||
final newAttach = await attach.updateOne(attachments[idx].attachment!.id, thumbnail.alt, metadata: {
|
||||
...attachments[idx].attachment!.metadata,
|
||||
'thumbnail': thumbnail.rid,
|
||||
});
|
||||
final newAttach = await attach.updateOne(attachments[idx].attachment!.id, thumbnail: thumbnail.rid);
|
||||
|
||||
onUpdate!(idx, PostWriteMedia(newAttach));
|
||||
}
|
||||
@ -124,7 +121,7 @@ class PostMediaPendingList extends StatelessWidget {
|
||||
ContextMenu _createContextMenu(BuildContext context, int idx, PostWriteMedia media) {
|
||||
return ContextMenu(
|
||||
entries: [
|
||||
if (media.attachment != null && media.type == PostWriteMediaType.video)
|
||||
if (media.attachment != null && media.type == SnMediaType.video)
|
||||
MenuItem(
|
||||
label: 'attachmentSetThumbnail'.tr(),
|
||||
icon: Symbols.image,
|
||||
@ -140,7 +137,7 @@ class PostMediaPendingList extends StatelessWidget {
|
||||
onUpload!(idx);
|
||||
}),
|
||||
if (media.attachment != null &&
|
||||
media.type == PostWriteMediaType.image &&
|
||||
media.type == SnMediaType.image &&
|
||||
onPostSetThumbnail != null &&
|
||||
idx != -1)
|
||||
MenuItem(
|
||||
@ -150,7 +147,7 @@ class PostMediaPendingList extends StatelessWidget {
|
||||
onPostSetThumbnail!(idx);
|
||||
},
|
||||
)
|
||||
else if (media.attachment != null && media.type == PostWriteMediaType.image && onPostSetThumbnail != null)
|
||||
else if (media.attachment != null && media.type == SnMediaType.image && onPostSetThumbnail != null)
|
||||
MenuItem(
|
||||
label: 'attachmentUnsetAsPostThumbnail'.tr(),
|
||||
icon: Symbols.cancel,
|
||||
@ -166,7 +163,7 @@ class PostMediaPendingList extends StatelessWidget {
|
||||
onInsertLink!(idx);
|
||||
},
|
||||
),
|
||||
if (media.type == PostWriteMediaType.image && media.attachment != null)
|
||||
if (media.type == SnMediaType.image && media.attachment != null)
|
||||
MenuItem(
|
||||
label: 'preview'.tr(),
|
||||
icon: Symbols.preview,
|
||||
@ -177,7 +174,7 @@ class PostMediaPendingList extends StatelessWidget {
|
||||
);
|
||||
},
|
||||
),
|
||||
if (media.type == PostWriteMediaType.image && media.attachment == null)
|
||||
if (media.type == SnMediaType.image && media.attachment == null)
|
||||
MenuItem(
|
||||
label: 'crop'.tr(),
|
||||
icon: Symbols.crop,
|
||||
@ -219,10 +216,6 @@ class PostMediaPendingList extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final devicePixelRatio = MediaQuery.of(context).devicePixelRatio;
|
||||
|
||||
final sn = context.read<SnNetworkProvider>();
|
||||
|
||||
return Container(
|
||||
constraints: const BoxConstraints(maxHeight: 120),
|
||||
child: Row(
|
||||
@ -285,7 +278,7 @@ class _PostMediaPendingItem extends StatelessWidget {
|
||||
child: AspectRatio(
|
||||
aspectRatio: 1,
|
||||
child: switch (media.type) {
|
||||
PostWriteMediaType.image => Container(
|
||||
SnMediaType.image => Container(
|
||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||
child: LayoutBuilder(builder: (context, constraints) {
|
||||
return Image(
|
||||
@ -298,7 +291,7 @@ class _PostMediaPendingItem extends StatelessWidget {
|
||||
);
|
||||
}),
|
||||
),
|
||||
PostWriteMediaType.video => Container(
|
||||
SnMediaType.video => Container(
|
||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||
child: media.attachment?.metadata['thumbnail'] != null
|
||||
? AutoResizeUniversalImage(sn.getAttachmentUrl(media.attachment?.metadata['thumbnail']))
|
||||
@ -345,7 +338,7 @@ class AddPostMediaButton extends StatelessWidget {
|
||||
PostWriteMedia.fromBytes(
|
||||
imageBytes,
|
||||
'attachmentPastedImage'.tr(),
|
||||
PostWriteMediaType.image,
|
||||
SnMediaType.image,
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
26
pubspec.lock
26
pubspec.lock
@ -490,10 +490,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: file_picker
|
||||
sha256: c2376a6aae82358a9f9ccdd7d1f4006d08faa39a2767cce01031d9f593a8bd3b
|
||||
sha256: c904b4ab56d53385563c7c39d8e9fa9af086f91495dfc48717ad84a42c3cf204
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.1.6"
|
||||
version: "8.1.7"
|
||||
file_saver:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -763,13 +763,13 @@ packages:
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_webrtc:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_webrtc
|
||||
sha256: "0e138a0a3bf6830c29c8439b17be0e222d0de27fa72f24e6aee4d34de72f22ef"
|
||||
sha256: "3efe9828f19a07d29a51a726759ad0c70a840d231548a1c7d0332075a94db1df"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.12.5"
|
||||
version: "0.12.5+hotfix.1"
|
||||
freezed:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
@ -1354,6 +1354,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.0"
|
||||
pausable_timer:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pausable_timer
|
||||
sha256: "6ef1a95441ec3439de6fb63f39a011b67e693198e7dae14e20675c3c00e86074"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.0+3"
|
||||
permission_handler:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -1711,6 +1719,14 @@ packages:
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
slide_countdown:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: slide_countdown
|
||||
sha256: "363914f96389502467d4dc9c0f26e88f93df3d8e37de2d5ff05b16d981fe973d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.2"
|
||||
sliver_tools:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -113,6 +113,8 @@ dependencies:
|
||||
version: ^3.0.2
|
||||
flutter_colorpicker: ^1.1.0
|
||||
fl_chart: ^0.70.0
|
||||
flutter_webrtc: ^0.12.5+hotfix.1
|
||||
slide_countdown: ^2.0.2
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
Loading…
x
Reference in New Issue
Block a user