Compare commits
12 Commits
Author | SHA1 | Date | |
---|---|---|---|
03275b46ca | |||
cf3b482fef | |||
aa4c04d4ef | |||
73b82f65e4 | |||
9471fe40fe | |||
0d1e18735e | |||
8bb62b5992 | |||
1e8a6dea5b | |||
5c2804cc4d | |||
0dbb8f132a | |||
3395f3dbd0 | |||
d258ba776e |
@ -15,11 +15,11 @@ body:json {
|
|||||||
"client_id": "{{third_client_id}}",
|
"client_id": "{{third_client_id}}",
|
||||||
"client_secret":"{{third_client_tk}}",
|
"client_secret":"{{third_client_tk}}",
|
||||||
"type": "general",
|
"type": "general",
|
||||||
"subject": "Merry Christmas!",
|
"subject": "新年快乐!",
|
||||||
"subtitle": "一条来自 Solar Network 团队的信息",
|
"subtitle": "一条来自 Solar Network 团队的信息",
|
||||||
"content": "今天是 12 月 25 日 (UTC+8),小羊祝您圣诞快乐 🎄",
|
"content": "今天是农历正月初一,小羊祝您新年快乐 🎉",
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"image": "6EqsYQwmFRCkbmhR"
|
"image": "D2EDbcrsTugs3xk5"
|
||||||
},
|
},
|
||||||
"priority": 10
|
"priority": 10
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
vars {
|
vars {
|
||||||
endpoint: https://api.sn.solsynth.dev
|
endpoint: https://api.sn.solsynth.dev
|
||||||
third_client_id: alphabot
|
third_client_id: alphabot
|
||||||
|
atk: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL3NvbHN5bnRoLmRldiIsInN1YiI6IjEiLCJhdWQiOlsic29sYXItbmV0d29yayJdLCJleHAiOjE3MzgwODE2OTYsIm5iZiI6MTczODA3ODY5NiwiaWF0IjoxNzM4MDc4Njk2LCJqdGkiOiI1Yzg2MTYxZC00MTZjLTQwNDYtOWFlNS04YWZhNGIyZjdlMTkiLCJzZWQiOiIyMTciLCJ0eXAiOiJhY2Nlc3MifQ.LdLZ6FLb8IqPI__U8sT6VyxK5S_ZgwVGGL-tk01tK9C19wnbFFThPDgga1pJu_VVVpGVxzvMyd-3tBotzLMB5LjhYbtPOJakN2oug1HAgJ8zfc1clORlpHlUVisaiQtl3ZkWtzxni8etZDhJpqHU65IGQG01TO6PZGLgxKkMel4gGeeKhHpg9Q4Eewr3Pbl_wJkHVChJ9IJPmgioc_CACE10nEHZgwptCbndUz3AbIDOG9qW-7ZoprtKoRwAcuUXANK277VvdBRhwQjqKBREqVeMXP_Rv37jDPYhWpfS6HtKoHElZOTJG-69S2Zc3HyHlPJAPlzLAjGPoxScky79Gg
|
||||||
}
|
}
|
||||||
vars:secret [
|
vars:secret [
|
||||||
atk,
|
|
||||||
third_client_tk
|
third_client_tk
|
||||||
]
|
]
|
||||||
|
@ -17,6 +17,9 @@
|
|||||||
"screenAccountProfileEdit": "Edit Profile",
|
"screenAccountProfileEdit": "Edit Profile",
|
||||||
"screenAbuseReport": "Abuse Reports",
|
"screenAbuseReport": "Abuse Reports",
|
||||||
"screenSettings": "Settings",
|
"screenSettings": "Settings",
|
||||||
|
"screenAccountSettings": "Account Settings",
|
||||||
|
"screenFactorSettings": "Auth Factors",
|
||||||
|
"screenAccountWallet": "Wallet",
|
||||||
"screenNews": "News",
|
"screenNews": "News",
|
||||||
"screenAlbum": "Album",
|
"screenAlbum": "Album",
|
||||||
"screenChat": "Chat",
|
"screenChat": "Chat",
|
||||||
@ -104,8 +107,18 @@
|
|||||||
},
|
},
|
||||||
"loginEnterPassword": "Enter the code",
|
"loginEnterPassword": "Enter the code",
|
||||||
"loginSuccess": "Logged in as {}",
|
"loginSuccess": "Logged in as {}",
|
||||||
|
"authFactorDelete": "Delete Auth Factor",
|
||||||
|
"authFactorDeleteDescription": "Are you sure you want delete auth factor {}?",
|
||||||
"authFactorPassword": "Password",
|
"authFactorPassword": "Password",
|
||||||
|
"authFactorPasswordDescription": "The password you set when you registered.",
|
||||||
"authFactorEmail": "Email verification code",
|
"authFactorEmail": "Email verification code",
|
||||||
|
"authFactorEmailDescription": "An one-time code sent to the email address you set when you registered.",
|
||||||
|
"authFactorTOTP": "Time-based OTP",
|
||||||
|
"authFactorTOTPDescription": "A one-time code generated by a TOTP authenticator such as Google Authenticator or Authy.",
|
||||||
|
"authFactorInAppNotify": "In-app notification",
|
||||||
|
"authFactorInAppNotifyDescription": "A one-time code sent via in-app notification.",
|
||||||
|
"authFactorAdd": "Add a factor",
|
||||||
|
"authFactorAddSubtitle": "Provide another way to login your account.",
|
||||||
"accountIntroTitle": "Hello there!",
|
"accountIntroTitle": "Hello there!",
|
||||||
"accountIntroSubtitle": "Pick an option below to get started.",
|
"accountIntroSubtitle": "Pick an option below to get started.",
|
||||||
"accountLogout": "Logout",
|
"accountLogout": "Logout",
|
||||||
@ -114,8 +127,14 @@
|
|||||||
"accountLogoutConfirm": "You will need to re-enter your account password, even if you have already done so. This is required to login again.",
|
"accountLogoutConfirm": "You will need to re-enter your account password, even if you have already done so. This is required to login again.",
|
||||||
"accountPublishers": "Your publishers",
|
"accountPublishers": "Your publishers",
|
||||||
"accountPublishersSubtitle": "Manage your publish identities.",
|
"accountPublishersSubtitle": "Manage your publish identities.",
|
||||||
|
"accountSettings": "Account Settings",
|
||||||
|
"accountSettingsSubtitle": "Manage your account and make it yours.",
|
||||||
"accountProfileEdit": "Edit your profile",
|
"accountProfileEdit": "Edit your profile",
|
||||||
"accountProfileEditSubtitle": "Make your Solarpass account more looks like you.",
|
"accountProfileEditSubtitle": "Make your Solarpass account more looks like you.",
|
||||||
|
"accountWallet": "Wallet",
|
||||||
|
"accountWalletSubtitle": "View your balance and transactions.",
|
||||||
|
"factorSettings": "Auth Factors",
|
||||||
|
"factorSettingsSubtitle": "Manage your authentication factors.",
|
||||||
"accountProfileEditApplied": "Profile modification applied.",
|
"accountProfileEditApplied": "Profile modification applied.",
|
||||||
"publishersNew": "New Publisher",
|
"publishersNew": "New Publisher",
|
||||||
"publisherNewSubtitle": "Create a new publisher identity.",
|
"publisherNewSubtitle": "Create a new publisher identity.",
|
||||||
@ -180,6 +199,9 @@
|
|||||||
"other": "{} comments"
|
"other": "{} comments"
|
||||||
},
|
},
|
||||||
"settingsAppearance": "Appearance",
|
"settingsAppearance": "Appearance",
|
||||||
|
"settingsDisplayLanguage": "Display Language",
|
||||||
|
"settingsDisplayLanguageDescription": "Set the application language.",
|
||||||
|
"settingsDisplayLanguageSystem": "Follow System",
|
||||||
"settingsAppBarTransparent": "Transparent App Bar",
|
"settingsAppBarTransparent": "Transparent App Bar",
|
||||||
"settingsAppBarTransparentDescription": "Enable transparent effect for the app bar.",
|
"settingsAppBarTransparentDescription": "Enable transparent effect for the app bar.",
|
||||||
"settingsDrawerPreferCollapse": "Prefer Drawer Collapse",
|
"settingsDrawerPreferCollapse": "Prefer Drawer Collapse",
|
||||||
@ -565,5 +587,18 @@
|
|||||||
"newsReadingFromReader": "You're reading from HyperNet.Reader",
|
"newsReadingFromReader": "You're reading from HyperNet.Reader",
|
||||||
"newsReadingFromOriginal": "You're reading the original article",
|
"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.",
|
"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"
|
"newsToday": "Today's News",
|
||||||
|
"totpPostSetup": "One More Thing",
|
||||||
|
"totpPostSetupDescription": "Scan the QR Code below with Google Authenticator, Microsoft Authenticator, 1Password, Authy, Bitwarden or any of kind of authenticator app which supports TOTP.",
|
||||||
|
"totpNeverShare": "Never share this QR Code",
|
||||||
|
"needHelp": "Need Help?",
|
||||||
|
"needHelpLaunch": "Check out our Goatpedia!",
|
||||||
|
"walletCreate": "Create a Wallet",
|
||||||
|
"walletCreateSubtitle": "Create a wallet to start using Source Points",
|
||||||
|
"walletCreatePassword": "Set a payment password for your new wallet below",
|
||||||
|
"walletCurrencyShort": "SRC",
|
||||||
|
"walletCurrency": {
|
||||||
|
"one": "{} Source Point",
|
||||||
|
"other": "{} Source Points"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,9 @@
|
|||||||
"screenAccountProfileEdit": "编辑资料",
|
"screenAccountProfileEdit": "编辑资料",
|
||||||
"screenAbuseReport": "滥用检举",
|
"screenAbuseReport": "滥用检举",
|
||||||
"screenSettings": "设置",
|
"screenSettings": "设置",
|
||||||
|
"screenAccountSettings": "账号设置",
|
||||||
|
"screenFactorSettings": "验证因子",
|
||||||
|
"screenAccountWallet": "钱包",
|
||||||
"screenNews": "新闻",
|
"screenNews": "新闻",
|
||||||
"screenAlbum": "相册",
|
"screenAlbum": "相册",
|
||||||
"screenChat": "聊天",
|
"screenChat": "聊天",
|
||||||
@ -88,8 +91,18 @@
|
|||||||
},
|
},
|
||||||
"loginEnterPassword": "验证代码",
|
"loginEnterPassword": "验证代码",
|
||||||
"loginSuccess": "登录为 {}",
|
"loginSuccess": "登录为 {}",
|
||||||
|
"authFactorDelete": "删除验证因子",
|
||||||
|
"authFactorDeleteDescription": "你确定要删除 {} 验证因子吗?",
|
||||||
"authFactorPassword": "密码",
|
"authFactorPassword": "密码",
|
||||||
|
"authFactorPasswordDescription": "注册时选择设置的密码。",
|
||||||
"authFactorEmail": "电邮一次性验证码",
|
"authFactorEmail": "电邮一次性验证码",
|
||||||
|
"authFactorEmailDescription": "由我们生成并发送到绑定的的电子邮箱的一次性验证码。",
|
||||||
|
"authFactorTOTP": "时序验证码",
|
||||||
|
"authFactorTOTPDescription": "使用 Google Authenticator 或 Authy 等验证器生成的一次性验证码。",
|
||||||
|
"authFactorInAppNotify": "应用内通知验证码",
|
||||||
|
"authFactorInAppNotifyDescription": "通过站内通知推送的一次性验证码。",
|
||||||
|
"authFactorAdd": "添加新验证因子",
|
||||||
|
"authFactorAddSubtitle": "给你的帐户登陆时提供另一个方案。",
|
||||||
"accountIntroTitle": "喜欢您来!",
|
"accountIntroTitle": "喜欢您来!",
|
||||||
"accountIntroSubtitle": "登陆以探索更广大的世界。",
|
"accountIntroSubtitle": "登陆以探索更广大的世界。",
|
||||||
"accountLogout": "退出登录",
|
"accountLogout": "退出登录",
|
||||||
@ -98,8 +111,14 @@
|
|||||||
"accountLogoutConfirm": "您需要重新输入账号密码,甚至可能需要多步验证来再次登陆。",
|
"accountLogoutConfirm": "您需要重新输入账号密码,甚至可能需要多步验证来再次登陆。",
|
||||||
"accountPublishers": "你的发布者",
|
"accountPublishers": "你的发布者",
|
||||||
"accountPublishersSubtitle": "管理你的公共形象。",
|
"accountPublishersSubtitle": "管理你的公共形象。",
|
||||||
|
"accountSettings": "帐号设置",
|
||||||
|
"accountSettingsSubtitle": "管理你的帐号并让它更好的服务你。",
|
||||||
"accountProfileEdit": "编辑资料",
|
"accountProfileEdit": "编辑资料",
|
||||||
"accountProfileEditSubtitle": "使你的 Solarpass 账户更像你。",
|
"accountProfileEditSubtitle": "使你的 Solarpass 账户更像你。",
|
||||||
|
"accountWallet": "钱包",
|
||||||
|
"accountWalletSubtitle": "查看你的余额和交易记录。",
|
||||||
|
"factorSettings": "验证因子",
|
||||||
|
"factorSettingsSubtitle": "管理你的登陆验证方式。",
|
||||||
"accountProfileEditApplied": "个人资料修改已被应用。",
|
"accountProfileEditApplied": "个人资料修改已被应用。",
|
||||||
"publishersNew": "新发布者",
|
"publishersNew": "新发布者",
|
||||||
"publisherNewSubtitle": "创建一个新的公共身份。",
|
"publisherNewSubtitle": "创建一个新的公共身份。",
|
||||||
@ -178,6 +197,9 @@
|
|||||||
"other": "{} 条评论"
|
"other": "{} 条评论"
|
||||||
},
|
},
|
||||||
"settingsAppearance": "外观",
|
"settingsAppearance": "外观",
|
||||||
|
"settingsDisplayLanguage": "显示语言",
|
||||||
|
"settingsDisplayLanguageDescription": "设置应用程序使用的语言",
|
||||||
|
"settingsDisplayLanguageSystem": "跟随系统",
|
||||||
"settingsBackgroundImage": "背景图片",
|
"settingsBackgroundImage": "背景图片",
|
||||||
"settingsBackgroundImageDescription": "设置应用全局生效的的背景图片。",
|
"settingsBackgroundImageDescription": "设置应用全局生效的的背景图片。",
|
||||||
"settingsBackgroundImageClear": "清除现存背景图",
|
"settingsBackgroundImageClear": "清除现存背景图",
|
||||||
@ -563,5 +585,18 @@
|
|||||||
"newsReadingFromReader": "你正在从 HyperNet.Reader 阅读文章",
|
"newsReadingFromReader": "你正在从 HyperNet.Reader 阅读文章",
|
||||||
"newsReadingFromOriginal": "你正在阅读原始文章",
|
"newsReadingFromOriginal": "你正在阅读原始文章",
|
||||||
"newsDisclaimer": "本文由 HyperNet.Reader 从互联网上获取,我们不担保其内容的真实性,请自行判断。本文章的所有内容版权归原作者所有。",
|
"newsDisclaimer": "本文由 HyperNet.Reader 从互联网上获取,我们不担保其内容的真实性,请自行判断。本文章的所有内容版权归原作者所有。",
|
||||||
"newsToday": "快讯"
|
"newsToday": "快讯",
|
||||||
|
"totpPostSetup": "还有一件事",
|
||||||
|
"totpPostSetupDescription": "使用 Google Authenticator, Microsoft Authenticator, 1Password, Authy, Bitwarden 或其他支持 TOTP 的验证器扫描本 QR Code 来添加。",
|
||||||
|
"totpNeverShare": "永远不要分享这个 QR Code",
|
||||||
|
"needHelp": "需要帮助?",
|
||||||
|
"needHelpLaunch": "查看我们的山羊维基!",
|
||||||
|
"walletCreate": "创建钱包",
|
||||||
|
"walletCreateSubtitle": "创建于一个钱包来开始使用源点。",
|
||||||
|
"walletCreatePassword": "在下方设置你的付款密码",
|
||||||
|
"walletCurrencyShort": "源点",
|
||||||
|
"walletCurrency": {
|
||||||
|
"one": "{} 源点",
|
||||||
|
"other": "{} 源点"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,9 @@
|
|||||||
"screenAccountProfileEdit": "編輯資料",
|
"screenAccountProfileEdit": "編輯資料",
|
||||||
"screenAbuseReport": "濫用檢舉",
|
"screenAbuseReport": "濫用檢舉",
|
||||||
"screenSettings": "設置",
|
"screenSettings": "設置",
|
||||||
|
"screenAccountSettings": "賬號設置",
|
||||||
|
"screenFactorSettings": "驗證因子",
|
||||||
|
"screenAccountWallet": "錢包",
|
||||||
"screenNews": "新聞",
|
"screenNews": "新聞",
|
||||||
"screenAlbum": "相冊",
|
"screenAlbum": "相冊",
|
||||||
"screenChat": "聊天",
|
"screenChat": "聊天",
|
||||||
@ -88,8 +91,18 @@
|
|||||||
},
|
},
|
||||||
"loginEnterPassword": "驗證代碼",
|
"loginEnterPassword": "驗證代碼",
|
||||||
"loginSuccess": "登錄為 {}",
|
"loginSuccess": "登錄為 {}",
|
||||||
|
"authFactorDelete": "刪除驗證因子",
|
||||||
|
"authFactorDeleteDescription": "你確定要刪除 {} 驗證因子嗎?",
|
||||||
"authFactorPassword": "密碼",
|
"authFactorPassword": "密碼",
|
||||||
|
"authFactorPasswordDescription": "註冊時選擇設置的密碼。",
|
||||||
"authFactorEmail": "電郵一次性驗證碼",
|
"authFactorEmail": "電郵一次性驗證碼",
|
||||||
|
"authFactorEmailDescription": "由我們生成併發送到綁定的的電子郵箱的一次性驗證碼。",
|
||||||
|
"authFactorTOTP": "時序驗證碼",
|
||||||
|
"authFactorTOTPDescription": "使用 Google Authenticator 或 Authy 等驗證器生成的一次性驗證碼。",
|
||||||
|
"authFactorInAppNotify": "應用內通知驗證碼",
|
||||||
|
"authFactorInAppNotifyDescription": "通過站內通知推送的一次性驗證碼。",
|
||||||
|
"authFactorAdd": "添加新驗證因子",
|
||||||
|
"authFactorAddSubtitle": "給你的帳户登陸時提供另一個方案。",
|
||||||
"accountIntroTitle": "喜歡您來!",
|
"accountIntroTitle": "喜歡您來!",
|
||||||
"accountIntroSubtitle": "登陸以探索更廣大的世界。",
|
"accountIntroSubtitle": "登陸以探索更廣大的世界。",
|
||||||
"accountLogout": "退出登錄",
|
"accountLogout": "退出登錄",
|
||||||
@ -98,8 +111,14 @@
|
|||||||
"accountLogoutConfirm": "您需要重新輸入賬號密碼,甚至可能需要多步驗證來再次登陸。",
|
"accountLogoutConfirm": "您需要重新輸入賬號密碼,甚至可能需要多步驗證來再次登陸。",
|
||||||
"accountPublishers": "你的發佈者",
|
"accountPublishers": "你的發佈者",
|
||||||
"accountPublishersSubtitle": "管理你的公共形象。",
|
"accountPublishersSubtitle": "管理你的公共形象。",
|
||||||
|
"accountSettings": "帳號設置",
|
||||||
|
"accountSettingsSubtitle": "管理你的帳號並讓它更好的服務你。",
|
||||||
"accountProfileEdit": "編輯資料",
|
"accountProfileEdit": "編輯資料",
|
||||||
"accountProfileEditSubtitle": "使你的 Solarpass 賬户更像你。",
|
"accountProfileEditSubtitle": "使你的 Solarpass 賬户更像你。",
|
||||||
|
"accountWallet": "錢包",
|
||||||
|
"accountWalletSubtitle": "查看你的餘額和交易記錄。",
|
||||||
|
"factorSettings": "驗證因子",
|
||||||
|
"factorSettingsSubtitle": "管理你的登陸驗證方式。",
|
||||||
"accountProfileEditApplied": "個人資料修改已被應用。",
|
"accountProfileEditApplied": "個人資料修改已被應用。",
|
||||||
"publishersNew": "新發布者",
|
"publishersNew": "新發布者",
|
||||||
"publisherNewSubtitle": "創建一個新的公共身份。",
|
"publisherNewSubtitle": "創建一個新的公共身份。",
|
||||||
@ -178,6 +197,9 @@
|
|||||||
"other": "{} 條評論"
|
"other": "{} 條評論"
|
||||||
},
|
},
|
||||||
"settingsAppearance": "外觀",
|
"settingsAppearance": "外觀",
|
||||||
|
"settingsDisplayLanguage": "顯示語言",
|
||||||
|
"settingsDisplayLanguageDescription": "設置應用程序使用的語言",
|
||||||
|
"settingsDisplayLanguageSystem": "跟隨系統",
|
||||||
"settingsBackgroundImage": "背景圖片",
|
"settingsBackgroundImage": "背景圖片",
|
||||||
"settingsBackgroundImageDescription": "設置應用全局生效的的背景圖片。",
|
"settingsBackgroundImageDescription": "設置應用全局生效的的背景圖片。",
|
||||||
"settingsBackgroundImageClear": "清除現存背景圖",
|
"settingsBackgroundImageClear": "清除現存背景圖",
|
||||||
@ -563,5 +585,18 @@
|
|||||||
"newsReadingFromReader": "你正在從 HyperNet.Reader 閲讀文章",
|
"newsReadingFromReader": "你正在從 HyperNet.Reader 閲讀文章",
|
||||||
"newsReadingFromOriginal": "你正在閲讀原始文章",
|
"newsReadingFromOriginal": "你正在閲讀原始文章",
|
||||||
"newsDisclaimer": "本文由 HyperNet.Reader 從互聯網上獲取,我們不擔保其內容的真實性,請自行判斷。本文章的所有內容版權歸原作者所有。",
|
"newsDisclaimer": "本文由 HyperNet.Reader 從互聯網上獲取,我們不擔保其內容的真實性,請自行判斷。本文章的所有內容版權歸原作者所有。",
|
||||||
"newsToday": "快訊"
|
"newsToday": "快訊",
|
||||||
|
"totpPostSetup": "還有一件事",
|
||||||
|
"totpPostSetupDescription": "使用 Google Authenticator, Microsoft Authenticator, 1Password, Authy, Bitwarden 或其他支持 TOTP 的驗證器掃描本 QR Code 來添加。",
|
||||||
|
"totpNeverShare": "永遠不要分享這個 QR Code",
|
||||||
|
"needHelp": "需要幫助?",
|
||||||
|
"needHelpLaunch": "查看我們的山羊維基!",
|
||||||
|
"walletCreate": "創建錢包",
|
||||||
|
"walletCreateSubtitle": "創建於一個錢包來開始使用源點。",
|
||||||
|
"walletCreatePassword": "在下方設置你的付款密碼",
|
||||||
|
"walletCurrencyShort": "源點",
|
||||||
|
"walletCurrency": {
|
||||||
|
"one": "{} 源點",
|
||||||
|
"other": "{} 源點"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,9 @@
|
|||||||
"screenAccountProfileEdit": "編輯資料",
|
"screenAccountProfileEdit": "編輯資料",
|
||||||
"screenAbuseReport": "濫用檢舉",
|
"screenAbuseReport": "濫用檢舉",
|
||||||
"screenSettings": "設置",
|
"screenSettings": "設置",
|
||||||
|
"screenAccountSettings": "賬號設置",
|
||||||
|
"screenFactorSettings": "驗證因子",
|
||||||
|
"screenAccountWallet": "錢包",
|
||||||
"screenNews": "新聞",
|
"screenNews": "新聞",
|
||||||
"screenAlbum": "相冊",
|
"screenAlbum": "相冊",
|
||||||
"screenChat": "聊天",
|
"screenChat": "聊天",
|
||||||
@ -88,8 +91,18 @@
|
|||||||
},
|
},
|
||||||
"loginEnterPassword": "驗證代碼",
|
"loginEnterPassword": "驗證代碼",
|
||||||
"loginSuccess": "登錄為 {}",
|
"loginSuccess": "登錄為 {}",
|
||||||
|
"authFactorDelete": "刪除驗證因子",
|
||||||
|
"authFactorDeleteDescription": "你確定要刪除 {} 驗證因子嗎?",
|
||||||
"authFactorPassword": "密碼",
|
"authFactorPassword": "密碼",
|
||||||
|
"authFactorPasswordDescription": "註冊時選擇設置的密碼。",
|
||||||
"authFactorEmail": "電郵一次性驗證碼",
|
"authFactorEmail": "電郵一次性驗證碼",
|
||||||
|
"authFactorEmailDescription": "由我們生成併發送到綁定的的電子郵箱的一次性驗證碼。",
|
||||||
|
"authFactorTOTP": "時序驗證碼",
|
||||||
|
"authFactorTOTPDescription": "使用 Google Authenticator 或 Authy 等驗證器生成的一次性驗證碼。",
|
||||||
|
"authFactorInAppNotify": "應用內通知驗證碼",
|
||||||
|
"authFactorInAppNotifyDescription": "通過站內通知推送的一次性驗證碼。",
|
||||||
|
"authFactorAdd": "添加新驗證因子",
|
||||||
|
"authFactorAddSubtitle": "給你的帳戶登陸時提供另一個方案。",
|
||||||
"accountIntroTitle": "喜歡您來!",
|
"accountIntroTitle": "喜歡您來!",
|
||||||
"accountIntroSubtitle": "登陸以探索更廣大的世界。",
|
"accountIntroSubtitle": "登陸以探索更廣大的世界。",
|
||||||
"accountLogout": "退出登錄",
|
"accountLogout": "退出登錄",
|
||||||
@ -98,8 +111,14 @@
|
|||||||
"accountLogoutConfirm": "您需要重新輸入賬號密碼,甚至可能需要多步驗證來再次登陸。",
|
"accountLogoutConfirm": "您需要重新輸入賬號密碼,甚至可能需要多步驗證來再次登陸。",
|
||||||
"accountPublishers": "你的發佈者",
|
"accountPublishers": "你的發佈者",
|
||||||
"accountPublishersSubtitle": "管理你的公共形象。",
|
"accountPublishersSubtitle": "管理你的公共形象。",
|
||||||
|
"accountSettings": "帳號設置",
|
||||||
|
"accountSettingsSubtitle": "管理你的帳號並讓它更好的服務你。",
|
||||||
"accountProfileEdit": "編輯資料",
|
"accountProfileEdit": "編輯資料",
|
||||||
"accountProfileEditSubtitle": "使你的 Solarpass 賬戶更像你。",
|
"accountProfileEditSubtitle": "使你的 Solarpass 賬戶更像你。",
|
||||||
|
"accountWallet": "錢包",
|
||||||
|
"accountWalletSubtitle": "查看你的餘額和交易記錄。",
|
||||||
|
"factorSettings": "驗證因子",
|
||||||
|
"factorSettingsSubtitle": "管理你的登陸驗證方式。",
|
||||||
"accountProfileEditApplied": "個人資料修改已被應用。",
|
"accountProfileEditApplied": "個人資料修改已被應用。",
|
||||||
"publishersNew": "新發布者",
|
"publishersNew": "新發布者",
|
||||||
"publisherNewSubtitle": "創建一個新的公共身份。",
|
"publisherNewSubtitle": "創建一個新的公共身份。",
|
||||||
@ -178,6 +197,9 @@
|
|||||||
"other": "{} 條評論"
|
"other": "{} 條評論"
|
||||||
},
|
},
|
||||||
"settingsAppearance": "外觀",
|
"settingsAppearance": "外觀",
|
||||||
|
"settingsDisplayLanguage": "顯示語言",
|
||||||
|
"settingsDisplayLanguageDescription": "設置應用程序使用的語言",
|
||||||
|
"settingsDisplayLanguageSystem": "跟隨系統",
|
||||||
"settingsBackgroundImage": "背景圖片",
|
"settingsBackgroundImage": "背景圖片",
|
||||||
"settingsBackgroundImageDescription": "設置應用全局生效的的背景圖片。",
|
"settingsBackgroundImageDescription": "設置應用全局生效的的背景圖片。",
|
||||||
"settingsBackgroundImageClear": "清除現存背景圖",
|
"settingsBackgroundImageClear": "清除現存背景圖",
|
||||||
@ -563,5 +585,18 @@
|
|||||||
"newsReadingFromReader": "你正在從 HyperNet.Reader 閱讀文章",
|
"newsReadingFromReader": "你正在從 HyperNet.Reader 閱讀文章",
|
||||||
"newsReadingFromOriginal": "你正在閱讀原始文章",
|
"newsReadingFromOriginal": "你正在閱讀原始文章",
|
||||||
"newsDisclaimer": "本文由 HyperNet.Reader 從互聯網上獲取,我們不擔保其內容的真實性,請自行判斷。本文章的所有內容版權歸原作者所有。",
|
"newsDisclaimer": "本文由 HyperNet.Reader 從互聯網上獲取,我們不擔保其內容的真實性,請自行判斷。本文章的所有內容版權歸原作者所有。",
|
||||||
"newsToday": "快訊"
|
"newsToday": "快訊",
|
||||||
|
"totpPostSetup": "還有一件事",
|
||||||
|
"totpPostSetupDescription": "使用 Google Authenticator, Microsoft Authenticator, 1Password, Authy, Bitwarden 或其他支持 TOTP 的驗證器掃描本 QR Code 來添加。",
|
||||||
|
"totpNeverShare": "永遠不要分享這個 QR Code",
|
||||||
|
"needHelp": "需要幫助?",
|
||||||
|
"needHelpLaunch": "查看我們的山羊維基!",
|
||||||
|
"walletCreate": "創建錢包",
|
||||||
|
"walletCreateSubtitle": "創建於一個錢包來開始使用源點。",
|
||||||
|
"walletCreatePassword": "在下方設置你的付款密碼",
|
||||||
|
"walletCurrencyShort": "源點",
|
||||||
|
"walletCurrency": {
|
||||||
|
"one": "{} 源點",
|
||||||
|
"other": "{} 源點"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -53,14 +53,14 @@ PODS:
|
|||||||
- Firebase/Messaging (11.6.0):
|
- Firebase/Messaging (11.6.0):
|
||||||
- Firebase/CoreOnly
|
- Firebase/CoreOnly
|
||||||
- FirebaseMessaging (~> 11.6.0)
|
- FirebaseMessaging (~> 11.6.0)
|
||||||
- firebase_analytics (11.4.0):
|
- firebase_analytics (11.4.1):
|
||||||
- Firebase/Analytics (= 11.6.0)
|
- Firebase/Analytics (= 11.6.0)
|
||||||
- firebase_core
|
- firebase_core
|
||||||
- Flutter
|
- Flutter
|
||||||
- firebase_core (3.10.0):
|
- firebase_core (3.10.1):
|
||||||
- Firebase/CoreOnly (= 11.6.0)
|
- Firebase/CoreOnly (= 11.6.0)
|
||||||
- Flutter
|
- Flutter
|
||||||
- firebase_messaging (15.2.0):
|
- firebase_messaging (15.2.1):
|
||||||
- Firebase/Messaging (= 11.6.0)
|
- Firebase/Messaging (= 11.6.0)
|
||||||
- firebase_core
|
- firebase_core
|
||||||
- Flutter
|
- Flutter
|
||||||
@ -382,9 +382,9 @@ SPEC CHECKSUMS:
|
|||||||
file_picker: 09aa5ec1ab24135ccd7a1621c46c84134bfd6655
|
file_picker: 09aa5ec1ab24135ccd7a1621c46c84134bfd6655
|
||||||
file_saver: 503e386464dbe118f630e17b4c2e1190fa0cf808
|
file_saver: 503e386464dbe118f630e17b4c2e1190fa0cf808
|
||||||
Firebase: 374a441a91ead896215703a674d58cdb3e9d772b
|
Firebase: 374a441a91ead896215703a674d58cdb3e9d772b
|
||||||
firebase_analytics: 07bd7cfbac54bfcdccf2bb2530f9a65486f7ef3f
|
firebase_analytics: 13ea4ad8a42c5060bad7e6694304dabb8b02fe7e
|
||||||
firebase_core: feb37e79f775c2bd08dd35e02d83678291317e10
|
firebase_core: e2aa06dbd854d961f8ce46c2e20933bee1bf2d2b
|
||||||
firebase_messaging: e2f0ba891b1509668c07f5099761518a5af8fe3c
|
firebase_messaging: 96cf6d67121b3f39746b2a4f29a26c0eee4af70e
|
||||||
FirebaseAnalytics: 7114c698cac995602e3b1b96663473e50d54d6e7
|
FirebaseAnalytics: 7114c698cac995602e3b1b96663473e50d54d6e7
|
||||||
FirebaseCore: 48b0dd707581cf9c1a1220da68223fb0a562afaa
|
FirebaseCore: 48b0dd707581cf9c1a1220da68223fb0a562afaa
|
||||||
FirebaseCoreInternal: d98ab91e2d80a56d7b246856a8885443b302c0c2
|
FirebaseCoreInternal: d98ab91e2d80a56d7b246856a8885443b302c0c2
|
||||||
|
104
lib/router.dart
104
lib/router.dart
@ -3,6 +3,8 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:surface/screens/abuse_report.dart';
|
import 'package:surface/screens/abuse_report.dart';
|
||||||
import 'package:surface/screens/account.dart';
|
import 'package:surface/screens/account.dart';
|
||||||
|
import 'package:surface/screens/account/account_settings.dart';
|
||||||
|
import 'package:surface/screens/account/factor_settings.dart';
|
||||||
import 'package:surface/screens/account/profile_page.dart';
|
import 'package:surface/screens/account/profile_page.dart';
|
||||||
import 'package:surface/screens/account/profile_edit.dart';
|
import 'package:surface/screens/account/profile_edit.dart';
|
||||||
import 'package:surface/screens/account/publishers/publisher_edit.dart';
|
import 'package:surface/screens/account/publishers/publisher_edit.dart';
|
||||||
@ -31,6 +33,7 @@ import 'package:surface/screens/realm/manage.dart';
|
|||||||
import 'package:surface/screens/realm/realm_detail.dart';
|
import 'package:surface/screens/realm/realm_detail.dart';
|
||||||
import 'package:surface/screens/settings.dart';
|
import 'package:surface/screens/settings.dart';
|
||||||
import 'package:surface/screens/sharing.dart';
|
import 'package:surface/screens/sharing.dart';
|
||||||
|
import 'package:surface/screens/wallet.dart';
|
||||||
import 'package:surface/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_scaffold.dart';
|
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
||||||
@ -96,11 +99,52 @@ final _appRoutes = [
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(path: '/account', name: 'account', builder: (context, state) => const AccountScreen(), routes: [
|
||||||
path: '/account',
|
GoRoute(
|
||||||
name: 'account',
|
path: '/wallet',
|
||||||
builder: (context, state) => const AccountScreen(),
|
name: 'accountWallet',
|
||||||
),
|
builder: (context, state) => const WalletScreen(),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/settings',
|
||||||
|
name: 'accountSettings',
|
||||||
|
builder: (context, state) => AccountSettingsScreen(),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/settings/factors',
|
||||||
|
name: 'factorSettings',
|
||||||
|
builder: (context, state) => FactorSettingsScreen(),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/profile/edit',
|
||||||
|
name: 'accountProfileEdit',
|
||||||
|
builder: (context, state) => ProfileEditScreen(),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/publishers',
|
||||||
|
name: 'accountPublishers',
|
||||||
|
builder: (context, state) => PublisherScreen(),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/publishers/new',
|
||||||
|
name: 'accountPublisherNew',
|
||||||
|
builder: (context, state) => AccountPublisherNewScreen(),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/publishers/edit/:name',
|
||||||
|
name: 'accountPublisherEdit',
|
||||||
|
builder: (context, state) => AccountPublisherEditScreen(
|
||||||
|
name: state.pathParameters['name']!,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/:name',
|
||||||
|
name: 'accountProfilePage',
|
||||||
|
pageBuilder: (context, state) => NoTransitionPage(
|
||||||
|
child: UserScreen(name: state.pathParameters['name']!),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/chat',
|
path: '/chat',
|
||||||
name: 'chat',
|
name: 'chat',
|
||||||
@ -161,20 +205,15 @@ final _appRoutes = [
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(path: '/news', name: 'news', builder: (context, state) => const NewsScreen(), routes: [
|
||||||
path: '/news',
|
GoRoute(
|
||||||
name: 'news',
|
path: '/:hash',
|
||||||
builder: (context, state) => const NewsScreen(),
|
name: 'newsDetail',
|
||||||
routes: [
|
builder: (context, state) => NewsDetailScreen(
|
||||||
GoRoute(
|
hash: state.pathParameters['hash']!,
|
||||||
path: '/:hash',
|
|
||||||
name: 'newsDetail',
|
|
||||||
builder: (context, state) => NewsDetailScreen(
|
|
||||||
hash: state.pathParameters['hash']!,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
]
|
),
|
||||||
),
|
]),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/album',
|
path: '/album',
|
||||||
name: 'album',
|
name: 'album',
|
||||||
@ -205,35 +244,6 @@ final _appRoutes = [
|
|||||||
name: 'abuseReport',
|
name: 'abuseReport',
|
||||||
builder: (context, state) => AbuseReportScreen(),
|
builder: (context, state) => AbuseReportScreen(),
|
||||||
),
|
),
|
||||||
GoRoute(
|
|
||||||
path: '/account/profile/edit',
|
|
||||||
name: 'accountProfileEdit',
|
|
||||||
builder: (context, state) => ProfileEditScreen(),
|
|
||||||
),
|
|
||||||
GoRoute(
|
|
||||||
path: '/account/publishers',
|
|
||||||
name: 'accountPublishers',
|
|
||||||
builder: (context, state) => PublisherScreen(),
|
|
||||||
),
|
|
||||||
GoRoute(
|
|
||||||
path: '/account/publishers/new',
|
|
||||||
name: 'accountPublisherNew',
|
|
||||||
builder: (context, state) => AccountPublisherNewScreen(),
|
|
||||||
),
|
|
||||||
GoRoute(
|
|
||||||
path: '/account/publishers/edit/:name',
|
|
||||||
name: 'accountPublisherEdit',
|
|
||||||
builder: (context, state) => AccountPublisherEditScreen(
|
|
||||||
name: state.pathParameters['name']!,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
GoRoute(
|
|
||||||
path: '/account/:name',
|
|
||||||
name: 'accountProfilePage',
|
|
||||||
pageBuilder: (context, state) => NoTransitionPage(
|
|
||||||
child: UserScreen(name: state.pathParameters['name']!),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/settings',
|
path: '/settings',
|
||||||
name: 'settings',
|
name: 'settings',
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
@ -13,6 +15,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/navigation/app_scaffold.dart';
|
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
||||||
|
import 'package:surface/widgets/universal_image.dart';
|
||||||
|
|
||||||
class AccountScreen extends StatelessWidget {
|
class AccountScreen extends StatelessWidget {
|
||||||
const AccountScreen({super.key});
|
const AccountScreen({super.key});
|
||||||
@ -20,11 +23,39 @@ class AccountScreen extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final ua = context.watch<UserProvider>();
|
final ua = context.watch<UserProvider>();
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
|
||||||
return AppScaffold(
|
return AppScaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
leading: AutoAppBarLeading(),
|
leading: AutoAppBarLeading(),
|
||||||
title: Text("screenAccount").tr(),
|
title: Text("screenAccount").tr(),
|
||||||
|
flexibleSpace: ua.user != null && ua.user!.banner.isNotEmpty
|
||||||
|
? Stack(
|
||||||
|
fit: StackFit.expand,
|
||||||
|
children: [
|
||||||
|
AutoResizeUniversalImage(sn.getAttachmentUrl(ua.user!.banner), fit: BoxFit.cover),
|
||||||
|
Positioned(
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
height: 56 + MediaQuery.of(context).padding.top,
|
||||||
|
child: ClipRect(
|
||||||
|
child: BackdropFilter(
|
||||||
|
filter: ImageFilter.blur(
|
||||||
|
sigmaX: 10,
|
||||||
|
sigmaY: 10,
|
||||||
|
),
|
||||||
|
child: Container(
|
||||||
|
color: Colors.black.withOpacity(
|
||||||
|
clampDouble(10 * 0.1, 0, 0.5),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
: null,
|
||||||
actions: [
|
actions: [
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Symbols.settings, fill: 1),
|
icon: const Icon(Symbols.settings, fill: 1),
|
||||||
@ -83,16 +114,6 @@ class _AuthorizedAccountScreen extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}).padding(all: 20),
|
}).padding(all: 20),
|
||||||
).padding(horizontal: 8, top: 16, bottom: 4),
|
).padding(horizontal: 8, top: 16, bottom: 4),
|
||||||
ListTile(
|
|
||||||
title: Text('accountProfileEdit').tr(),
|
|
||||||
subtitle: Text('accountProfileEditSubtitle').tr(),
|
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
|
||||||
leading: const Icon(Symbols.contact_page),
|
|
||||||
trailing: const Icon(Symbols.chevron_right),
|
|
||||||
onTap: () {
|
|
||||||
GoRouter.of(context).pushNamed('accountProfileEdit');
|
|
||||||
},
|
|
||||||
),
|
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text('accountPublishers').tr(),
|
title: Text('accountPublishers').tr(),
|
||||||
subtitle: Text('accountPublishersSubtitle').tr(),
|
subtitle: Text('accountPublishersSubtitle').tr(),
|
||||||
@ -113,6 +134,36 @@ class _AuthorizedAccountScreen extends StatelessWidget {
|
|||||||
GoRouter.of(context).pushNamed('abuseReport');
|
GoRouter.of(context).pushNamed('abuseReport');
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
ListTile(
|
||||||
|
title: Text('factorSettings').tr(),
|
||||||
|
subtitle: Text('factorSettingsSubtitle').tr(),
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
leading: const Icon(Symbols.lock),
|
||||||
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
|
onTap: () {
|
||||||
|
GoRouter.of(context).pushNamed('factorSettings');
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
title: Text('accountWallet').tr(),
|
||||||
|
subtitle: Text('accountWalletSubtitle').tr(),
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
leading: const Icon(Symbols.wallet),
|
||||||
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
|
onTap: () {
|
||||||
|
GoRouter.of(context).pushNamed('accountWallet');
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
title: Text('accountSettings').tr(),
|
||||||
|
subtitle: Text('accountSettingsSubtitle').tr(),
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
leading: const Icon(Symbols.manage_accounts),
|
||||||
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
|
onTap: () {
|
||||||
|
GoRouter.of(context).pushNamed('accountSettings');
|
||||||
|
},
|
||||||
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text('accountLogout').tr(),
|
title: Text('accountLogout').tr(),
|
||||||
subtitle: Text('accountLogoutSubtitle').tr(),
|
subtitle: Text('accountLogoutSubtitle').tr(),
|
||||||
@ -134,33 +185,6 @@ class _AuthorizedAccountScreen extends StatelessWidget {
|
|||||||
await Hive.initFlutter();
|
await Hive.initFlutter();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
ListTile(
|
|
||||||
title: Text('accountDeletion'.tr()),
|
|
||||||
subtitle: Text('accountDeletionActionDescription'.tr()),
|
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
|
||||||
leading: const Icon(Symbols.person_cancel),
|
|
||||||
trailing: const Icon(Symbols.chevron_right),
|
|
||||||
onTap: () {
|
|
||||||
context
|
|
||||||
.showConfirmDialog(
|
|
||||||
'accountDeletion'.tr(),
|
|
||||||
'accountDeletionDescription'.tr(),
|
|
||||||
)
|
|
||||||
.then((value) {
|
|
||||||
if (!value || !context.mounted) return;
|
|
||||||
final sn = context.read<SnNetworkProvider>();
|
|
||||||
sn.client.post('/cgi/id/users/me/deletion').then((value) {
|
|
||||||
if (context.mounted) {
|
|
||||||
context.showSnackbar('accountDeletionSubmitted'.tr());
|
|
||||||
}
|
|
||||||
}).catchError((err) {
|
|
||||||
if (context.mounted) {
|
|
||||||
context.showErrorDialog(err);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
66
lib/screens/account/account_settings.dart
Normal file
66
lib/screens/account/account_settings.dart
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:surface/providers/sn_network.dart';
|
||||||
|
import 'package:surface/widgets/dialog.dart';
|
||||||
|
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
||||||
|
|
||||||
|
class AccountSettingsScreen extends StatelessWidget {
|
||||||
|
const AccountSettingsScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return AppScaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
leading: PageBackButton(),
|
||||||
|
title: Text('screenAccountSettings').tr(),
|
||||||
|
),
|
||||||
|
body: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
ListTile(
|
||||||
|
title: Text('accountProfileEdit').tr(),
|
||||||
|
subtitle: Text('accountProfileEditSubtitle').tr(),
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
leading: const Icon(Symbols.contact_page),
|
||||||
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
|
onTap: () {
|
||||||
|
GoRouter.of(context).pushNamed('accountProfileEdit');
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
title: Text('accountDeletion'.tr()),
|
||||||
|
subtitle: Text('accountDeletionActionDescription'.tr()),
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
leading: const Icon(Symbols.person_cancel),
|
||||||
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
|
onTap: () {
|
||||||
|
context
|
||||||
|
.showConfirmDialog(
|
||||||
|
'accountDeletion'.tr(),
|
||||||
|
'accountDeletionDescription'.tr(),
|
||||||
|
)
|
||||||
|
.then((value) {
|
||||||
|
if (!value || !context.mounted) return;
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
sn.client.post('/cgi/id/users/me/deletion').then((value) {
|
||||||
|
if (context.mounted) {
|
||||||
|
context.showSnackbar('accountDeletionSubmitted'.tr());
|
||||||
|
}
|
||||||
|
}).catchError((err) {
|
||||||
|
if (context.mounted) {
|
||||||
|
context.showErrorDialog(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
294
lib/screens/account/factor_settings.dart
Normal file
294
lib/screens/account/factor_settings.dart
Normal file
@ -0,0 +1,294 @@
|
|||||||
|
import 'package:dropdown_button2/dropdown_button2.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:gap/gap.dart';
|
||||||
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:qr_flutter/qr_flutter.dart';
|
||||||
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
import 'package:surface/providers/sn_network.dart';
|
||||||
|
import 'package:surface/types/auth.dart';
|
||||||
|
import 'package:surface/widgets/dialog.dart';
|
||||||
|
import 'package:surface/widgets/loading_indicator.dart';
|
||||||
|
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
||||||
|
|
||||||
|
final Map<int, (String, String, IconData)> kFactorTypes = {
|
||||||
|
0: ('authFactorPassword', 'authFactorPasswordDescription', Symbols.password),
|
||||||
|
1: ('authFactorEmail', 'authFactorEmailDescription', Symbols.email),
|
||||||
|
2: ('authFactorTOTP', 'authFactorTOTPDescription', Symbols.timer),
|
||||||
|
3: ('authFactorInAppNotify', 'authFactorInAppNotifyDescription', Symbols.notifications_active),
|
||||||
|
};
|
||||||
|
|
||||||
|
class FactorSettingsScreen extends StatefulWidget {
|
||||||
|
const FactorSettingsScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<FactorSettingsScreen> createState() => _FactorSettingsScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FactorSettingsScreenState extends State<FactorSettingsScreen> {
|
||||||
|
bool _isBusy = false;
|
||||||
|
List<SnAuthFactor>? _factors;
|
||||||
|
|
||||||
|
Future<void> _fetchFactors() async {
|
||||||
|
try {
|
||||||
|
setState(() => _isBusy = true);
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
final resp = await sn.client.get('/cgi/id/users/me/factors');
|
||||||
|
_factors = List<SnAuthFactor>.from(
|
||||||
|
resp.data?.map((e) => SnAuthFactor.fromJson(e as Map<String, dynamic>)).toList() ?? [],
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
if (!mounted) return;
|
||||||
|
context.showErrorDialog(err);
|
||||||
|
} finally {
|
||||||
|
setState(() => _isBusy = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_fetchFactors();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return AppScaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
leading: PageBackButton(),
|
||||||
|
title: Text('screenFactorSettings').tr(),
|
||||||
|
),
|
||||||
|
body: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
LoadingIndicator(
|
||||||
|
isActive: _isBusy,
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
title: Text('authFactorAdd').tr(),
|
||||||
|
subtitle: Text('authFactorAddSubtitle').tr(),
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
leading: const Icon(Symbols.add),
|
||||||
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
|
onTap: () {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => _FactorNewDialog(
|
||||||
|
currentlyHave: _factors!,
|
||||||
|
),
|
||||||
|
).then((val) {
|
||||||
|
if (val == true) _fetchFactors();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const Divider(height: 1),
|
||||||
|
Expanded(
|
||||||
|
child: MediaQuery.removePadding(
|
||||||
|
context: context,
|
||||||
|
removeTop: true,
|
||||||
|
child: RefreshIndicator(
|
||||||
|
onRefresh: _fetchFactors,
|
||||||
|
child: ListView.builder(
|
||||||
|
itemCount: _factors?.length ?? 0,
|
||||||
|
itemBuilder: (context, idx) {
|
||||||
|
final ele = _factors![idx];
|
||||||
|
return ListTile(
|
||||||
|
title: Text(kFactorTypes[ele.type]!.$1).tr(),
|
||||||
|
subtitle: Text(kFactorTypes[ele.type]!.$2).tr(),
|
||||||
|
contentPadding: const EdgeInsets.only(left: 24, right: 12),
|
||||||
|
leading: Icon(kFactorTypes[ele.type]!.$3),
|
||||||
|
trailing: IconButton(
|
||||||
|
icon: const Icon(Symbols.close),
|
||||||
|
onPressed: ele.type > 0
|
||||||
|
? () {
|
||||||
|
context
|
||||||
|
.showConfirmDialog(
|
||||||
|
'authFactorDelete'.tr(),
|
||||||
|
'authFactorDeleteDescription'.tr(args: [kFactorTypes[ele.type]!.$1.tr()]),
|
||||||
|
)
|
||||||
|
.then((val) async {
|
||||||
|
if (!val) return;
|
||||||
|
try {
|
||||||
|
if (!context.mounted) return;
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
await sn.client.delete('/cgi/id/users/me/factors/${ele.id}');
|
||||||
|
_fetchFactors();
|
||||||
|
} catch (err) {
|
||||||
|
if (!context.mounted) return;
|
||||||
|
context.showErrorDialog(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FactorNewDialog extends StatefulWidget {
|
||||||
|
final List<SnAuthFactor> currentlyHave;
|
||||||
|
|
||||||
|
const _FactorNewDialog({required this.currentlyHave});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<_FactorNewDialog> createState() => _FactorNewDialogState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FactorNewDialogState extends State<_FactorNewDialog> {
|
||||||
|
int? _factorType;
|
||||||
|
bool _isBusy = false;
|
||||||
|
|
||||||
|
Future<void> _submit() async {
|
||||||
|
try {
|
||||||
|
setState(() => _isBusy = true);
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
final resp = await sn.client.post('/cgi/id/users/me/factors', data: {
|
||||||
|
'type': _factorType,
|
||||||
|
});
|
||||||
|
final factor = SnAuthFactor.fromJson(resp.data);
|
||||||
|
if (!mounted) return;
|
||||||
|
if (factor.type == 2) {
|
||||||
|
await showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => _FactorTotpFactorDialog(factor: factor),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (!mounted) return;
|
||||||
|
Navigator.of(context).pop(true);
|
||||||
|
} catch (err) {
|
||||||
|
if (!mounted) return;
|
||||||
|
context.showErrorDialog(err);
|
||||||
|
} finally {
|
||||||
|
setState(() => _isBusy = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: Text('authFactorAdd').tr(),
|
||||||
|
content: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
DropdownButtonHideUnderline(
|
||||||
|
child: DropdownButton2<int>(
|
||||||
|
hint: Text(
|
||||||
|
'Select Item',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
value: _factorType,
|
||||||
|
items: kFactorTypes.entries.map(
|
||||||
|
(ele) {
|
||||||
|
final contains = widget.currentlyHave.map((ele) => ele.type).contains(ele.key);
|
||||||
|
return DropdownMenuItem<int>(
|
||||||
|
enabled: !contains,
|
||||||
|
value: ele.key,
|
||||||
|
child: Text(
|
||||||
|
ele.value.$1.tr(),
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
).opacity(contains ? 0.75 : 1),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
).toList(),
|
||||||
|
onChanged: (val) => setState(() {
|
||||||
|
_factorType = val;
|
||||||
|
}),
|
||||||
|
buttonStyleData: ButtonStyleData(
|
||||||
|
height: 50,
|
||||||
|
padding: const EdgeInsets.only(left: 14, right: 14),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(14),
|
||||||
|
border: Border.all(
|
||||||
|
color: Theme.of(context).dividerColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: _isBusy ? null : () => Navigator.of(context).pop(),
|
||||||
|
child: Text('dialogCancel').tr(),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: _isBusy ? null : () => _submit(),
|
||||||
|
child: Text('dialogConfirm').tr(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FactorTotpFactorDialog extends StatelessWidget {
|
||||||
|
final SnAuthFactor factor;
|
||||||
|
|
||||||
|
const _FactorTotpFactorDialog({required this.factor});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Center(
|
||||||
|
child: Text(
|
||||||
|
'totpPostSetup',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: Theme.of(context).textTheme.titleLarge,
|
||||||
|
).tr().width(280),
|
||||||
|
),
|
||||||
|
const Gap(4),
|
||||||
|
Center(
|
||||||
|
child: Text(
|
||||||
|
'totpPostSetupDescription',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: Theme.of(context).textTheme.bodySmall,
|
||||||
|
).tr().width(280),
|
||||||
|
),
|
||||||
|
const Gap(16),
|
||||||
|
QrImageView(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
data: factor.config!['url'],
|
||||||
|
errorCorrectionLevel: QrErrorCorrectLevel.H,
|
||||||
|
version: QrVersions.auto,
|
||||||
|
size: 160,
|
||||||
|
gapless: true,
|
||||||
|
eyeStyle: QrEyeStyle(
|
||||||
|
eyeShape: QrEyeShape.circle,
|
||||||
|
color: Theme.of(context).colorScheme.onSurface,
|
||||||
|
),
|
||||||
|
dataModuleStyle: QrDataModuleStyle(
|
||||||
|
dataModuleShape: QrDataModuleShape.square,
|
||||||
|
color: Theme.of(context).colorScheme.onSurface,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Gap(16),
|
||||||
|
Center(
|
||||||
|
child: Text(
|
||||||
|
'totpNeverShare',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
|
).tr().bold().width(280),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -7,6 +7,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/providers/userinfo.dart';
|
import 'package:surface/providers/userinfo.dart';
|
||||||
|
import 'package:surface/screens/account/factor_settings.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:surface/widgets/navigation/app_scaffold.dart';
|
||||||
@ -14,11 +15,6 @@ import 'package:url_launcher/url_launcher_string.dart';
|
|||||||
|
|
||||||
import '../../providers/websocket.dart';
|
import '../../providers/websocket.dart';
|
||||||
|
|
||||||
final Map<int, (String label, IconData icon, bool isOtp)> _factorLabelMap = {
|
|
||||||
0: ('authFactorPassword'.tr(), Symbols.password, false),
|
|
||||||
1: ('authFactorEmail'.tr(), Symbols.email, true),
|
|
||||||
};
|
|
||||||
|
|
||||||
class LoginScreen extends StatefulWidget {
|
class LoginScreen extends StatefulWidget {
|
||||||
const LoginScreen({super.key});
|
const LoginScreen({super.key});
|
||||||
|
|
||||||
@ -212,7 +208,9 @@ class _LoginCheckScreenState extends State<_LoginCheckScreen> {
|
|||||||
controller: _passwordController,
|
controller: _passwordController,
|
||||||
obscureText: true,
|
obscureText: true,
|
||||||
autofillHints: [
|
autofillHints: [
|
||||||
(_factorLabelMap[widget.factor!.type]?.$3 ?? true) ? AutofillHints.password : AutofillHints.oneTimeCode
|
widget.factor!.type == 0
|
||||||
|
? AutofillHints.password
|
||||||
|
: AutofillHints.oneTimeCode
|
||||||
],
|
],
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
isDense: true,
|
isDense: true,
|
||||||
@ -267,7 +265,8 @@ class _LoginPickerScreenState extends State<_LoginPickerScreen> {
|
|||||||
bool _isBusy = false;
|
bool _isBusy = false;
|
||||||
int? _factorPicked;
|
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 {
|
void _performGetFactorCode() async {
|
||||||
if (_factorPicked == null) return;
|
if (_factorPicked == null) return;
|
||||||
@ -328,11 +327,11 @@ class _LoginPickerScreenState extends State<_LoginPickerScreen> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
secondary: Icon(
|
secondary: Icon(
|
||||||
_factorLabelMap[x.type]?.$2 ?? Symbols.question_mark,
|
kFactorTypes[x.type]?.$3 ?? Symbols.question_mark,
|
||||||
),
|
),
|
||||||
title: Text(
|
title: Text(
|
||||||
_factorLabelMap[x.type]?.$1 ?? 'unknown'.tr(),
|
kFactorTypes[x.type]?.$1 ?? 'unknown',
|
||||||
),
|
).tr(),
|
||||||
enabled: !widget.ticket!.factorTrail.contains(x.id),
|
enabled: !widget.ticket!.factorTrail.contains(x.id),
|
||||||
value: _factorPicked == x.id,
|
value: _factorPicked == x.id,
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
@ -408,11 +407,14 @@ class _LoginLookupScreenState extends State<_LoginLookupScreen> {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
final sn = context.read<SnNetworkProvider>();
|
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: {
|
await sn.client.post('/cgi/id/users/me/password-reset', data: {
|
||||||
'user_id': lookupResp.data['id'],
|
'user_id': lookupResp.data['id'],
|
||||||
});
|
});
|
||||||
if (mounted) context.showModalDialog('done'.tr(), 'signinResetPasswordSent'.tr());
|
if (mounted) {
|
||||||
|
context.showModalDialog('done'.tr(), 'signinResetPasswordSent'.tr());
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (mounted) context.showErrorDialog(err);
|
if (mounted) context.showErrorDialog(err);
|
||||||
} finally {
|
} finally {
|
||||||
@ -437,7 +439,8 @@ class _LoginLookupScreenState extends State<_LoginLookupScreen> {
|
|||||||
widget.onTicket(result.ticket);
|
widget.onTicket(result.ticket);
|
||||||
|
|
||||||
// Pull factors
|
// 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(),
|
'ticketId': result.ticket!.id.toString(),
|
||||||
});
|
});
|
||||||
widget.onFactor(
|
widget.onFactor(
|
||||||
@ -531,7 +534,10 @@ class _LoginLookupScreenState extends State<_LoginLookupScreen> {
|
|||||||
'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).colorScheme.onSurface.withAlpha((255 * 0.75).round()),
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.onSurface
|
||||||
|
.withAlpha((255 * 0.75).round()),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Material(
|
Material(
|
||||||
|
@ -7,6 +7,7 @@ import 'package:go_router/go_router.dart';
|
|||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
import 'package:surface/providers/config.dart';
|
||||||
import 'package:surface/providers/post.dart';
|
import 'package:surface/providers/post.dart';
|
||||||
import 'package:surface/providers/sn_network.dart';
|
import 'package:surface/providers/sn_network.dart';
|
||||||
import 'package:surface/screens/post/post_detail.dart';
|
import 'package:surface/screens/post/post_detail.dart';
|
||||||
@ -96,6 +97,8 @@ class _ExploreScreenState extends State<ExploreScreen> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final cfg = context.read<ConfigProvider>();
|
||||||
|
|
||||||
return AppScaffold(
|
return AppScaffold(
|
||||||
floatingActionButtonLocation: ExpandableFab.location,
|
floatingActionButtonLocation: ExpandableFab.location,
|
||||||
floatingActionButton: ExpandableFab(
|
floatingActionButton: ExpandableFab(
|
||||||
@ -243,8 +246,10 @@ class _ExploreScreenState extends State<ExploreScreen> {
|
|||||||
),
|
),
|
||||||
openColor: Colors.transparent,
|
openColor: Colors.transparent,
|
||||||
openElevation: 0,
|
openElevation: 0,
|
||||||
closedColor: Theme.of(context).colorScheme.surfaceContainerLow.withOpacity(0.75),
|
|
||||||
transitionType: ContainerTransitionType.fade,
|
transitionType: ContainerTransitionType.fade,
|
||||||
|
closedColor: Theme.of(context).colorScheme.surfaceContainerLow.withOpacity(
|
||||||
|
cfg.prefs.getBool(kAppBackgroundStoreKey) == true ? 0.75 : 1,
|
||||||
|
),
|
||||||
closedShape: const RoundedRectangleBorder(
|
closedShape: const RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.all(Radius.circular(16)),
|
borderRadius: BorderRadius.all(Radius.circular(16)),
|
||||||
),
|
),
|
||||||
|
@ -51,7 +51,7 @@ class HomeScreen extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _HomeScreenState extends State<HomeScreen> {
|
class _HomeScreenState extends State<HomeScreen> {
|
||||||
static const List<HomeScreenDashEntry> kCards = [
|
late final List<HomeScreenDashEntry> kCards = [
|
||||||
HomeScreenDashEntry(
|
HomeScreenDashEntry(
|
||||||
name: 'dashEntryRecommendation',
|
name: 'dashEntryRecommendation',
|
||||||
child: _HomeDashRecommendationPostWidget(),
|
child: _HomeDashRecommendationPostWidget(),
|
||||||
@ -69,7 +69,7 @@ class _HomeScreenState extends State<HomeScreen> {
|
|||||||
HomeScreenDashEntry(
|
HomeScreenDashEntry(
|
||||||
name: 'dashEntryTodayNews',
|
name: 'dashEntryTodayNews',
|
||||||
child: _HomeDashTodayNews(),
|
child: _HomeDashTodayNews(),
|
||||||
cols: 2,
|
cols: MediaQuery.of(context).size.width >= 640 ? 3 : 2,
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -293,7 +293,7 @@ class _HomeDashTodayNewsState extends State<_HomeDashTodayNews> {
|
|||||||
Text(
|
Text(
|
||||||
_article!.title,
|
_article!.title,
|
||||||
style: Theme.of(context).textTheme.titleMedium!.copyWith(fontSize: 18),
|
style: Theme.of(context).textTheme.titleMedium!.copyWith(fontSize: 18),
|
||||||
maxLines: 2,
|
maxLines: MediaQuery.of(context).size.width >= 640 ? 2 : 1,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
@ -302,20 +302,18 @@ class _HomeDashTodayNewsState extends State<_HomeDashTodayNews> {
|
|||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
style: Theme.of(context).textTheme.bodyMedium,
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
),
|
),
|
||||||
Builder(
|
Builder(builder: (context) {
|
||||||
builder: (context) {
|
final date = _article!.publishedAt ?? _article!.createdAt;
|
||||||
final date = _article!.publishedAt ?? _article!.createdAt;
|
return Row(
|
||||||
return Row(
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
spacing: 2,
|
||||||
spacing: 2,
|
children: [
|
||||||
children: [
|
Text(DateFormat().format(date)).textStyle(Theme.of(context).textTheme.bodySmall!),
|
||||||
Text(DateFormat().format(date)).textStyle(Theme.of(context).textTheme.bodySmall!),
|
Text(' · ').textStyle(Theme.of(context).textTheme.bodySmall!).bold(),
|
||||||
Text(' · ').textStyle(Theme.of(context).textTheme.bodySmall!).bold(),
|
Text(RelativeTime(context).format(date)).textStyle(Theme.of(context).textTheme.bodySmall!),
|
||||||
Text(RelativeTime(context).format(date)).textStyle(Theme.of(context).textTheme.bodySmall!),
|
],
|
||||||
],
|
).opacity(0.75);
|
||||||
).opacity(0.75);
|
}),
|
||||||
}
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
).padding(horizontal: 16),
|
).padding(horizontal: 16),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
@ -515,6 +513,11 @@ class _HomeDashCheckInWidgetState extends State<_HomeDashCheckInWidget> {
|
|||||||
'+${_todayRecord!.resultExperience} EXP',
|
'+${_todayRecord!.resultExperience} EXP',
|
||||||
style: Theme.of(context).textTheme.bodyLarge,
|
style: Theme.of(context).textTheme.bodyLarge,
|
||||||
),
|
),
|
||||||
|
if (_todayRecord!.resultCoin >= 0)
|
||||||
|
Text(
|
||||||
|
'+${_todayRecord!.resultCoin} ${'walletCurrencyShort'.tr()}',
|
||||||
|
style: Theme.of(context).textTheme.bodyLarge,
|
||||||
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -175,54 +175,57 @@ class _NewsDetailScreenState extends State<NewsDetailScreen> {
|
|||||||
),
|
),
|
||||||
if (_articleFragment != null && _isReadingFromReader)
|
if (_articleFragment != null && _isReadingFromReader)
|
||||||
Expanded(
|
Expanded(
|
||||||
child: SingleChildScrollView(
|
child: Container(
|
||||||
child: Column(
|
constraints: BoxConstraints(maxWidth: 640),
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
child: SingleChildScrollView(
|
||||||
spacing: 8,
|
child: Column(
|
||||||
children: [
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
Text(_article!.title, style: Theme.of(context).textTheme.titleLarge),
|
spacing: 8,
|
||||||
Builder(builder: (context) {
|
children: [
|
||||||
final htmlDescription = parse(_article!.description);
|
Text(_article!.title, style: Theme.of(context).textTheme.titleLarge),
|
||||||
return Text(
|
Builder(builder: (context) {
|
||||||
htmlDescription.children.map((ele) => ele.text.trim()).join(),
|
final htmlDescription = parse(_article!.description);
|
||||||
style: Theme.of(context).textTheme.bodyMedium,
|
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(
|
Builder(builder: (context) {
|
||||||
spacing: 2,
|
final date = _article!.publishedAt ?? _article!.createdAt;
|
||||||
children: [
|
return Row(
|
||||||
Text(DateFormat().format(date)).textStyle(Theme.of(context).textTheme.bodySmall!),
|
spacing: 2,
|
||||||
Text(' · ').textStyle(Theme.of(context).textTheme.bodySmall!).bold(),
|
children: [
|
||||||
Text(RelativeTime(context).format(date)).textStyle(Theme.of(context).textTheme.bodySmall!),
|
Text(DateFormat().format(date)).textStyle(Theme.of(context).textTheme.bodySmall!),
|
||||||
],
|
Text(' · ').textStyle(Theme.of(context).textTheme.bodySmall!).bold(),
|
||||||
).opacity(0.75);
|
Text(RelativeTime(context).format(date)).textStyle(Theme.of(context).textTheme.bodySmall!),
|
||||||
}),
|
],
|
||||||
Text('newsDisclaimer').tr().textStyle(Theme.of(context).textTheme.bodySmall!).opacity(0.75),
|
).opacity(0.75);
|
||||||
const Divider(),
|
}),
|
||||||
..._parseHtmlToWidgets(_articleFragment!.children),
|
Text('newsDisclaimer').tr().textStyle(Theme.of(context).textTheme.bodySmall!).opacity(0.75),
|
||||||
const Divider(),
|
const Divider(),
|
||||||
InkWell(
|
..._parseHtmlToWidgets(_articleFragment!.children),
|
||||||
child: Row(
|
const Divider(),
|
||||||
mainAxisSize: MainAxisSize.min,
|
InkWell(
|
||||||
children: [
|
child: Row(
|
||||||
Text(
|
mainAxisSize: MainAxisSize.min,
|
||||||
'Reference from original website',
|
children: [
|
||||||
style: TextStyle(decoration: TextDecoration.underline),
|
Text(
|
||||||
),
|
'Reference from original website',
|
||||||
const Gap(4),
|
style: TextStyle(decoration: TextDecoration.underline),
|
||||||
Icon(Icons.launch, size: 16),
|
),
|
||||||
],
|
const Gap(4),
|
||||||
).opacity(0.85),
|
Icon(Icons.launch, size: 16),
|
||||||
onTap: () {
|
],
|
||||||
launchUrlString(_article!.url);
|
).opacity(0.85),
|
||||||
},
|
onTap: () {
|
||||||
),
|
launchUrlString(_article!.url);
|
||||||
Gap(MediaQuery.of(context).padding.bottom),
|
},
|
||||||
],
|
),
|
||||||
).padding(horizontal: 12, vertical: 16),
|
Gap(MediaQuery.of(context).padding.bottom),
|
||||||
),
|
],
|
||||||
|
).padding(horizontal: 12, vertical: 16),
|
||||||
|
),
|
||||||
|
).center(),
|
||||||
)
|
)
|
||||||
else if (_article != null)
|
else if (_article != null)
|
||||||
Expanded(
|
Expanded(
|
||||||
|
@ -70,11 +70,16 @@ class _NewsScreenState extends State<NewsScreen> {
|
|||||||
sliver: SliverAppBar(
|
sliver: SliverAppBar(
|
||||||
leading: AutoAppBarLeading(),
|
leading: AutoAppBarLeading(),
|
||||||
title: Text('screenNews').tr(),
|
title: Text('screenNews').tr(),
|
||||||
|
floating: true,
|
||||||
|
snap: true,
|
||||||
bottom: TabBar(
|
bottom: TabBar(
|
||||||
isScrollable: true,
|
isScrollable: true,
|
||||||
tabs: [
|
tabs: [
|
||||||
Tab(child: Text('newsAllSources'.tr())),
|
Tab(child: Text('newsAllSources'.tr()).textColor(Theme.of(context).appBarTheme.foregroundColor)),
|
||||||
for (final source in _sources!) Tab(child: Text(source.label)),
|
for (final source in _sources!)
|
||||||
|
Tab(
|
||||||
|
child: Text(source.label).textColor(Theme.of(context).appBarTheme.foregroundColor),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -146,80 +151,87 @@ class _NewsArticleListWidgetState extends State<_NewsArticleListWidget> {
|
|||||||
return MediaQuery.removePadding(
|
return MediaQuery.removePadding(
|
||||||
context: context,
|
context: context,
|
||||||
removeTop: true,
|
removeTop: true,
|
||||||
child: RefreshIndicator(
|
child: Center(
|
||||||
onRefresh: _fetchArticles,
|
child: Container(
|
||||||
child: InfiniteList(
|
constraints: BoxConstraints(maxWidth: 640),
|
||||||
isLoading: _isBusy,
|
child: RefreshIndicator(
|
||||||
itemCount: _articles.length,
|
onRefresh: _fetchArticles,
|
||||||
hasReachedMax: _totalCount != null && _articles.length >= _totalCount!,
|
child: InfiniteList(
|
||||||
onFetchData: () {
|
isLoading: _isBusy,
|
||||||
_fetchArticles();
|
itemCount: _articles.length,
|
||||||
},
|
hasReachedMax: _totalCount != null && _articles.length >= _totalCount!,
|
||||||
itemBuilder: (context, index) {
|
onFetchData: () {
|
||||||
final article = _articles[index];
|
_fetchArticles();
|
||||||
|
},
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final article = _articles[index];
|
||||||
|
|
||||||
final baseUri = Uri.parse(article.url);
|
final baseUri = Uri.parse(article.url);
|
||||||
final baseUrl = '${baseUri.scheme}://${baseUri.host}';
|
final baseUrl = '${baseUri.scheme}://${baseUri.host}';
|
||||||
|
|
||||||
final htmlDescription = parse(article.description);
|
final htmlDescription = parse(article.description);
|
||||||
final date = article.publishedAt ?? article.createdAt;
|
final date = article.publishedAt ?? article.createdAt;
|
||||||
|
|
||||||
return Card(
|
return Card(
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
radius: 8,
|
radius: 8,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
GoRouter.of(context).pushNamed(
|
GoRouter.of(context).pushNamed(
|
||||||
'newsDetail',
|
'newsDetail',
|
||||||
pathParameters: {'hash': article.hash},
|
pathParameters: {'hash': article.hash},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
if (article.thumbnail.isNotEmpty && !article.thumbnail.endsWith('.svg'))
|
if (article.thumbnail.isNotEmpty && !article.thumbnail.endsWith('.svg'))
|
||||||
ClipRRect(
|
ClipRRect(
|
||||||
borderRadius: BorderRadius.only(
|
borderRadius: BorderRadius.only(
|
||||||
topRight: Radius.circular(8),
|
topRight: Radius.circular(8),
|
||||||
topLeft: Radius.circular(8),
|
topLeft: Radius.circular(8),
|
||||||
),
|
),
|
||||||
child: AspectRatio(
|
child: AspectRatio(
|
||||||
aspectRatio: 16 / 9,
|
aspectRatio: 16 / 9,
|
||||||
child: Container(
|
child: Container(
|
||||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||||
child: AutoResizeUniversalImage(
|
child: AutoResizeUniversalImage(
|
||||||
article.thumbnail.startsWith('http') ? article.thumbnail : '$baseUrl/${article.thumbnail}',
|
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(16),
|
const Gap(8),
|
||||||
Text(article.title).textStyle(Theme.of(context).textTheme.titleLarge!).padding(horizontal: 16),
|
Text(htmlDescription.children.map((ele) => ele.text.trim()).join())
|
||||||
const Gap(8),
|
.textStyle(Theme.of(context).textTheme.bodyMedium!)
|
||||||
Text(htmlDescription.children.map((ele) => ele.text.trim()).join())
|
.padding(horizontal: 16),
|
||||||
.textStyle(Theme.of(context).textTheme.bodyMedium!)
|
const Gap(8),
|
||||||
.padding(horizontal: 16),
|
Row(
|
||||||
const Gap(8),
|
spacing: 2,
|
||||||
Row(
|
children: [
|
||||||
spacing: 2,
|
Text(widget.allSources.where((x) => x.id == article.source).first.label)
|
||||||
children: [
|
.textStyle(Theme.of(context).textTheme.bodySmall!),
|
||||||
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),
|
||||||
],
|
],
|
||||||
).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),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -38,9 +38,11 @@ class _NotificationScreenState extends State<NotificationScreen> {
|
|||||||
|
|
||||||
static const Map<String, IconData> kNotificationTopicIcons = {
|
static const Map<String, IconData> kNotificationTopicIcons = {
|
||||||
'passport.security.alert': Symbols.gpp_maybe,
|
'passport.security.alert': Symbols.gpp_maybe,
|
||||||
|
'passport.security.otp': Symbols.password,
|
||||||
'interactive.subscription': Symbols.subscriptions,
|
'interactive.subscription': Symbols.subscriptions,
|
||||||
'interactive.feedback': Symbols.add_reaction,
|
'interactive.feedback': Symbols.add_reaction,
|
||||||
'messaging.callStart': Symbols.call_received,
|
'messaging.callStart': Symbols.call_received,
|
||||||
|
'wallet.transaction.new': Symbols.receipt,
|
||||||
};
|
};
|
||||||
|
|
||||||
Future<void> _fetchNotifications() async {
|
Future<void> _fetchNotifications() async {
|
||||||
|
@ -82,6 +82,48 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text('settingsAppearance').bold().fontSize(17).tr().padding(horizontal: 20, bottom: 4),
|
Text('settingsAppearance').bold().fontSize(17).tr().padding(horizontal: 20, bottom: 4),
|
||||||
|
ListTile(
|
||||||
|
title: Text('settingsDisplayLanguage').tr(),
|
||||||
|
subtitle: Text('settingsDisplayLanguageDescription').tr(),
|
||||||
|
contentPadding: const EdgeInsets.only(left: 24, right: 17),
|
||||||
|
leading: const Icon(Symbols.translate),
|
||||||
|
trailing: DropdownButtonHideUnderline(
|
||||||
|
child: DropdownButton2<Locale?>(
|
||||||
|
isExpanded: true,
|
||||||
|
items: [
|
||||||
|
...EasyLocalization.of(context)!.supportedLocales.mapIndexed((idx, ele) {
|
||||||
|
return DropdownMenuItem<Locale?>(
|
||||||
|
value: ele,
|
||||||
|
child: Text('${ele.languageCode}-${ele.countryCode}').fontSize(14),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
DropdownMenuItem<Locale?>(
|
||||||
|
value: null,
|
||||||
|
child: Text('settingsDisplayLanguageSystem').tr().fontSize(14),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
value: EasyLocalization.of(context)!.currentLocale,
|
||||||
|
onChanged: (Locale? value) {
|
||||||
|
if (value != null) {
|
||||||
|
EasyLocalization.of(context)!.setLocale(value);
|
||||||
|
} else {
|
||||||
|
EasyLocalization.of(context)!.resetLocale();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
buttonStyleData: const ButtonStyleData(
|
||||||
|
padding: EdgeInsets.symmetric(
|
||||||
|
horizontal: 16,
|
||||||
|
vertical: 5,
|
||||||
|
),
|
||||||
|
height: 40,
|
||||||
|
width: 160,
|
||||||
|
),
|
||||||
|
menuItemStyleData: const MenuItemStyleData(
|
||||||
|
height: 40,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
if (!kIsWeb)
|
if (!kIsWeb)
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text('settingsBackgroundImage').tr(),
|
title: Text('settingsBackgroundImage').tr(),
|
||||||
@ -147,30 +189,31 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
Color pickerColor = Color(_prefs.getInt(kAppColorSchemeStoreKey) ?? Colors.indigo.value);
|
Color pickerColor = Color(_prefs.getInt(kAppColorSchemeStoreKey) ?? Colors.indigo.value);
|
||||||
final color = await showDialog<Color?>(
|
final color = await showDialog<Color?>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => AlertDialog(
|
builder: (context) =>
|
||||||
content: SingleChildScrollView(
|
AlertDialog(
|
||||||
child: ColorPicker(
|
content: SingleChildScrollView(
|
||||||
pickerColor: pickerColor,
|
child: ColorPicker(
|
||||||
onColorChanged: (color) => pickerColor = color,
|
pickerColor: pickerColor,
|
||||||
enableAlpha: false,
|
onColorChanged: (color) => pickerColor = color,
|
||||||
hexInputBar: true,
|
enableAlpha: false,
|
||||||
|
hexInputBar: true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
actions: <Widget>[
|
||||||
|
TextButton(
|
||||||
|
child: const Text('dialogDismiss').tr(),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
child: const Text('dialogConfirm').tr(),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop(pickerColor);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
|
||||||
actions: <Widget>[
|
|
||||||
TextButton(
|
|
||||||
child: const Text('dialogDismiss').tr(),
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
TextButton(
|
|
||||||
child: const Text('dialogConfirm').tr(),
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.of(context).pop(pickerColor);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (color == null || !context.mounted) return;
|
if (color == null || !context.mounted) return;
|
||||||
@ -206,11 +249,13 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
value: _prefs.getInt(kAppColorSchemeStoreKey) == null
|
value: _prefs.getInt(kAppColorSchemeStoreKey) == null
|
||||||
? 1
|
? 1
|
||||||
: kColorSchemes.values
|
: kColorSchemes.values
|
||||||
.toList()
|
.toList()
|
||||||
.indexWhere((ele) => ele.value == _prefs.getInt(kAppColorSchemeStoreKey)),
|
.indexWhere((ele) => ele.value == _prefs.getInt(kAppColorSchemeStoreKey)),
|
||||||
onChanged: (int? value) {
|
onChanged: (int? value) {
|
||||||
if (value != null && value != -1) {
|
if (value != null && value != -1) {
|
||||||
_prefs.setInt(kAppColorSchemeStoreKey, kColorSchemes.values.elementAt(value).value);
|
_prefs.setInt(kAppColorSchemeStoreKey, kColorSchemes.values
|
||||||
|
.elementAt(value)
|
||||||
|
.value);
|
||||||
final th = context.read<ThemeProvider>();
|
final th = context.read<ThemeProvider>();
|
||||||
th.reloadTheme(seedColorOverride: kColorSchemes.values.elementAt(value));
|
th.reloadTheme(seedColorOverride: kColorSchemes.values.elementAt(value));
|
||||||
setState(() {});
|
setState(() {});
|
||||||
@ -342,7 +387,8 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
('Custom', _serverUrlController.text),
|
('Custom', _serverUrlController.text),
|
||||||
]
|
]
|
||||||
.map(
|
.map(
|
||||||
(item) => DropdownMenuItem<String>(
|
(item) =>
|
||||||
|
DropdownMenuItem<String>(
|
||||||
value: item.$2,
|
value: item.$2,
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.max,
|
mainAxisSize: MainAxisSize.max,
|
||||||
@ -354,7 +400,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.toList(),
|
.toList(),
|
||||||
value: _serverUrlController.text,
|
value: _serverUrlController.text,
|
||||||
onChanged: (String? value) {
|
onChanged: (String? value) {
|
||||||
@ -409,11 +455,12 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
isExpanded: true,
|
isExpanded: true,
|
||||||
items: kImageQualityLevel.entries
|
items: kImageQualityLevel.entries
|
||||||
.map(
|
.map(
|
||||||
(item) => DropdownMenuItem<FilterQuality>(
|
(item) =>
|
||||||
|
DropdownMenuItem<FilterQuality>(
|
||||||
value: item.value,
|
value: item.value,
|
||||||
child: Text(item.key).tr().fontSize(14),
|
child: Text(item.key).tr().fontSize(14),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.toList(),
|
.toList(),
|
||||||
onChanged: (FilterQuality? value) {
|
onChanged: (FilterQuality? value) {
|
||||||
if (value == null) return;
|
if (value == null) return;
|
||||||
|
282
lib/screens/wallet.dart
Normal file
282
lib/screens/wallet.dart
Normal file
@ -0,0 +1,282 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:developer';
|
||||||
|
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:gap/gap.dart';
|
||||||
|
import 'package:material_symbols_icons/material_symbols_icons.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
import 'package:surface/providers/sn_network.dart';
|
||||||
|
import 'package:surface/types/wallet.dart';
|
||||||
|
import 'package:surface/widgets/dialog.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';
|
||||||
|
|
||||||
|
class WalletScreen extends StatefulWidget {
|
||||||
|
const WalletScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<WalletScreen> createState() => _WalletScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _WalletScreenState extends State<WalletScreen> {
|
||||||
|
bool _isBusy = false;
|
||||||
|
SnWallet? _wallet;
|
||||||
|
|
||||||
|
Future<void> _fetchWallet() async {
|
||||||
|
try {
|
||||||
|
setState(() => _isBusy = true);
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
final resp = await sn.client.get('/cgi/wa/wallets/me');
|
||||||
|
_wallet = SnWallet.fromJson(resp.data);
|
||||||
|
} catch (err) {
|
||||||
|
if (!mounted) return;
|
||||||
|
context.showErrorDialog(err);
|
||||||
|
} finally {
|
||||||
|
setState(() => _isBusy = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_fetchWallet();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return AppScaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
leading: PageBackButton(),
|
||||||
|
title: Text('screenAccountWallet').tr(),
|
||||||
|
),
|
||||||
|
body: Column(
|
||||||
|
children: [
|
||||||
|
LoadingIndicator(isActive: _isBusy),
|
||||||
|
if (_wallet == null)
|
||||||
|
Expanded(
|
||||||
|
child: _CreateWalletWidget(
|
||||||
|
onCreate: () {
|
||||||
|
_fetchWallet();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
else
|
||||||
|
Card(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
CircleAvatar(
|
||||||
|
radius: 28,
|
||||||
|
child: Icon(Symbols.wallet, size: 28),
|
||||||
|
),
|
||||||
|
const Gap(12),
|
||||||
|
SizedBox(width: double.infinity),
|
||||||
|
Text(
|
||||||
|
NumberFormat.compactCurrency(
|
||||||
|
locale: EasyLocalization.of(context)!.currentLocale.toString(),
|
||||||
|
symbol: '${'walletCurrencyShort'.tr()} ',
|
||||||
|
decimalDigits: 2,
|
||||||
|
).format(double.parse(_wallet!.balance)),
|
||||||
|
style: Theme.of(context).textTheme.titleLarge,
|
||||||
|
),
|
||||||
|
Text('walletCurrency'.plural(double.parse(_wallet!.balance))),
|
||||||
|
],
|
||||||
|
).padding(horizontal: 20, vertical: 24),
|
||||||
|
).padding(horizontal: 8, top: 16, bottom: 4),
|
||||||
|
if (_wallet != null) Expanded(child: _WalletTransactionList(myself: _wallet!)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _WalletTransactionList extends StatefulWidget {
|
||||||
|
final SnWallet myself;
|
||||||
|
|
||||||
|
const _WalletTransactionList({required this.myself});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<_WalletTransactionList> createState() => _WalletTransactionListState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _WalletTransactionListState extends State<_WalletTransactionList> {
|
||||||
|
bool _isBusy = false;
|
||||||
|
int? _totalCount;
|
||||||
|
final List<SnTransaction> _transactions = List.empty(growable: true);
|
||||||
|
|
||||||
|
Future<void> _fetchTransactions() async {
|
||||||
|
try {
|
||||||
|
setState(() => _isBusy = true);
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
final resp = await sn.client.get('/cgi/wa/transactions/me', queryParameters: {
|
||||||
|
'take': 10,
|
||||||
|
'offset': _transactions.length,
|
||||||
|
});
|
||||||
|
_totalCount = resp.data['count'];
|
||||||
|
_transactions.addAll(
|
||||||
|
resp.data['data']?.map((e) => SnTransaction.fromJson(e)).cast<SnTransaction>() ?? [],
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
if (!mounted) return;
|
||||||
|
context.showErrorDialog(err);
|
||||||
|
} finally {
|
||||||
|
setState(() => _isBusy = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_fetchTransactions();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return MediaQuery.removePadding(
|
||||||
|
context: context,
|
||||||
|
removeTop: true,
|
||||||
|
child: RefreshIndicator(
|
||||||
|
onRefresh: _fetchTransactions,
|
||||||
|
child: InfiniteList(
|
||||||
|
itemCount: _transactions.length,
|
||||||
|
isLoading: _isBusy,
|
||||||
|
hasReachedMax: _totalCount != null && _transactions.length >= _totalCount!,
|
||||||
|
onFetchData: () {
|
||||||
|
_fetchTransactions();
|
||||||
|
},
|
||||||
|
itemBuilder: (context, idx) {
|
||||||
|
final ele = _transactions[idx];
|
||||||
|
final isIncoming = ele.payeeId == widget.myself.id;
|
||||||
|
return ListTile(
|
||||||
|
leading: isIncoming ? const Icon(Symbols.call_received) : const Icon(Symbols.call_made),
|
||||||
|
title: Text(
|
||||||
|
'${isIncoming ? '+' : '-'}${ele.amount} ${'walletCurrencyShort'.tr()}',
|
||||||
|
style: TextStyle(color: isIncoming ? Colors.green : Colors.red),
|
||||||
|
),
|
||||||
|
subtitle: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(ele.remark),
|
||||||
|
const Gap(2),
|
||||||
|
Text(
|
||||||
|
DateFormat(
|
||||||
|
null,
|
||||||
|
EasyLocalization.of(context)!.currentLocale.toString(),
|
||||||
|
).format(ele.createdAt),
|
||||||
|
style: Theme.of(context).textTheme.labelSmall,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CreateWalletWidget extends StatefulWidget {
|
||||||
|
final Function()? onCreate;
|
||||||
|
|
||||||
|
const _CreateWalletWidget({required this.onCreate});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<_CreateWalletWidget> createState() => _CreateWalletWidgetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CreateWalletWidgetState extends State<_CreateWalletWidget> {
|
||||||
|
bool _isBusy = false;
|
||||||
|
|
||||||
|
Future<void> _createWallet() async {
|
||||||
|
final TextEditingController passwordController = TextEditingController();
|
||||||
|
final password = await showDialog<String?>(
|
||||||
|
context: context,
|
||||||
|
builder: (ctx) => AlertDialog(
|
||||||
|
title: Text('walletCreate').tr(),
|
||||||
|
content: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text('walletCreatePassword').tr(),
|
||||||
|
const Gap(8),
|
||||||
|
TextField(
|
||||||
|
autofocus: true,
|
||||||
|
obscureText: true,
|
||||||
|
controller: passwordController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'fieldPassword'.tr(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.of(ctx).pop(),
|
||||||
|
child: Text('cancel').tr(),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(ctx).pop(passwordController.text);
|
||||||
|
},
|
||||||
|
child: Text('next').tr(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
passwordController.dispose();
|
||||||
|
});
|
||||||
|
if (password == null || password.isEmpty) return;
|
||||||
|
if (!mounted) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
setState(() => _isBusy = true);
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
await sn.client.post('/cgi/wa/wallets/me', data: {
|
||||||
|
'password': password,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
if (!mounted) return;
|
||||||
|
context.showErrorDialog(err);
|
||||||
|
} finally {
|
||||||
|
setState(() => _isBusy = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Center(
|
||||||
|
child: Container(
|
||||||
|
constraints: const BoxConstraints(maxWidth: 380),
|
||||||
|
child: Card(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
CircleAvatar(
|
||||||
|
radius: 28,
|
||||||
|
child: Icon(Symbols.add, size: 28),
|
||||||
|
),
|
||||||
|
const Gap(12),
|
||||||
|
Text('walletCreate', style: Theme.of(context).textTheme.titleLarge).tr(),
|
||||||
|
Text('walletCreateSubtitle', style: Theme.of(context).textTheme.bodyMedium).tr(),
|
||||||
|
const Gap(8),
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.centerRight,
|
||||||
|
child: TextButton(
|
||||||
|
onPressed: _isBusy ? null : () => _createWallet(),
|
||||||
|
child: Text('next').tr(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).padding(horizontal: 20, vertical: 24),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -15,8 +15,8 @@ class SnAccount with _$SnAccount {
|
|||||||
required DateTime? deletedAt,
|
required DateTime? deletedAt,
|
||||||
required DateTime? confirmedAt,
|
required DateTime? confirmedAt,
|
||||||
required List<SnAccountContact>? contacts,
|
required List<SnAccountContact>? contacts,
|
||||||
required String avatar,
|
@Default("") String avatar,
|
||||||
required String banner,
|
@Default("") String banner,
|
||||||
required String description,
|
required String description,
|
||||||
required String name,
|
required String name,
|
||||||
required String nick,
|
required String nick,
|
||||||
|
@ -367,8 +367,8 @@ class _$SnAccountImpl extends _SnAccount {
|
|||||||
required this.deletedAt,
|
required this.deletedAt,
|
||||||
required this.confirmedAt,
|
required this.confirmedAt,
|
||||||
required final List<SnAccountContact>? contacts,
|
required final List<SnAccountContact>? contacts,
|
||||||
required this.avatar,
|
this.avatar = "",
|
||||||
required this.banner,
|
this.banner = "",
|
||||||
required this.description,
|
required this.description,
|
||||||
required this.name,
|
required this.name,
|
||||||
required this.nick,
|
required this.nick,
|
||||||
@ -410,8 +410,10 @@ class _$SnAccountImpl extends _SnAccount {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@JsonKey()
|
||||||
final String avatar;
|
final String avatar;
|
||||||
@override
|
@override
|
||||||
|
@JsonKey()
|
||||||
final String banner;
|
final String banner;
|
||||||
@override
|
@override
|
||||||
final String description;
|
final String description;
|
||||||
@ -540,8 +542,8 @@ abstract class _SnAccount extends SnAccount {
|
|||||||
required final DateTime? deletedAt,
|
required final DateTime? deletedAt,
|
||||||
required final DateTime? confirmedAt,
|
required final DateTime? confirmedAt,
|
||||||
required final List<SnAccountContact>? contacts,
|
required final List<SnAccountContact>? contacts,
|
||||||
required final String avatar,
|
final String avatar,
|
||||||
required final String banner,
|
final String banner,
|
||||||
required final String description,
|
required final String description,
|
||||||
required final String name,
|
required final String name,
|
||||||
required final String nick,
|
required final String nick,
|
||||||
|
@ -20,8 +20,8 @@ _$SnAccountImpl _$$SnAccountImplFromJson(Map<String, dynamic> json) =>
|
|||||||
contacts: (json['contacts'] as List<dynamic>?)
|
contacts: (json['contacts'] as List<dynamic>?)
|
||||||
?.map((e) => SnAccountContact.fromJson(e as Map<String, dynamic>))
|
?.map((e) => SnAccountContact.fromJson(e as Map<String, dynamic>))
|
||||||
.toList(),
|
.toList(),
|
||||||
avatar: json['avatar'] as String,
|
avatar: json['avatar'] as String? ?? "",
|
||||||
banner: json['banner'] as String,
|
banner: json['banner'] as String? ?? "",
|
||||||
description: json['description'] as String,
|
description: json['description'] as String,
|
||||||
name: json['name'] as String,
|
name: json['name'] as String,
|
||||||
nick: json['nick'] as String,
|
nick: json['nick'] as String,
|
||||||
|
@ -16,6 +16,7 @@ class SnCheckInRecord with _$SnCheckInRecord {
|
|||||||
required DateTime? deletedAt,
|
required DateTime? deletedAt,
|
||||||
required int resultTier,
|
required int resultTier,
|
||||||
required int resultExperience,
|
required int resultExperience,
|
||||||
|
required double resultCoin,
|
||||||
required List<int> resultModifiers,
|
required List<int> resultModifiers,
|
||||||
required int accountId,
|
required int accountId,
|
||||||
}) = _SnCheckInRecord;
|
}) = _SnCheckInRecord;
|
||||||
|
@ -26,6 +26,7 @@ mixin _$SnCheckInRecord {
|
|||||||
DateTime? get deletedAt => throw _privateConstructorUsedError;
|
DateTime? get deletedAt => throw _privateConstructorUsedError;
|
||||||
int get resultTier => throw _privateConstructorUsedError;
|
int get resultTier => throw _privateConstructorUsedError;
|
||||||
int get resultExperience => throw _privateConstructorUsedError;
|
int get resultExperience => throw _privateConstructorUsedError;
|
||||||
|
double get resultCoin => throw _privateConstructorUsedError;
|
||||||
List<int> get resultModifiers => throw _privateConstructorUsedError;
|
List<int> get resultModifiers => throw _privateConstructorUsedError;
|
||||||
int get accountId => throw _privateConstructorUsedError;
|
int get accountId => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
@ -52,6 +53,7 @@ abstract class $SnCheckInRecordCopyWith<$Res> {
|
|||||||
DateTime? deletedAt,
|
DateTime? deletedAt,
|
||||||
int resultTier,
|
int resultTier,
|
||||||
int resultExperience,
|
int resultExperience,
|
||||||
|
double resultCoin,
|
||||||
List<int> resultModifiers,
|
List<int> resultModifiers,
|
||||||
int accountId});
|
int accountId});
|
||||||
}
|
}
|
||||||
@ -77,6 +79,7 @@ class _$SnCheckInRecordCopyWithImpl<$Res, $Val extends SnCheckInRecord>
|
|||||||
Object? deletedAt = freezed,
|
Object? deletedAt = freezed,
|
||||||
Object? resultTier = null,
|
Object? resultTier = null,
|
||||||
Object? resultExperience = null,
|
Object? resultExperience = null,
|
||||||
|
Object? resultCoin = null,
|
||||||
Object? resultModifiers = null,
|
Object? resultModifiers = null,
|
||||||
Object? accountId = null,
|
Object? accountId = null,
|
||||||
}) {
|
}) {
|
||||||
@ -105,6 +108,10 @@ class _$SnCheckInRecordCopyWithImpl<$Res, $Val extends SnCheckInRecord>
|
|||||||
? _value.resultExperience
|
? _value.resultExperience
|
||||||
: resultExperience // ignore: cast_nullable_to_non_nullable
|
: resultExperience // ignore: cast_nullable_to_non_nullable
|
||||||
as int,
|
as int,
|
||||||
|
resultCoin: null == resultCoin
|
||||||
|
? _value.resultCoin
|
||||||
|
: resultCoin // ignore: cast_nullable_to_non_nullable
|
||||||
|
as double,
|
||||||
resultModifiers: null == resultModifiers
|
resultModifiers: null == resultModifiers
|
||||||
? _value.resultModifiers
|
? _value.resultModifiers
|
||||||
: resultModifiers // ignore: cast_nullable_to_non_nullable
|
: resultModifiers // ignore: cast_nullable_to_non_nullable
|
||||||
@ -132,6 +139,7 @@ abstract class _$$SnCheckInRecordImplCopyWith<$Res>
|
|||||||
DateTime? deletedAt,
|
DateTime? deletedAt,
|
||||||
int resultTier,
|
int resultTier,
|
||||||
int resultExperience,
|
int resultExperience,
|
||||||
|
double resultCoin,
|
||||||
List<int> resultModifiers,
|
List<int> resultModifiers,
|
||||||
int accountId});
|
int accountId});
|
||||||
}
|
}
|
||||||
@ -155,6 +163,7 @@ class __$$SnCheckInRecordImplCopyWithImpl<$Res>
|
|||||||
Object? deletedAt = freezed,
|
Object? deletedAt = freezed,
|
||||||
Object? resultTier = null,
|
Object? resultTier = null,
|
||||||
Object? resultExperience = null,
|
Object? resultExperience = null,
|
||||||
|
Object? resultCoin = null,
|
||||||
Object? resultModifiers = null,
|
Object? resultModifiers = null,
|
||||||
Object? accountId = null,
|
Object? accountId = null,
|
||||||
}) {
|
}) {
|
||||||
@ -183,6 +192,10 @@ class __$$SnCheckInRecordImplCopyWithImpl<$Res>
|
|||||||
? _value.resultExperience
|
? _value.resultExperience
|
||||||
: resultExperience // ignore: cast_nullable_to_non_nullable
|
: resultExperience // ignore: cast_nullable_to_non_nullable
|
||||||
as int,
|
as int,
|
||||||
|
resultCoin: null == resultCoin
|
||||||
|
? _value.resultCoin
|
||||||
|
: resultCoin // ignore: cast_nullable_to_non_nullable
|
||||||
|
as double,
|
||||||
resultModifiers: null == resultModifiers
|
resultModifiers: null == resultModifiers
|
||||||
? _value._resultModifiers
|
? _value._resultModifiers
|
||||||
: resultModifiers // ignore: cast_nullable_to_non_nullable
|
: resultModifiers // ignore: cast_nullable_to_non_nullable
|
||||||
@ -205,6 +218,7 @@ class _$SnCheckInRecordImpl extends _SnCheckInRecord {
|
|||||||
required this.deletedAt,
|
required this.deletedAt,
|
||||||
required this.resultTier,
|
required this.resultTier,
|
||||||
required this.resultExperience,
|
required this.resultExperience,
|
||||||
|
required this.resultCoin,
|
||||||
required final List<int> resultModifiers,
|
required final List<int> resultModifiers,
|
||||||
required this.accountId})
|
required this.accountId})
|
||||||
: _resultModifiers = resultModifiers,
|
: _resultModifiers = resultModifiers,
|
||||||
@ -225,6 +239,8 @@ class _$SnCheckInRecordImpl extends _SnCheckInRecord {
|
|||||||
final int resultTier;
|
final int resultTier;
|
||||||
@override
|
@override
|
||||||
final int resultExperience;
|
final int resultExperience;
|
||||||
|
@override
|
||||||
|
final double resultCoin;
|
||||||
final List<int> _resultModifiers;
|
final List<int> _resultModifiers;
|
||||||
@override
|
@override
|
||||||
List<int> get resultModifiers {
|
List<int> get resultModifiers {
|
||||||
@ -238,7 +254,7 @@ class _$SnCheckInRecordImpl extends _SnCheckInRecord {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'SnCheckInRecord(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, resultTier: $resultTier, resultExperience: $resultExperience, resultModifiers: $resultModifiers, accountId: $accountId)';
|
return 'SnCheckInRecord(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, resultTier: $resultTier, resultExperience: $resultExperience, resultCoin: $resultCoin, resultModifiers: $resultModifiers, accountId: $accountId)';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -257,6 +273,8 @@ class _$SnCheckInRecordImpl extends _SnCheckInRecord {
|
|||||||
other.resultTier == resultTier) &&
|
other.resultTier == resultTier) &&
|
||||||
(identical(other.resultExperience, resultExperience) ||
|
(identical(other.resultExperience, resultExperience) ||
|
||||||
other.resultExperience == resultExperience) &&
|
other.resultExperience == resultExperience) &&
|
||||||
|
(identical(other.resultCoin, resultCoin) ||
|
||||||
|
other.resultCoin == resultCoin) &&
|
||||||
const DeepCollectionEquality()
|
const DeepCollectionEquality()
|
||||||
.equals(other._resultModifiers, _resultModifiers) &&
|
.equals(other._resultModifiers, _resultModifiers) &&
|
||||||
(identical(other.accountId, accountId) ||
|
(identical(other.accountId, accountId) ||
|
||||||
@ -273,6 +291,7 @@ class _$SnCheckInRecordImpl extends _SnCheckInRecord {
|
|||||||
deletedAt,
|
deletedAt,
|
||||||
resultTier,
|
resultTier,
|
||||||
resultExperience,
|
resultExperience,
|
||||||
|
resultCoin,
|
||||||
const DeepCollectionEquality().hash(_resultModifiers),
|
const DeepCollectionEquality().hash(_resultModifiers),
|
||||||
accountId);
|
accountId);
|
||||||
|
|
||||||
@ -301,6 +320,7 @@ abstract class _SnCheckInRecord extends SnCheckInRecord {
|
|||||||
required final DateTime? deletedAt,
|
required final DateTime? deletedAt,
|
||||||
required final int resultTier,
|
required final int resultTier,
|
||||||
required final int resultExperience,
|
required final int resultExperience,
|
||||||
|
required final double resultCoin,
|
||||||
required final List<int> resultModifiers,
|
required final List<int> resultModifiers,
|
||||||
required final int accountId}) = _$SnCheckInRecordImpl;
|
required final int accountId}) = _$SnCheckInRecordImpl;
|
||||||
const _SnCheckInRecord._() : super._();
|
const _SnCheckInRecord._() : super._();
|
||||||
@ -321,6 +341,8 @@ abstract class _SnCheckInRecord extends SnCheckInRecord {
|
|||||||
@override
|
@override
|
||||||
int get resultExperience;
|
int get resultExperience;
|
||||||
@override
|
@override
|
||||||
|
double get resultCoin;
|
||||||
|
@override
|
||||||
List<int> get resultModifiers;
|
List<int> get resultModifiers;
|
||||||
@override
|
@override
|
||||||
int get accountId;
|
int get accountId;
|
||||||
|
@ -17,6 +17,7 @@ _$SnCheckInRecordImpl _$$SnCheckInRecordImplFromJson(
|
|||||||
: DateTime.parse(json['deleted_at'] as String),
|
: DateTime.parse(json['deleted_at'] as String),
|
||||||
resultTier: (json['result_tier'] as num).toInt(),
|
resultTier: (json['result_tier'] as num).toInt(),
|
||||||
resultExperience: (json['result_experience'] as num).toInt(),
|
resultExperience: (json['result_experience'] as num).toInt(),
|
||||||
|
resultCoin: (json['result_coin'] as num).toDouble(),
|
||||||
resultModifiers: (json['result_modifiers'] as List<dynamic>)
|
resultModifiers: (json['result_modifiers'] as List<dynamic>)
|
||||||
.map((e) => (e as num).toInt())
|
.map((e) => (e as num).toInt())
|
||||||
.toList(),
|
.toList(),
|
||||||
@ -32,6 +33,7 @@ Map<String, dynamic> _$$SnCheckInRecordImplToJson(
|
|||||||
'deleted_at': instance.deletedAt?.toIso8601String(),
|
'deleted_at': instance.deletedAt?.toIso8601String(),
|
||||||
'result_tier': instance.resultTier,
|
'result_tier': instance.resultTier,
|
||||||
'result_experience': instance.resultExperience,
|
'result_experience': instance.resultExperience,
|
||||||
|
'result_coin': instance.resultCoin,
|
||||||
'result_modifiers': instance.resultModifiers,
|
'result_modifiers': instance.resultModifiers,
|
||||||
'account_id': instance.accountId,
|
'account_id': instance.accountId,
|
||||||
};
|
};
|
||||||
|
37
lib/types/wallet.dart
Normal file
37
lib/types/wallet.dart
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
|
part 'wallet.freezed.dart';
|
||||||
|
part 'wallet.g.dart';
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class SnWallet with _$SnWallet {
|
||||||
|
const factory SnWallet({
|
||||||
|
required int id,
|
||||||
|
required DateTime createdAt,
|
||||||
|
required DateTime updatedAt,
|
||||||
|
required DateTime? deletedAt,
|
||||||
|
required String balance,
|
||||||
|
required String password,
|
||||||
|
required int accountId,
|
||||||
|
}) = _SnWallet;
|
||||||
|
|
||||||
|
factory SnWallet.fromJson(Map<String, dynamic> json) => _$SnWalletFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class SnTransaction with _$SnTransaction {
|
||||||
|
const factory SnTransaction({
|
||||||
|
required int id,
|
||||||
|
required DateTime createdAt,
|
||||||
|
required DateTime updatedAt,
|
||||||
|
required DateTime? deletedAt,
|
||||||
|
required String remark,
|
||||||
|
required String amount,
|
||||||
|
required SnWallet? payer,
|
||||||
|
required SnWallet? payee,
|
||||||
|
required int? payerId,
|
||||||
|
required int? payeeId,
|
||||||
|
}) = _SnTransaction;
|
||||||
|
|
||||||
|
factory SnTransaction.fromJson(Map<String, dynamic> json) => _$SnTransactionFromJson(json);
|
||||||
|
}
|
666
lib/types/wallet.freezed.dart
Normal file
666
lib/types/wallet.freezed.dart
Normal file
@ -0,0 +1,666 @@
|
|||||||
|
// 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 'wallet.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');
|
||||||
|
|
||||||
|
SnWallet _$SnWalletFromJson(Map<String, dynamic> json) {
|
||||||
|
return _SnWallet.fromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$SnWallet {
|
||||||
|
int get id => throw _privateConstructorUsedError;
|
||||||
|
DateTime get createdAt => throw _privateConstructorUsedError;
|
||||||
|
DateTime get updatedAt => throw _privateConstructorUsedError;
|
||||||
|
DateTime? get deletedAt => throw _privateConstructorUsedError;
|
||||||
|
String get balance => throw _privateConstructorUsedError;
|
||||||
|
String get password => throw _privateConstructorUsedError;
|
||||||
|
int get accountId => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
|
/// Serializes this SnWallet to a JSON map.
|
||||||
|
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
|
/// Create a copy of SnWallet
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
$SnWalletCopyWith<SnWallet> get copyWith =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class $SnWalletCopyWith<$Res> {
|
||||||
|
factory $SnWalletCopyWith(SnWallet value, $Res Function(SnWallet) then) =
|
||||||
|
_$SnWalletCopyWithImpl<$Res, SnWallet>;
|
||||||
|
@useResult
|
||||||
|
$Res call(
|
||||||
|
{int id,
|
||||||
|
DateTime createdAt,
|
||||||
|
DateTime updatedAt,
|
||||||
|
DateTime? deletedAt,
|
||||||
|
String balance,
|
||||||
|
String password,
|
||||||
|
int accountId});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class _$SnWalletCopyWithImpl<$Res, $Val extends SnWallet>
|
||||||
|
implements $SnWalletCopyWith<$Res> {
|
||||||
|
_$SnWalletCopyWithImpl(this._value, this._then);
|
||||||
|
|
||||||
|
// ignore: unused_field
|
||||||
|
final $Val _value;
|
||||||
|
// ignore: unused_field
|
||||||
|
final $Res Function($Val) _then;
|
||||||
|
|
||||||
|
/// Create a copy of SnWallet
|
||||||
|
/// 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? balance = null,
|
||||||
|
Object? password = null,
|
||||||
|
Object? accountId = null,
|
||||||
|
}) {
|
||||||
|
return _then(_value.copyWith(
|
||||||
|
id: null == id
|
||||||
|
? _value.id
|
||||||
|
: id // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,
|
||||||
|
createdAt: null == createdAt
|
||||||
|
? _value.createdAt
|
||||||
|
: createdAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,
|
||||||
|
updatedAt: null == updatedAt
|
||||||
|
? _value.updatedAt
|
||||||
|
: updatedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,
|
||||||
|
deletedAt: freezed == deletedAt
|
||||||
|
? _value.deletedAt
|
||||||
|
: deletedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime?,
|
||||||
|
balance: null == balance
|
||||||
|
? _value.balance
|
||||||
|
: balance // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
password: null == password
|
||||||
|
? _value.password
|
||||||
|
: password // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
accountId: null == accountId
|
||||||
|
? _value.accountId
|
||||||
|
: accountId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,
|
||||||
|
) as $Val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class _$$SnWalletImplCopyWith<$Res>
|
||||||
|
implements $SnWalletCopyWith<$Res> {
|
||||||
|
factory _$$SnWalletImplCopyWith(
|
||||||
|
_$SnWalletImpl value, $Res Function(_$SnWalletImpl) then) =
|
||||||
|
__$$SnWalletImplCopyWithImpl<$Res>;
|
||||||
|
@override
|
||||||
|
@useResult
|
||||||
|
$Res call(
|
||||||
|
{int id,
|
||||||
|
DateTime createdAt,
|
||||||
|
DateTime updatedAt,
|
||||||
|
DateTime? deletedAt,
|
||||||
|
String balance,
|
||||||
|
String password,
|
||||||
|
int accountId});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class __$$SnWalletImplCopyWithImpl<$Res>
|
||||||
|
extends _$SnWalletCopyWithImpl<$Res, _$SnWalletImpl>
|
||||||
|
implements _$$SnWalletImplCopyWith<$Res> {
|
||||||
|
__$$SnWalletImplCopyWithImpl(
|
||||||
|
_$SnWalletImpl _value, $Res Function(_$SnWalletImpl) _then)
|
||||||
|
: super(_value, _then);
|
||||||
|
|
||||||
|
/// Create a copy of SnWallet
|
||||||
|
/// 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? balance = null,
|
||||||
|
Object? password = null,
|
||||||
|
Object? accountId = null,
|
||||||
|
}) {
|
||||||
|
return _then(_$SnWalletImpl(
|
||||||
|
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 DateTime?,
|
||||||
|
balance: null == balance
|
||||||
|
? _value.balance
|
||||||
|
: balance // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
password: null == password
|
||||||
|
? _value.password
|
||||||
|
: password // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
accountId: null == accountId
|
||||||
|
? _value.accountId
|
||||||
|
: accountId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
@JsonSerializable()
|
||||||
|
class _$SnWalletImpl implements _SnWallet {
|
||||||
|
const _$SnWalletImpl(
|
||||||
|
{required this.id,
|
||||||
|
required this.createdAt,
|
||||||
|
required this.updatedAt,
|
||||||
|
required this.deletedAt,
|
||||||
|
required this.balance,
|
||||||
|
required this.password,
|
||||||
|
required this.accountId});
|
||||||
|
|
||||||
|
factory _$SnWalletImpl.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$$SnWalletImplFromJson(json);
|
||||||
|
|
||||||
|
@override
|
||||||
|
final int id;
|
||||||
|
@override
|
||||||
|
final DateTime createdAt;
|
||||||
|
@override
|
||||||
|
final DateTime updatedAt;
|
||||||
|
@override
|
||||||
|
final DateTime? deletedAt;
|
||||||
|
@override
|
||||||
|
final String balance;
|
||||||
|
@override
|
||||||
|
final String password;
|
||||||
|
@override
|
||||||
|
final int accountId;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'SnWallet(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, balance: $balance, password: $password, accountId: $accountId)';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) ||
|
||||||
|
(other.runtimeType == runtimeType &&
|
||||||
|
other is _$SnWalletImpl &&
|
||||||
|
(identical(other.id, id) || other.id == id) &&
|
||||||
|
(identical(other.createdAt, createdAt) ||
|
||||||
|
other.createdAt == createdAt) &&
|
||||||
|
(identical(other.updatedAt, updatedAt) ||
|
||||||
|
other.updatedAt == updatedAt) &&
|
||||||
|
(identical(other.deletedAt, deletedAt) ||
|
||||||
|
other.deletedAt == deletedAt) &&
|
||||||
|
(identical(other.balance, balance) || other.balance == balance) &&
|
||||||
|
(identical(other.password, password) ||
|
||||||
|
other.password == password) &&
|
||||||
|
(identical(other.accountId, accountId) ||
|
||||||
|
other.accountId == accountId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType, id, createdAt, updatedAt,
|
||||||
|
deletedAt, balance, password, accountId);
|
||||||
|
|
||||||
|
/// Create a copy of SnWallet
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$$SnWalletImplCopyWith<_$SnWalletImpl> get copyWith =>
|
||||||
|
__$$SnWalletImplCopyWithImpl<_$SnWalletImpl>(this, _$identity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return _$$SnWalletImplToJson(
|
||||||
|
this,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class _SnWallet implements SnWallet {
|
||||||
|
const factory _SnWallet(
|
||||||
|
{required final int id,
|
||||||
|
required final DateTime createdAt,
|
||||||
|
required final DateTime updatedAt,
|
||||||
|
required final DateTime? deletedAt,
|
||||||
|
required final String balance,
|
||||||
|
required final String password,
|
||||||
|
required final int accountId}) = _$SnWalletImpl;
|
||||||
|
|
||||||
|
factory _SnWallet.fromJson(Map<String, dynamic> json) =
|
||||||
|
_$SnWalletImpl.fromJson;
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get id;
|
||||||
|
@override
|
||||||
|
DateTime get createdAt;
|
||||||
|
@override
|
||||||
|
DateTime get updatedAt;
|
||||||
|
@override
|
||||||
|
DateTime? get deletedAt;
|
||||||
|
@override
|
||||||
|
String get balance;
|
||||||
|
@override
|
||||||
|
String get password;
|
||||||
|
@override
|
||||||
|
int get accountId;
|
||||||
|
|
||||||
|
/// Create a copy of SnWallet
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
_$$SnWalletImplCopyWith<_$SnWalletImpl> get copyWith =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
}
|
||||||
|
|
||||||
|
SnTransaction _$SnTransactionFromJson(Map<String, dynamic> json) {
|
||||||
|
return _SnTransaction.fromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$SnTransaction {
|
||||||
|
int get id => throw _privateConstructorUsedError;
|
||||||
|
DateTime get createdAt => throw _privateConstructorUsedError;
|
||||||
|
DateTime get updatedAt => throw _privateConstructorUsedError;
|
||||||
|
DateTime? get deletedAt => throw _privateConstructorUsedError;
|
||||||
|
String get remark => throw _privateConstructorUsedError;
|
||||||
|
String get amount => throw _privateConstructorUsedError;
|
||||||
|
SnWallet? get payer => throw _privateConstructorUsedError;
|
||||||
|
SnWallet? get payee => throw _privateConstructorUsedError;
|
||||||
|
int? get payerId => throw _privateConstructorUsedError;
|
||||||
|
int? get payeeId => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
|
/// Serializes this SnTransaction to a JSON map.
|
||||||
|
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
|
/// Create a copy of SnTransaction
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
$SnTransactionCopyWith<SnTransaction> get copyWith =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class $SnTransactionCopyWith<$Res> {
|
||||||
|
factory $SnTransactionCopyWith(
|
||||||
|
SnTransaction value, $Res Function(SnTransaction) then) =
|
||||||
|
_$SnTransactionCopyWithImpl<$Res, SnTransaction>;
|
||||||
|
@useResult
|
||||||
|
$Res call(
|
||||||
|
{int id,
|
||||||
|
DateTime createdAt,
|
||||||
|
DateTime updatedAt,
|
||||||
|
DateTime? deletedAt,
|
||||||
|
String remark,
|
||||||
|
String amount,
|
||||||
|
SnWallet? payer,
|
||||||
|
SnWallet? payee,
|
||||||
|
int? payerId,
|
||||||
|
int? payeeId});
|
||||||
|
|
||||||
|
$SnWalletCopyWith<$Res>? get payer;
|
||||||
|
$SnWalletCopyWith<$Res>? get payee;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class _$SnTransactionCopyWithImpl<$Res, $Val extends SnTransaction>
|
||||||
|
implements $SnTransactionCopyWith<$Res> {
|
||||||
|
_$SnTransactionCopyWithImpl(this._value, this._then);
|
||||||
|
|
||||||
|
// ignore: unused_field
|
||||||
|
final $Val _value;
|
||||||
|
// ignore: unused_field
|
||||||
|
final $Res Function($Val) _then;
|
||||||
|
|
||||||
|
/// Create a copy of SnTransaction
|
||||||
|
/// 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? remark = null,
|
||||||
|
Object? amount = null,
|
||||||
|
Object? payer = freezed,
|
||||||
|
Object? payee = freezed,
|
||||||
|
Object? payerId = freezed,
|
||||||
|
Object? payeeId = 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 DateTime?,
|
||||||
|
remark: null == remark
|
||||||
|
? _value.remark
|
||||||
|
: remark // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
amount: null == amount
|
||||||
|
? _value.amount
|
||||||
|
: amount // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
payer: freezed == payer
|
||||||
|
? _value.payer
|
||||||
|
: payer // ignore: cast_nullable_to_non_nullable
|
||||||
|
as SnWallet?,
|
||||||
|
payee: freezed == payee
|
||||||
|
? _value.payee
|
||||||
|
: payee // ignore: cast_nullable_to_non_nullable
|
||||||
|
as SnWallet?,
|
||||||
|
payerId: freezed == payerId
|
||||||
|
? _value.payerId
|
||||||
|
: payerId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int?,
|
||||||
|
payeeId: freezed == payeeId
|
||||||
|
? _value.payeeId
|
||||||
|
: payeeId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int?,
|
||||||
|
) as $Val);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a copy of SnTransaction
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SnWalletCopyWith<$Res>? get payer {
|
||||||
|
if (_value.payer == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $SnWalletCopyWith<$Res>(_value.payer!, (value) {
|
||||||
|
return _then(_value.copyWith(payer: value) as $Val);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a copy of SnTransaction
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SnWalletCopyWith<$Res>? get payee {
|
||||||
|
if (_value.payee == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $SnWalletCopyWith<$Res>(_value.payee!, (value) {
|
||||||
|
return _then(_value.copyWith(payee: value) as $Val);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class _$$SnTransactionImplCopyWith<$Res>
|
||||||
|
implements $SnTransactionCopyWith<$Res> {
|
||||||
|
factory _$$SnTransactionImplCopyWith(
|
||||||
|
_$SnTransactionImpl value, $Res Function(_$SnTransactionImpl) then) =
|
||||||
|
__$$SnTransactionImplCopyWithImpl<$Res>;
|
||||||
|
@override
|
||||||
|
@useResult
|
||||||
|
$Res call(
|
||||||
|
{int id,
|
||||||
|
DateTime createdAt,
|
||||||
|
DateTime updatedAt,
|
||||||
|
DateTime? deletedAt,
|
||||||
|
String remark,
|
||||||
|
String amount,
|
||||||
|
SnWallet? payer,
|
||||||
|
SnWallet? payee,
|
||||||
|
int? payerId,
|
||||||
|
int? payeeId});
|
||||||
|
|
||||||
|
@override
|
||||||
|
$SnWalletCopyWith<$Res>? get payer;
|
||||||
|
@override
|
||||||
|
$SnWalletCopyWith<$Res>? get payee;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class __$$SnTransactionImplCopyWithImpl<$Res>
|
||||||
|
extends _$SnTransactionCopyWithImpl<$Res, _$SnTransactionImpl>
|
||||||
|
implements _$$SnTransactionImplCopyWith<$Res> {
|
||||||
|
__$$SnTransactionImplCopyWithImpl(
|
||||||
|
_$SnTransactionImpl _value, $Res Function(_$SnTransactionImpl) _then)
|
||||||
|
: super(_value, _then);
|
||||||
|
|
||||||
|
/// Create a copy of SnTransaction
|
||||||
|
/// 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? remark = null,
|
||||||
|
Object? amount = null,
|
||||||
|
Object? payer = freezed,
|
||||||
|
Object? payee = freezed,
|
||||||
|
Object? payerId = freezed,
|
||||||
|
Object? payeeId = freezed,
|
||||||
|
}) {
|
||||||
|
return _then(_$SnTransactionImpl(
|
||||||
|
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 DateTime?,
|
||||||
|
remark: null == remark
|
||||||
|
? _value.remark
|
||||||
|
: remark // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
amount: null == amount
|
||||||
|
? _value.amount
|
||||||
|
: amount // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
payer: freezed == payer
|
||||||
|
? _value.payer
|
||||||
|
: payer // ignore: cast_nullable_to_non_nullable
|
||||||
|
as SnWallet?,
|
||||||
|
payee: freezed == payee
|
||||||
|
? _value.payee
|
||||||
|
: payee // ignore: cast_nullable_to_non_nullable
|
||||||
|
as SnWallet?,
|
||||||
|
payerId: freezed == payerId
|
||||||
|
? _value.payerId
|
||||||
|
: payerId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int?,
|
||||||
|
payeeId: freezed == payeeId
|
||||||
|
? _value.payeeId
|
||||||
|
: payeeId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int?,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
@JsonSerializable()
|
||||||
|
class _$SnTransactionImpl implements _SnTransaction {
|
||||||
|
const _$SnTransactionImpl(
|
||||||
|
{required this.id,
|
||||||
|
required this.createdAt,
|
||||||
|
required this.updatedAt,
|
||||||
|
required this.deletedAt,
|
||||||
|
required this.remark,
|
||||||
|
required this.amount,
|
||||||
|
required this.payer,
|
||||||
|
required this.payee,
|
||||||
|
required this.payerId,
|
||||||
|
required this.payeeId});
|
||||||
|
|
||||||
|
factory _$SnTransactionImpl.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$$SnTransactionImplFromJson(json);
|
||||||
|
|
||||||
|
@override
|
||||||
|
final int id;
|
||||||
|
@override
|
||||||
|
final DateTime createdAt;
|
||||||
|
@override
|
||||||
|
final DateTime updatedAt;
|
||||||
|
@override
|
||||||
|
final DateTime? deletedAt;
|
||||||
|
@override
|
||||||
|
final String remark;
|
||||||
|
@override
|
||||||
|
final String amount;
|
||||||
|
@override
|
||||||
|
final SnWallet? payer;
|
||||||
|
@override
|
||||||
|
final SnWallet? payee;
|
||||||
|
@override
|
||||||
|
final int? payerId;
|
||||||
|
@override
|
||||||
|
final int? payeeId;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'SnTransaction(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, remark: $remark, amount: $amount, payer: $payer, payee: $payee, payerId: $payerId, payeeId: $payeeId)';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) ||
|
||||||
|
(other.runtimeType == runtimeType &&
|
||||||
|
other is _$SnTransactionImpl &&
|
||||||
|
(identical(other.id, id) || other.id == id) &&
|
||||||
|
(identical(other.createdAt, createdAt) ||
|
||||||
|
other.createdAt == createdAt) &&
|
||||||
|
(identical(other.updatedAt, updatedAt) ||
|
||||||
|
other.updatedAt == updatedAt) &&
|
||||||
|
(identical(other.deletedAt, deletedAt) ||
|
||||||
|
other.deletedAt == deletedAt) &&
|
||||||
|
(identical(other.remark, remark) || other.remark == remark) &&
|
||||||
|
(identical(other.amount, amount) || other.amount == amount) &&
|
||||||
|
(identical(other.payer, payer) || other.payer == payer) &&
|
||||||
|
(identical(other.payee, payee) || other.payee == payee) &&
|
||||||
|
(identical(other.payerId, payerId) || other.payerId == payerId) &&
|
||||||
|
(identical(other.payeeId, payeeId) || other.payeeId == payeeId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType, id, createdAt, updatedAt,
|
||||||
|
deletedAt, remark, amount, payer, payee, payerId, payeeId);
|
||||||
|
|
||||||
|
/// Create a copy of SnTransaction
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$$SnTransactionImplCopyWith<_$SnTransactionImpl> get copyWith =>
|
||||||
|
__$$SnTransactionImplCopyWithImpl<_$SnTransactionImpl>(this, _$identity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return _$$SnTransactionImplToJson(
|
||||||
|
this,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class _SnTransaction implements SnTransaction {
|
||||||
|
const factory _SnTransaction(
|
||||||
|
{required final int id,
|
||||||
|
required final DateTime createdAt,
|
||||||
|
required final DateTime updatedAt,
|
||||||
|
required final DateTime? deletedAt,
|
||||||
|
required final String remark,
|
||||||
|
required final String amount,
|
||||||
|
required final SnWallet? payer,
|
||||||
|
required final SnWallet? payee,
|
||||||
|
required final int? payerId,
|
||||||
|
required final int? payeeId}) = _$SnTransactionImpl;
|
||||||
|
|
||||||
|
factory _SnTransaction.fromJson(Map<String, dynamic> json) =
|
||||||
|
_$SnTransactionImpl.fromJson;
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get id;
|
||||||
|
@override
|
||||||
|
DateTime get createdAt;
|
||||||
|
@override
|
||||||
|
DateTime get updatedAt;
|
||||||
|
@override
|
||||||
|
DateTime? get deletedAt;
|
||||||
|
@override
|
||||||
|
String get remark;
|
||||||
|
@override
|
||||||
|
String get amount;
|
||||||
|
@override
|
||||||
|
SnWallet? get payer;
|
||||||
|
@override
|
||||||
|
SnWallet? get payee;
|
||||||
|
@override
|
||||||
|
int? get payerId;
|
||||||
|
@override
|
||||||
|
int? get payeeId;
|
||||||
|
|
||||||
|
/// Create a copy of SnTransaction
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
_$$SnTransactionImplCopyWith<_$SnTransactionImpl> get copyWith =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
}
|
65
lib/types/wallet.g.dart
Normal file
65
lib/types/wallet.g.dart
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'wallet.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// JsonSerializableGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
_$SnWalletImpl _$$SnWalletImplFromJson(Map<String, dynamic> json) =>
|
||||||
|
_$SnWalletImpl(
|
||||||
|
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'] == null
|
||||||
|
? null
|
||||||
|
: DateTime.parse(json['deleted_at'] as String),
|
||||||
|
balance: json['balance'] as String,
|
||||||
|
password: json['password'] as String,
|
||||||
|
accountId: (json['account_id'] as num).toInt(),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$$SnWalletImplToJson(_$SnWalletImpl instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'id': instance.id,
|
||||||
|
'created_at': instance.createdAt.toIso8601String(),
|
||||||
|
'updated_at': instance.updatedAt.toIso8601String(),
|
||||||
|
'deleted_at': instance.deletedAt?.toIso8601String(),
|
||||||
|
'balance': instance.balance,
|
||||||
|
'password': instance.password,
|
||||||
|
'account_id': instance.accountId,
|
||||||
|
};
|
||||||
|
|
||||||
|
_$SnTransactionImpl _$$SnTransactionImplFromJson(Map<String, dynamic> json) =>
|
||||||
|
_$SnTransactionImpl(
|
||||||
|
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'] == null
|
||||||
|
? null
|
||||||
|
: DateTime.parse(json['deleted_at'] as String),
|
||||||
|
remark: json['remark'] as String,
|
||||||
|
amount: json['amount'] as String,
|
||||||
|
payer: json['payer'] == null
|
||||||
|
? null
|
||||||
|
: SnWallet.fromJson(json['payer'] as Map<String, dynamic>),
|
||||||
|
payee: json['payee'] == null
|
||||||
|
? null
|
||||||
|
: SnWallet.fromJson(json['payee'] as Map<String, dynamic>),
|
||||||
|
payerId: (json['payer_id'] as num?)?.toInt(),
|
||||||
|
payeeId: (json['payee_id'] as num?)?.toInt(),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$$SnTransactionImplToJson(_$SnTransactionImpl instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'id': instance.id,
|
||||||
|
'created_at': instance.createdAt.toIso8601String(),
|
||||||
|
'updated_at': instance.updatedAt.toIso8601String(),
|
||||||
|
'deleted_at': instance.deletedAt?.toIso8601String(),
|
||||||
|
'remark': instance.remark,
|
||||||
|
'amount': instance.amount,
|
||||||
|
'payer': instance.payer?.toJson(),
|
||||||
|
'payee': instance.payee?.toJson(),
|
||||||
|
'payer_id': instance.payerId,
|
||||||
|
'payee_id': instance.payeeId,
|
||||||
|
};
|
@ -196,68 +196,71 @@ class _AttachmentListState extends State<AttachmentList> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Container(
|
return AspectRatio(
|
||||||
constraints: BoxConstraints(maxHeight: constraints.maxHeight),
|
aspectRatio: widget.data[0]?.data['ratio']?.toDouble() ?? 1,
|
||||||
child: ScrollConfiguration(
|
child: Container(
|
||||||
behavior: _AttachmentListScrollBehavior(),
|
constraints: BoxConstraints(maxHeight: constraints.maxHeight),
|
||||||
child: ListView.separated(
|
child: ScrollConfiguration(
|
||||||
padding: widget.padding,
|
behavior: _AttachmentListScrollBehavior(),
|
||||||
shrinkWrap: true,
|
child: ListView.separated(
|
||||||
itemCount: widget.data.length,
|
padding: widget.padding,
|
||||||
itemBuilder: (context, idx) {
|
shrinkWrap: true,
|
||||||
return Container(
|
itemCount: widget.data.length,
|
||||||
constraints: constraints.copyWith(maxWidth: widget.maxWidth),
|
itemBuilder: (context, idx) {
|
||||||
child: AspectRatio(
|
return Container(
|
||||||
aspectRatio: (widget.data[idx]?.data['ratio'] ?? 1).toDouble(),
|
constraints: constraints.copyWith(maxWidth: widget.maxWidth),
|
||||||
child: GestureDetector(
|
child: AspectRatio(
|
||||||
onTap: () {
|
aspectRatio: (widget.data[idx]?.data['ratio'] ?? 1).toDouble(),
|
||||||
if (widget.data[idx]?.mediaType != SnMediaType.image) return;
|
child: GestureDetector(
|
||||||
context.pushTransparentRoute(
|
onTap: () {
|
||||||
AttachmentZoomView(
|
if (widget.data[idx]?.mediaType != SnMediaType.image) return;
|
||||||
data: widget.data.where((ele) => ele != null && ele.mediaType == SnMediaType.image).cast(),
|
context.pushTransparentRoute(
|
||||||
initialIndex: idx,
|
AttachmentZoomView(
|
||||||
heroTags: heroTags,
|
data: widget.data.where((ele) => ele != null && ele.mediaType == SnMediaType.image).cast(),
|
||||||
),
|
initialIndex: idx,
|
||||||
backgroundColor: Colors.black.withOpacity(0.7),
|
heroTags: heroTags,
|
||||||
rootNavigator: true,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
child: Stack(
|
|
||||||
fit: StackFit.expand,
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: backgroundColor,
|
|
||||||
border: Border(
|
|
||||||
top: borderSide,
|
|
||||||
bottom: borderSide,
|
|
||||||
),
|
|
||||||
borderRadius: AttachmentList.kDefaultRadius,
|
|
||||||
),
|
),
|
||||||
child: ClipRRect(
|
backgroundColor: Colors.black.withOpacity(0.7),
|
||||||
borderRadius: AttachmentList.kDefaultRadius,
|
rootNavigator: true,
|
||||||
child: AttachmentItem(
|
);
|
||||||
data: widget.data[idx],
|
},
|
||||||
heroTag: heroTags[idx],
|
child: Stack(
|
||||||
|
fit: StackFit.expand,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: backgroundColor,
|
||||||
|
border: Border(
|
||||||
|
top: borderSide,
|
||||||
|
bottom: borderSide,
|
||||||
|
),
|
||||||
|
borderRadius: AttachmentList.kDefaultRadius,
|
||||||
|
),
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: AttachmentList.kDefaultRadius,
|
||||||
|
child: AttachmentItem(
|
||||||
|
data: widget.data[idx],
|
||||||
|
heroTag: heroTags[idx],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
Positioned(
|
||||||
Positioned(
|
right: 8,
|
||||||
right: 8,
|
bottom: 8,
|
||||||
bottom: 8,
|
child: Chip(
|
||||||
child: Chip(
|
label: Text('${idx + 1}/${widget.data.length}'),
|
||||||
label: Text('${idx + 1}/${widget.data.length}'),
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
);
|
},
|
||||||
},
|
separatorBuilder: (context, index) => const Gap(8),
|
||||||
separatorBuilder: (context, index) => const Gap(8),
|
physics: const BouncingScrollPhysics(),
|
||||||
physics: const BouncingScrollPhysics(),
|
scrollDirection: Axis.horizontal,
|
||||||
scrollDirection: Axis.horizontal,
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -2,7 +2,9 @@ import 'dart:math' as math;
|
|||||||
|
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:url_launcher/url_launcher_string.dart';
|
||||||
|
|
||||||
extension AppPromptExtension on BuildContext {
|
extension AppPromptExtension on BuildContext {
|
||||||
void showSnackbar(String content, {SnackBarAction? action}) {
|
void showSnackbar(String content, {SnackBarAction? action}) {
|
||||||
@ -111,7 +113,34 @@ extension AppPromptExtension on BuildContext {
|
|||||||
context: this,
|
context: this,
|
||||||
builder: (ctx) => AlertDialog(
|
builder: (ctx) => AlertDialog(
|
||||||
title: Text('dialogError').tr(),
|
title: Text('dialogError').tr(),
|
||||||
content: content,
|
content: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
spacing: 20,
|
||||||
|
children: [
|
||||||
|
content,
|
||||||
|
Text.rich(
|
||||||
|
TextSpan(
|
||||||
|
text: 'needHelp'.tr(),
|
||||||
|
children: [
|
||||||
|
TextSpan(text: ' '),
|
||||||
|
TextSpan(
|
||||||
|
text: 'needHelpLaunch'.tr(),
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(ctx).colorScheme.primary,
|
||||||
|
decoration: TextDecoration.underline,
|
||||||
|
decorationColor: Theme.of(ctx).colorScheme.primary,
|
||||||
|
),
|
||||||
|
recognizer: TapGestureRecognizer()
|
||||||
|
..onTap = () {
|
||||||
|
launchUrlString('https://kb.solsynth.dev/solar-network');
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.pop(ctx),
|
onPressed: () => Navigator.pop(ctx),
|
||||||
@ -128,17 +157,7 @@ extension ByteFormatter on int {
|
|||||||
if (this == 0) return '0 Bytes';
|
if (this == 0) return '0 Bytes';
|
||||||
const k = 1024;
|
const k = 1024;
|
||||||
final dm = decimals < 0 ? 0 : decimals;
|
final dm = decimals < 0 ? 0 : decimals;
|
||||||
final sizes = [
|
final sizes = ['Bytes', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'];
|
||||||
'Bytes',
|
|
||||||
'KiB',
|
|
||||||
'MiB',
|
|
||||||
'GiB',
|
|
||||||
'TiB',
|
|
||||||
'PiB',
|
|
||||||
'EiB',
|
|
||||||
'ZiB',
|
|
||||||
'YiB'
|
|
||||||
];
|
|
||||||
final i = (math.log(this) / math.log(k)).floor().toInt();
|
final i = (math.log(this) / math.log(k)).floor().toInt();
|
||||||
return '${(this / math.pow(k, i)).toStringAsFixed(dm)} ${sizes[i]}';
|
return '${(this / math.pow(k, i)).toStringAsFixed(dm)} ${sizes[i]}';
|
||||||
}
|
}
|
||||||
|
@ -5,10 +5,9 @@ import 'package:material_symbols_icons/symbols.dart';
|
|||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
import 'package:flutter_animate/flutter_animate.dart';
|
import 'package:flutter_animate/flutter_animate.dart';
|
||||||
|
import 'package:surface/providers/config.dart';
|
||||||
// Keep this import to make the web image render work
|
// Keep this import to make the web image render work
|
||||||
import 'package:cached_network_image_platform_interface/cached_network_image_platform_interface.dart';
|
import 'package:cached_network_image_platform_interface/cached_network_image_platform_interface.dart';
|
||||||
import 'package:surface/providers/config.dart';
|
|
||||||
|
|
||||||
class UniversalImage extends StatelessWidget {
|
class UniversalImage extends StatelessWidget {
|
||||||
final String url;
|
final String url;
|
||||||
|
@ -22,14 +22,14 @@ PODS:
|
|||||||
- Firebase/Messaging (11.6.0):
|
- Firebase/Messaging (11.6.0):
|
||||||
- Firebase/CoreOnly
|
- Firebase/CoreOnly
|
||||||
- FirebaseMessaging (~> 11.6.0)
|
- FirebaseMessaging (~> 11.6.0)
|
||||||
- firebase_analytics (11.4.0):
|
- firebase_analytics (11.4.1):
|
||||||
- Firebase/Analytics (= 11.6.0)
|
- Firebase/Analytics (= 11.6.0)
|
||||||
- firebase_core
|
- firebase_core
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- firebase_core (3.10.0):
|
- firebase_core (3.10.1):
|
||||||
- Firebase/CoreOnly (~> 11.6.0)
|
- Firebase/CoreOnly (~> 11.6.0)
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- firebase_messaging (15.2.0):
|
- firebase_messaging (15.2.1):
|
||||||
- Firebase/CoreOnly (~> 11.6.0)
|
- Firebase/CoreOnly (~> 11.6.0)
|
||||||
- Firebase/Messaging (~> 11.6.0)
|
- Firebase/Messaging (~> 11.6.0)
|
||||||
- firebase_core
|
- firebase_core
|
||||||
@ -296,9 +296,9 @@ SPEC CHECKSUMS:
|
|||||||
file_saver: 44e6fbf666677faf097302460e214e977fdd977b
|
file_saver: 44e6fbf666677faf097302460e214e977fdd977b
|
||||||
file_selector_macos: cc3858c981fe6889f364731200d6232dac1d812d
|
file_selector_macos: cc3858c981fe6889f364731200d6232dac1d812d
|
||||||
Firebase: 374a441a91ead896215703a674d58cdb3e9d772b
|
Firebase: 374a441a91ead896215703a674d58cdb3e9d772b
|
||||||
firebase_analytics: 5249f87da6fed852901581aab2602e0280ec2fdb
|
firebase_analytics: 91efc58e8e37964469fdd59ad11ba36bc97e75d6
|
||||||
firebase_core: 6d9bb8b0ea817e8fe0d928177d42275b45fdba6f
|
firebase_core: 75e003524565fb5bd80c9960bc5892e8475821cd
|
||||||
firebase_messaging: ae8e88b586e4d50abc7cac5bacf74d21967fd226
|
firebase_messaging: 082a385eb98b5bb843a566cb30404859c4bd6e25
|
||||||
FirebaseAnalytics: 7114c698cac995602e3b1b96663473e50d54d6e7
|
FirebaseAnalytics: 7114c698cac995602e3b1b96663473e50d54d6e7
|
||||||
FirebaseCore: 48b0dd707581cf9c1a1220da68223fb0a562afaa
|
FirebaseCore: 48b0dd707581cf9c1a1220da68223fb0a562afaa
|
||||||
FirebaseCoreInternal: d98ab91e2d80a56d7b246856a8885443b302c0c2
|
FirebaseCoreInternal: d98ab91e2d80a56d7b246856a8885443b302c0c2
|
||||||
|
76
pubspec.lock
76
pubspec.lock
@ -13,10 +13,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: _flutterfire_internals
|
name: _flutterfire_internals
|
||||||
sha256: "27899c95f9e7ec06c8310e6e0eac967707714b9f1450c4a58fa00ca011a4a8ae"
|
sha256: e4f2a7ef31b0ab2c89d2bde35ef3e6e6aff1dce5e66069c6540b0e9cfe33ee6b
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.49"
|
version: "1.3.50"
|
||||||
_macros:
|
_macros:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: dart
|
description: dart
|
||||||
@ -338,10 +338,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: dart_style
|
name: dart_style
|
||||||
sha256: "7856d364b589d1f08986e140938578ed36ed948581fbc3bc9aef1805039ac5ab"
|
sha256: "7306ab8a2359a48d22310ad823521d723acfed60ee1f7e37388e8986853b6820"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.7"
|
version: "2.3.8"
|
||||||
dart_webrtc:
|
dart_webrtc:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -418,10 +418,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: easy_localization
|
name: easy_localization
|
||||||
sha256: fa59bcdbbb911a764aa6acf96bbb6fa7a5cf8234354fc45ec1a43a0349ef0201
|
sha256: "0f5239c7b8ab06c66440cfb0e9aa4b4640429c6668d5a42fe389c5de42220b12"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.7"
|
version: "3.0.7+1"
|
||||||
easy_localization_loader:
|
easy_localization_loader:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -538,34 +538,34 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: firebase_analytics
|
name: firebase_analytics
|
||||||
sha256: "498c6cb8468e348a556709c745d92a52173ab3a9b906aa0593393f0787f201ea"
|
sha256: eac382bbcd5ae78c1d1ce5619d13f5a7424429f4bf55df9e3ad5110da34d1060
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "11.4.0"
|
version: "11.4.1"
|
||||||
firebase_analytics_platform_interface:
|
firebase_analytics_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: firebase_analytics_platform_interface
|
name: firebase_analytics_platform_interface
|
||||||
sha256: ccbb350554e98afdb4b59852689292d194d31232a2647b5012a66622b3711df9
|
sha256: a34db46c367265c4c961626e4b128bfb7b7e50958e7add4c27ba103f5f81b9b0
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.3.0"
|
version: "4.3.1"
|
||||||
firebase_analytics_web:
|
firebase_analytics_web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: firebase_analytics_web
|
name: firebase_analytics_web
|
||||||
sha256: "68e1f18fc16482c211c658e739c25f015b202a260d9ad8249c6d3d7963b8105f"
|
sha256: b6b4cef08e45e4c7d48476d9fc49fe9577081809a59026fe95b1a1b1eea165fa
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.5.10+6"
|
version: "0.5.10+7"
|
||||||
firebase_core:
|
firebase_core:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: firebase_core
|
name: firebase_core
|
||||||
sha256: "0307c1fde82e2b8b97e0be2dab93612aff9a72f31ebe9bfac66ed8b37ef7c568"
|
sha256: d851c1ca98fd5a4c07c747f8c65dacc2edd84a4d9ac055d32a5f0342529069f5
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.10.0"
|
version: "3.10.1"
|
||||||
firebase_core_platform_interface:
|
firebase_core_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -586,26 +586,26 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: firebase_messaging
|
name: firebase_messaging
|
||||||
sha256: "48a8a59197c1c5174060ba9aa1e0036e9b5a0d28a0cc22d19c1fcabc67fafe3c"
|
sha256: e20ea2a0ecf9b0971575ab3ab42a6e285a94e50092c555b090c1a588a81b4d54
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "15.2.0"
|
version: "15.2.1"
|
||||||
firebase_messaging_platform_interface:
|
firebase_messaging_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: firebase_messaging_platform_interface
|
name: firebase_messaging_platform_interface
|
||||||
sha256: "9770a8e91f54296829dcaa61ce9b7c2f9ae9abbf99976dd3103a60470d5264dd"
|
sha256: c57a92b5ae1857ef4fe4ae2e73452b44d32e984e15ab8b53415ea1bb514bdabd
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.6.0"
|
version: "4.6.1"
|
||||||
firebase_messaging_web:
|
firebase_messaging_web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: firebase_messaging_web
|
name: firebase_messaging_web
|
||||||
sha256: "329ca4ef45ec616abe6f1d5e58feed0934a50840a65aa327052354ad3c64ed77"
|
sha256: "83694a990d8525d6b01039240b97757298369622ca0253ad0ebcfed221bf8ee0"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.10.0"
|
version: "3.10.1"
|
||||||
fixnum:
|
fixnum:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -830,10 +830,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: flutter_webrtc
|
name: flutter_webrtc
|
||||||
sha256: "188401cc3275bc4f1f965babdff6cac612a4b46572f1e49f49db8af5361d5712"
|
sha256: "4c76069f724f79dc6e8739c8f16c2ee00ca30a1f8e3aa75c0e830f8183278f03"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.12.6"
|
version: "0.12.7"
|
||||||
freezed:
|
freezed:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
@ -878,18 +878,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: glob
|
name: glob
|
||||||
sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63"
|
sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.2"
|
version: "2.1.3"
|
||||||
go_router:
|
go_router:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: go_router
|
name: go_router
|
||||||
sha256: "7c2d40b59890a929824f30d442e810116caf5088482629c894b9e4478c67472d"
|
sha256: daf3ff5570f55396b2d2c9bf8136d7db3a8acf208ac0cef92a3ae2beb9a81550
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "14.6.3"
|
version: "14.7.1"
|
||||||
google_fonts:
|
google_fonts:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -950,10 +950,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: http
|
name: http
|
||||||
sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010
|
sha256: fe7ab022b76f3034adc518fb6ea04a82387620e19977665ea18d30a1cf43442f
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.2"
|
version: "1.3.0"
|
||||||
http_multi_server:
|
http_multi_server:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -1030,10 +1030,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: image_picker_macos
|
name: image_picker_macos
|
||||||
sha256: "3f5ad1e8112a9a6111c46d0b57a7be2286a9a07fc6e1976fdf5be2bd31d4ff62"
|
sha256: "1b90ebbd9dcf98fb6c1d01427e49a55bd96b5d67b8c67cf955d60a5de74207c1"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.2.1+1"
|
version: "0.2.1+2"
|
||||||
image_picker_platform_interface:
|
image_picker_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -1710,18 +1710,18 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: shared_preferences
|
name: shared_preferences
|
||||||
sha256: a752ce92ea7540fc35a0d19722816e04d0e72828a4200e83a98cf1a1eb524c9a
|
sha256: c59819dacc6669a1165d54d2735a9543f136f9b3cec94ca65cea6ab8dffc422e
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.5"
|
version: "2.4.0"
|
||||||
shared_preferences_android:
|
shared_preferences_android:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: shared_preferences_android
|
name: shared_preferences_android
|
||||||
sha256: "138b7bbbc7f59c56236e426c37afb8f78cbc57b094ac64c440e0bb90e380a4f5"
|
sha256: "650584dcc0a39856f369782874e562efd002a9c94aec032412c9eb81419cce1f"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.2"
|
version: "2.4.4"
|
||||||
shared_preferences_foundation:
|
shared_preferences_foundation:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -2171,10 +2171,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: web_socket_channel
|
name: web_socket_channel
|
||||||
sha256: "9f187088ed104edd8662ca07af4b124465893caf063ba29758f97af57e61da8f"
|
sha256: "0b8e2457400d8a859b7b2030786835a28a8e80836ef64402abef392ff4f1d0e5"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.1"
|
version: "3.0.2"
|
||||||
webrtc_interface:
|
webrtc_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -2187,10 +2187,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: win32
|
name: win32
|
||||||
sha256: "154360849a56b7b67331c21f09a386562d88903f90a1099c5987afc1912e1f29"
|
sha256: daf97c9d80197ed7b619040e86c8ab9a9dad285e7671ee7390f9180cc828a51e
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.10.0"
|
version: "5.10.1"
|
||||||
win32_registry:
|
win32_registry:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
|
|||||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||||
# In Windows, build-name is used as the major, minor, and patch parts
|
# In Windows, build-name is used as the major, minor, and patch parts
|
||||||
# of the product and file versions while build-number is used as the build suffix.
|
# of the product and file versions while build-number is used as the build suffix.
|
||||||
version: 2.2.2+57
|
version: 2.2.2+59
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.5.4
|
sdk: ^3.5.4
|
||||||
|
Reference in New Issue
Block a user