Compare commits

..

No commits in common. "03275b46ca6b90971e60dafb8e5f09ad1f957740" and "8bb62b59921070d69669f32483d5305bdf7a2637" have entirely different histories.

22 changed files with 69 additions and 1335 deletions

View File

@ -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": "新年快乐!", "subject": "Merry Christmas!",
"subtitle": "一条来自 Solar Network 团队的信息", "subtitle": "一条来自 Solar Network 团队的信息",
"content": "今天是农历正月初一,小羊祝您新年快乐 🎉", "content": "今天是 12 月 25 日 (UTC+8),小羊祝您圣诞快乐 🎄",
"metadata": { "metadata": {
"image": "D2EDbcrsTugs3xk5" "image": "6EqsYQwmFRCkbmhR"
}, },
"priority": 10 "priority": 10
} }

View File

@ -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
] ]

View File

@ -18,8 +18,6 @@
"screenAbuseReport": "Abuse Reports", "screenAbuseReport": "Abuse Reports",
"screenSettings": "Settings", "screenSettings": "Settings",
"screenAccountSettings": "Account Settings", "screenAccountSettings": "Account Settings",
"screenFactorSettings": "Auth Factors",
"screenAccountWallet": "Wallet",
"screenNews": "News", "screenNews": "News",
"screenAlbum": "Album", "screenAlbum": "Album",
"screenChat": "Chat", "screenChat": "Chat",
@ -31,6 +29,7 @@
"screenNotification": "Notification", "screenNotification": "Notification",
"screenPostSearch": "Search Posts", "screenPostSearch": "Search Posts",
"screenFriend": "Friends", "screenFriend": "Friends",
"screenFactorSettings": "Auth Factors",
"dialogOkay": "Okay", "dialogOkay": "Okay",
"dialogCancel": "Cancel", "dialogCancel": "Cancel",
"dialogConfirm": "Confirm", "dialogConfirm": "Confirm",
@ -131,8 +130,6 @@
"accountSettingsSubtitle": "Manage your account and make it yours.", "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", "factorSettings": "Auth Factors",
"factorSettingsSubtitle": "Manage your authentication factors.", "factorSettingsSubtitle": "Manage your authentication factors.",
"accountProfileEditApplied": "Profile modification applied.", "accountProfileEditApplied": "Profile modification applied.",
@ -199,9 +196,6 @@
"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",
@ -590,15 +584,5 @@
"newsToday": "Today's News", "newsToday": "Today's News",
"totpPostSetup": "One More Thing", "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.", "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", "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"
}
} }

View File

@ -16,8 +16,6 @@
"screenAbuseReport": "滥用检举", "screenAbuseReport": "滥用检举",
"screenSettings": "设置", "screenSettings": "设置",
"screenAccountSettings": "账号设置", "screenAccountSettings": "账号设置",
"screenFactorSettings": "验证因子",
"screenAccountWallet": "钱包",
"screenNews": "新闻", "screenNews": "新闻",
"screenAlbum": "相册", "screenAlbum": "相册",
"screenChat": "聊天", "screenChat": "聊天",
@ -115,8 +113,6 @@
"accountSettingsSubtitle": "管理你的帐号并让它更好的服务你。", "accountSettingsSubtitle": "管理你的帐号并让它更好的服务你。",
"accountProfileEdit": "编辑资料", "accountProfileEdit": "编辑资料",
"accountProfileEditSubtitle": "使你的 Solarpass 账户更像你。", "accountProfileEditSubtitle": "使你的 Solarpass 账户更像你。",
"accountWallet": "钱包",
"accountWalletSubtitle": "查看你的余额和交易记录。",
"factorSettings": "验证因子", "factorSettings": "验证因子",
"factorSettingsSubtitle": "管理你的登陆验证方式。", "factorSettingsSubtitle": "管理你的登陆验证方式。",
"accountProfileEditApplied": "个人资料修改已被应用。", "accountProfileEditApplied": "个人资料修改已被应用。",
@ -197,9 +193,6 @@
"other": "{} 条评论" "other": "{} 条评论"
}, },
"settingsAppearance": "外观", "settingsAppearance": "外观",
"settingsDisplayLanguage": "显示语言",
"settingsDisplayLanguageDescription": "设置应用程序使用的语言",
"settingsDisplayLanguageSystem": "跟随系统",
"settingsBackgroundImage": "背景图片", "settingsBackgroundImage": "背景图片",
"settingsBackgroundImageDescription": "设置应用全局生效的的背景图片。", "settingsBackgroundImageDescription": "设置应用全局生效的的背景图片。",
"settingsBackgroundImageClear": "清除现存背景图", "settingsBackgroundImageClear": "清除现存背景图",
@ -588,15 +581,5 @@
"newsToday": "快讯", "newsToday": "快讯",
"totpPostSetup": "还有一件事", "totpPostSetup": "还有一件事",
"totpPostSetupDescription": "使用 Google Authenticator, Microsoft Authenticator, 1Password, Authy, Bitwarden 或其他支持 TOTP 的验证器扫描本 QR Code 来添加。", "totpPostSetupDescription": "使用 Google Authenticator, Microsoft Authenticator, 1Password, Authy, Bitwarden 或其他支持 TOTP 的验证器扫描本 QR Code 来添加。",
"totpNeverShare": "永远不要分享这个 QR Code", "totpNeverShare": "永远不要分享这个 QR Code"
"needHelp": "需要帮助?",
"needHelpLaunch": "查看我们的山羊维基!",
"walletCreate": "创建钱包",
"walletCreateSubtitle": "创建于一个钱包来开始使用源点。",
"walletCreatePassword": "在下方设置你的付款密码",
"walletCurrencyShort": "源点",
"walletCurrency": {
"one": "{} 源点",
"other": "{} 源点"
}
} }

View File

@ -15,9 +15,6 @@
"screenAccountProfileEdit": "編輯資料", "screenAccountProfileEdit": "編輯資料",
"screenAbuseReport": "濫用檢舉", "screenAbuseReport": "濫用檢舉",
"screenSettings": "設置", "screenSettings": "設置",
"screenAccountSettings": "賬號設置",
"screenFactorSettings": "驗證因子",
"screenAccountWallet": "錢包",
"screenNews": "新聞", "screenNews": "新聞",
"screenAlbum": "相冊", "screenAlbum": "相冊",
"screenChat": "聊天", "screenChat": "聊天",
@ -91,18 +88,8 @@
}, },
"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": "退出登錄",
@ -111,14 +98,8 @@
"accountLogoutConfirm": "您需要重新輸入賬號密碼,甚至可能需要多步驗證來再次登陸。", "accountLogoutConfirm": "您需要重新輸入賬號密碼,甚至可能需要多步驗證來再次登陸。",
"accountPublishers": "你的發佈者", "accountPublishers": "你的發佈者",
"accountPublishersSubtitle": "管理你的公共形象。", "accountPublishersSubtitle": "管理你的公共形象。",
"accountSettings": "帳號設置",
"accountSettingsSubtitle": "管理你的帳號並讓它更好的服務你。",
"accountProfileEdit": "編輯資料", "accountProfileEdit": "編輯資料",
"accountProfileEditSubtitle": "使你的 Solarpass 賬户更像你。", "accountProfileEditSubtitle": "使你的 Solarpass 賬户更像你。",
"accountWallet": "錢包",
"accountWalletSubtitle": "查看你的餘額和交易記錄。",
"factorSettings": "驗證因子",
"factorSettingsSubtitle": "管理你的登陸驗證方式。",
"accountProfileEditApplied": "個人資料修改已被應用。", "accountProfileEditApplied": "個人資料修改已被應用。",
"publishersNew": "新發布者", "publishersNew": "新發布者",
"publisherNewSubtitle": "創建一個新的公共身份。", "publisherNewSubtitle": "創建一個新的公共身份。",
@ -197,9 +178,6 @@
"other": "{} 條評論" "other": "{} 條評論"
}, },
"settingsAppearance": "外觀", "settingsAppearance": "外觀",
"settingsDisplayLanguage": "顯示語言",
"settingsDisplayLanguageDescription": "設置應用程序使用的語言",
"settingsDisplayLanguageSystem": "跟隨系統",
"settingsBackgroundImage": "背景圖片", "settingsBackgroundImage": "背景圖片",
"settingsBackgroundImageDescription": "設置應用全局生效的的背景圖片。", "settingsBackgroundImageDescription": "設置應用全局生效的的背景圖片。",
"settingsBackgroundImageClear": "清除現存背景圖", "settingsBackgroundImageClear": "清除現存背景圖",
@ -585,18 +563,5 @@
"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": "{} 源點"
}
} }

View File

@ -15,9 +15,6 @@
"screenAccountProfileEdit": "編輯資料", "screenAccountProfileEdit": "編輯資料",
"screenAbuseReport": "濫用檢舉", "screenAbuseReport": "濫用檢舉",
"screenSettings": "設置", "screenSettings": "設置",
"screenAccountSettings": "賬號設置",
"screenFactorSettings": "驗證因子",
"screenAccountWallet": "錢包",
"screenNews": "新聞", "screenNews": "新聞",
"screenAlbum": "相冊", "screenAlbum": "相冊",
"screenChat": "聊天", "screenChat": "聊天",
@ -91,18 +88,8 @@
}, },
"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": "退出登錄",
@ -111,14 +98,8 @@
"accountLogoutConfirm": "您需要重新輸入賬號密碼,甚至可能需要多步驗證來再次登陸。", "accountLogoutConfirm": "您需要重新輸入賬號密碼,甚至可能需要多步驗證來再次登陸。",
"accountPublishers": "你的發佈者", "accountPublishers": "你的發佈者",
"accountPublishersSubtitle": "管理你的公共形象。", "accountPublishersSubtitle": "管理你的公共形象。",
"accountSettings": "帳號設置",
"accountSettingsSubtitle": "管理你的帳號並讓它更好的服務你。",
"accountProfileEdit": "編輯資料", "accountProfileEdit": "編輯資料",
"accountProfileEditSubtitle": "使你的 Solarpass 賬戶更像你。", "accountProfileEditSubtitle": "使你的 Solarpass 賬戶更像你。",
"accountWallet": "錢包",
"accountWalletSubtitle": "查看你的餘額和交易記錄。",
"factorSettings": "驗證因子",
"factorSettingsSubtitle": "管理你的登陸驗證方式。",
"accountProfileEditApplied": "個人資料修改已被應用。", "accountProfileEditApplied": "個人資料修改已被應用。",
"publishersNew": "新發布者", "publishersNew": "新發布者",
"publisherNewSubtitle": "創建一個新的公共身份。", "publisherNewSubtitle": "創建一個新的公共身份。",
@ -197,9 +178,6 @@
"other": "{} 條評論" "other": "{} 條評論"
}, },
"settingsAppearance": "外觀", "settingsAppearance": "外觀",
"settingsDisplayLanguage": "顯示語言",
"settingsDisplayLanguageDescription": "設置應用程序使用的語言",
"settingsDisplayLanguageSystem": "跟隨系統",
"settingsBackgroundImage": "背景圖片", "settingsBackgroundImage": "背景圖片",
"settingsBackgroundImageDescription": "設置應用全局生效的的背景圖片。", "settingsBackgroundImageDescription": "設置應用全局生效的的背景圖片。",
"settingsBackgroundImageClear": "清除現存背景圖", "settingsBackgroundImageClear": "清除現存背景圖",
@ -585,18 +563,5 @@
"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": "{} 源點"
}
} }

View File

@ -33,7 +33,6 @@ 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';
@ -100,11 +99,6 @@ final _appRoutes = [
], ],
), ),
GoRoute(path: '/account', name: 'account', builder: (context, state) => const AccountScreen(), routes: [ GoRoute(path: '/account', name: 'account', builder: (context, state) => const AccountScreen(), routes: [
GoRoute(
path: '/wallet',
name: 'accountWallet',
builder: (context, state) => const WalletScreen(),
),
GoRoute( GoRoute(
path: '/settings', path: '/settings',
name: 'accountSettings', name: 'accountSettings',

View File

@ -144,16 +144,6 @@ class _AuthorizedAccountScreen extends StatelessWidget {
GoRouter.of(context).pushNamed('factorSettings'); 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( ListTile(
title: Text('accountSettings').tr(), title: Text('accountSettings').tr(),
subtitle: Text('accountSettingsSubtitle').tr(), subtitle: Text('accountSettingsSubtitle').tr(),

View File

@ -238,7 +238,7 @@ class _FactorNewDialogState extends State<_FactorNewDialog> {
class _FactorTotpFactorDialog extends StatelessWidget { class _FactorTotpFactorDialog extends StatelessWidget {
final SnAuthFactor factor; final SnAuthFactor factor;
const _FactorTotpFactorDialog({required this.factor}); const _FactorTotpFactorDialog({super.key, required this.factor});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {

View File

@ -412,9 +412,8 @@ class _LoginLookupScreenState extends State<_LoginLookupScreen> {
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) { if (mounted)
context.showModalDialog('done'.tr(), 'signinResetPasswordSent'.tr()); context.showModalDialog('done'.tr(), 'signinResetPasswordSent'.tr());
}
} catch (err) { } catch (err) {
if (mounted) context.showErrorDialog(err); if (mounted) context.showErrorDialog(err);
} finally { } finally {

View File

@ -302,18 +302,20 @@ 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: (context) { Builder(
final date = _article!.publishedAt ?? _article!.createdAt; builder: (context) {
return Row( final date = _article!.publishedAt ?? _article!.createdAt;
crossAxisAlignment: CrossAxisAlignment.center, return Row(
spacing: 2, crossAxisAlignment: CrossAxisAlignment.center,
children: [ spacing: 2,
Text(DateFormat().format(date)).textStyle(Theme.of(context).textTheme.bodySmall!), children: [
Text(' · ').textStyle(Theme.of(context).textTheme.bodySmall!).bold(), Text(DateFormat().format(date)).textStyle(Theme.of(context).textTheme.bodySmall!),
Text(RelativeTime(context).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); ],
}), ).opacity(0.75);
}
),
], ],
).padding(horizontal: 16), ).padding(horizontal: 16),
onTap: () { onTap: () {
@ -513,11 +515,6 @@ 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,
)
], ],
), ),
), ),

View File

@ -38,11 +38,9 @@ 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 {

View File

@ -82,48 +82,6 @@ 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(),
@ -189,31 +147,30 @@ 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) => builder: (context) => AlertDialog(
AlertDialog( content: SingleChildScrollView(
content: SingleChildScrollView( child: ColorPicker(
child: ColorPicker( pickerColor: pickerColor,
pickerColor: pickerColor, onColorChanged: (color) => pickerColor = color,
onColorChanged: (color) => pickerColor = color, enableAlpha: false,
enableAlpha: false, hexInputBar: true,
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;
@ -249,13 +206,11 @@ 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 _prefs.setInt(kAppColorSchemeStoreKey, kColorSchemes.values.elementAt(value).value);
.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(() {});
@ -387,8 +342,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
('Custom', _serverUrlController.text), ('Custom', _serverUrlController.text),
] ]
.map( .map(
(item) => (item) => DropdownMenuItem<String>(
DropdownMenuItem<String>(
value: item.$2, value: item.$2,
child: Column( child: Column(
mainAxisSize: MainAxisSize.max, mainAxisSize: MainAxisSize.max,
@ -400,7 +354,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
], ],
), ),
), ),
) )
.toList(), .toList(),
value: _serverUrlController.text, value: _serverUrlController.text,
onChanged: (String? value) { onChanged: (String? value) {
@ -455,12 +409,11 @@ class _SettingsScreenState extends State<SettingsScreen> {
isExpanded: true, isExpanded: true,
items: kImageQualityLevel.entries items: kImageQualityLevel.entries
.map( .map(
(item) => (item) => DropdownMenuItem<FilterQuality>(
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;

View File

@ -1,282 +0,0 @@
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),
),
),
);
}
}

View File

@ -16,7 +16,6 @@ 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;

View File

@ -26,7 +26,6 @@ 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;
@ -53,7 +52,6 @@ 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});
} }
@ -79,7 +77,6 @@ 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,
}) { }) {
@ -108,10 +105,6 @@ 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
@ -139,7 +132,6 @@ 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});
} }
@ -163,7 +155,6 @@ 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,
}) { }) {
@ -192,10 +183,6 @@ 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
@ -218,7 +205,6 @@ 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,
@ -239,8 +225,6 @@ 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 {
@ -254,7 +238,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, resultCoin: $resultCoin, resultModifiers: $resultModifiers, accountId: $accountId)'; return 'SnCheckInRecord(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, resultTier: $resultTier, resultExperience: $resultExperience, resultModifiers: $resultModifiers, accountId: $accountId)';
} }
@override @override
@ -273,8 +257,6 @@ 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) ||
@ -291,7 +273,6 @@ class _$SnCheckInRecordImpl extends _SnCheckInRecord {
deletedAt, deletedAt,
resultTier, resultTier,
resultExperience, resultExperience,
resultCoin,
const DeepCollectionEquality().hash(_resultModifiers), const DeepCollectionEquality().hash(_resultModifiers),
accountId); accountId);
@ -320,7 +301,6 @@ 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._();
@ -341,8 +321,6 @@ 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;

View File

@ -17,7 +17,6 @@ _$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(),
@ -33,7 +32,6 @@ 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,
}; };

View File

@ -1,37 +0,0 @@
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);
}

View File

@ -1,666 +0,0 @@
// 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;
}

View File

@ -1,65 +0,0 @@
// 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,
};

View File

@ -2,9 +2,7 @@ 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}) {
@ -113,34 +111,7 @@ extension AppPromptExtension on BuildContext {
context: this, context: this,
builder: (ctx) => AlertDialog( builder: (ctx) => AlertDialog(
title: Text('dialogError').tr(), title: Text('dialogError').tr(),
content: Column( content: content,
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),
@ -157,7 +128,17 @@ 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 = ['Bytes', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']; final sizes = [
'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]}';
} }

View File

@ -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+59 version: 2.2.2+58
environment: environment:
sdk: ^3.5.4 sdk: ^3.5.4