Compare commits
	
		
			21 Commits
		
	
	
		
			2.2.2+56
			...
			4b5b001739
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 4b5b001739 | |||
| db8871a455 | |||
| 38dcaa6066 | |||
| 03275b46ca | |||
| cf3b482fef | |||
| aa4c04d4ef | |||
| 73b82f65e4 | |||
| 9471fe40fe | |||
| 0d1e18735e | |||
| 8bb62b5992 | |||
| 1e8a6dea5b | |||
| 5c2804cc4d | |||
| 0dbb8f132a | |||
| 3395f3dbd0 | |||
| d258ba776e | |||
| 0dcfcaad56 | |||
| 687e720956 | |||
| 180876949e | |||
| 9718965809 | |||
| 5377161fb0 | |||
| 963e538ae5 | 
| @@ -19,10 +19,14 @@ | ||||
|         android:icon="@mipmap/ic_launcher" | ||||
|         android:enableOnBackInvokedCallback="true" | ||||
|         android:requestLegacyExternalStorage="true"> | ||||
|         <meta-data | ||||
|             android:name="flutterEmbedding" | ||||
|             android:value="2" /> | ||||
|  | ||||
|         <activity | ||||
|             android:name=".MainActivity" | ||||
|             android:exported="true" | ||||
|             android:launchMode="singleTask" | ||||
|             android:launchMode="singleInstance" | ||||
|             android:taskAffinity="" | ||||
|             android:theme="@style/LaunchTheme" | ||||
|             android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" | ||||
|   | ||||
| @@ -15,11 +15,11 @@ body:json { | ||||
|     "client_id": "{{third_client_id}}", | ||||
|     "client_secret":"{{third_client_tk}}", | ||||
|     "type": "general", | ||||
|     "subject": "Merry Christmas!", | ||||
|     "subject": "新年快乐!", | ||||
|     "subtitle": "一条来自 Solar Network 团队的信息", | ||||
|     "content": "今天是 12 月 25 日 (UTC+8),小羊祝您圣诞快乐 🎄", | ||||
|     "content": "今天是农历正月初一,小羊祝您新年快乐 🎉", | ||||
|     "metadata": { | ||||
|       "image": "6EqsYQwmFRCkbmhR" | ||||
|       "image": "D2EDbcrsTugs3xk5" | ||||
|     }, | ||||
|     "priority": 10 | ||||
|   } | ||||
|   | ||||
							
								
								
									
										11
									
								
								api/Reader/List News Sources.bru
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								api/Reader/List News Sources.bru
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | ||||
| meta { | ||||
|   name: List News Sources | ||||
|   type: http | ||||
|   seq: 3 | ||||
| } | ||||
|  | ||||
| get { | ||||
|   url: {{endpoint}}/cgi/re/well-known/sources | ||||
|   body: none | ||||
|   auth: none | ||||
| } | ||||
							
								
								
									
										17
									
								
								api/Reader/List News.bru
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								api/Reader/List News.bru
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | ||||
| meta { | ||||
|   name: List News | ||||
|   type: http | ||||
|   seq: 2 | ||||
| } | ||||
|  | ||||
| get { | ||||
|   url: {{endpoint}}/cgi/re/news?take=10&offset=0&source=shadiao | ||||
|   body: none | ||||
|   auth: none | ||||
| } | ||||
|  | ||||
| params:query { | ||||
|   take: 10 | ||||
|   offset: 0 | ||||
|   source: shadiao | ||||
| } | ||||
							
								
								
									
										18
									
								
								api/Reader/Trigger Scan News.bru
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								api/Reader/Trigger Scan News.bru
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | ||||
| meta { | ||||
|   name: Trigger Scan News | ||||
|   type: http | ||||
|   seq: 1 | ||||
| } | ||||
|  | ||||
| post { | ||||
|   url: {{endpoint}}/cgi/re/admin/scan | ||||
|   body: json | ||||
|   auth: inherit | ||||
| } | ||||
|  | ||||
| body:json { | ||||
|   { | ||||
|     "sources": ["taiwan-ltn"], | ||||
|     "eager": true | ||||
|   } | ||||
| } | ||||
							
								
								
									
										11
									
								
								api/WatchTower/Run Database Maintenance.bru
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								api/WatchTower/Run Database Maintenance.bru
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | ||||
| meta { | ||||
|   name: Run Database Maintenance | ||||
|   type: http | ||||
|   seq: 1 | ||||
| } | ||||
|  | ||||
| post { | ||||
|   url: {{endpoint}}/wt/maintenance/database | ||||
|   body: none | ||||
|   auth: inherit | ||||
| } | ||||
| @@ -17,6 +17,10 @@ | ||||
|   "screenAccountProfileEdit": "Edit Profile", | ||||
|   "screenAbuseReport": "Abuse Reports", | ||||
|   "screenSettings": "Settings", | ||||
|   "screenAccountSettings": "Account Settings", | ||||
|   "screenFactorSettings": "Auth Factors", | ||||
|   "screenAccountWallet": "Wallet", | ||||
|   "screenNews": "News", | ||||
|   "screenAlbum": "Album", | ||||
|   "screenChat": "Chat", | ||||
|   "screenChatManage": "Edit Channel", | ||||
| @@ -103,8 +107,18 @@ | ||||
|   }, | ||||
|   "loginEnterPassword": "Enter the code", | ||||
|   "loginSuccess": "Logged in as {}", | ||||
|   "authFactorDelete": "Delete Auth Factor", | ||||
|   "authFactorDeleteDescription": "Are you sure you want delete auth factor {}?", | ||||
|   "authFactorPassword": "Password", | ||||
|   "authFactorPasswordDescription": "The password you set when you registered.", | ||||
|   "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!", | ||||
|   "accountIntroSubtitle": "Pick an option below to get started.", | ||||
|   "accountLogout": "Logout", | ||||
| @@ -113,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.", | ||||
|   "accountPublishers": "Your publishers", | ||||
|   "accountPublishersSubtitle": "Manage your publish identities.", | ||||
|   "accountSettings": "Account Settings", | ||||
|   "accountSettingsSubtitle": "Manage your account and make it yours.", | ||||
|   "accountProfileEdit": "Edit your profile", | ||||
|   "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.", | ||||
|   "publishersNew": "New Publisher", | ||||
|   "publisherNewSubtitle": "Create a new publisher identity.", | ||||
| @@ -179,6 +199,9 @@ | ||||
|     "other": "{} comments" | ||||
|   }, | ||||
|   "settingsAppearance": "Appearance", | ||||
|   "settingsDisplayLanguage": "Display Language", | ||||
|   "settingsDisplayLanguageDescription": "Set the application language.", | ||||
|   "settingsDisplayLanguageSystem": "Follow System", | ||||
|   "settingsAppBarTransparent": "Transparent App Bar", | ||||
|   "settingsAppBarTransparentDescription": "Enable transparent effect for the app bar.", | ||||
|   "settingsDrawerPreferCollapse": "Prefer Drawer Collapse", | ||||
| @@ -531,6 +554,9 @@ | ||||
|   "postImageShareAds": "Explore posts on the Solar Network", | ||||
|   "postShare": "Share", | ||||
|   "postShareImage": "Share via Image", | ||||
|   "postGetInsight": "Get Insight", | ||||
|   "postGetInsightTitle": "AI Insight", | ||||
|   "postGetInsightDescription": "AI may make mistakes, check important information.", | ||||
|   "appInitializing": "Initializing", | ||||
|   "poweredBy": "Powered by {}", | ||||
|   "shareIntent": "Share", | ||||
| @@ -558,5 +584,25 @@ | ||||
|   "postCategoryKnowledge": "Knowledge", | ||||
|   "postCategoryLiterature": "Literature", | ||||
|   "postCategoryFunny": "Funny", | ||||
|   "postCategoryUncategorized": "Uncategorized" | ||||
|   "postCategoryUncategorized": "Uncategorized", | ||||
|   "newsAllSources": "All News", | ||||
|   "newsReadingProviderSwap": "Swap", | ||||
|   "newsReadingFromReader": "You're reading from HyperNet.Reader", | ||||
|   "newsReadingFromOriginal": "You're reading the original article", | ||||
|   "newsDisclaimer": "This article is fetched from the Internet, we do not guarantee its authenticity, please judge for yourself. All content in this article belongs to the original author.", | ||||
|   "newsToday": "Today's News", | ||||
|   "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" | ||||
|   }, | ||||
|   "aiThinkingProcess": "AI Thinking Process" | ||||
| } | ||||
|   | ||||
| @@ -15,6 +15,10 @@ | ||||
|   "screenAccountProfileEdit": "编辑资料", | ||||
|   "screenAbuseReport": "滥用检举", | ||||
|   "screenSettings": "设置", | ||||
|   "screenAccountSettings": "账号设置", | ||||
|   "screenFactorSettings": "验证因子", | ||||
|   "screenAccountWallet": "钱包", | ||||
|   "screenNews": "新闻", | ||||
|   "screenAlbum": "相册", | ||||
|   "screenChat": "聊天", | ||||
|   "screenChatManage": "编辑聊天频道", | ||||
| @@ -87,8 +91,18 @@ | ||||
|   }, | ||||
|   "loginEnterPassword": "验证代码", | ||||
|   "loginSuccess": "登录为 {}", | ||||
|   "authFactorDelete": "删除验证因子", | ||||
|   "authFactorDeleteDescription": "你确定要删除 {} 验证因子吗?", | ||||
|   "authFactorPassword": "密码", | ||||
|   "authFactorPasswordDescription": "注册时选择设置的密码。", | ||||
|   "authFactorEmail": "电邮一次性验证码", | ||||
|   "authFactorEmailDescription": "由我们生成并发送到绑定的的电子邮箱的一次性验证码。", | ||||
|   "authFactorTOTP": "时序验证码", | ||||
|   "authFactorTOTPDescription": "使用 Google Authenticator 或 Authy 等验证器生成的一次性验证码。", | ||||
|   "authFactorInAppNotify": "应用内通知验证码", | ||||
|   "authFactorInAppNotifyDescription": "通过站内通知推送的一次性验证码。", | ||||
|   "authFactorAdd": "添加新验证因子", | ||||
|   "authFactorAddSubtitle": "给你的帐户登陆时提供另一个方案。", | ||||
|   "accountIntroTitle": "喜欢您来!", | ||||
|   "accountIntroSubtitle": "登陆以探索更广大的世界。", | ||||
|   "accountLogout": "退出登录", | ||||
| @@ -97,8 +111,14 @@ | ||||
|   "accountLogoutConfirm": "您需要重新输入账号密码,甚至可能需要多步验证来再次登陆。", | ||||
|   "accountPublishers": "你的发布者", | ||||
|   "accountPublishersSubtitle": "管理你的公共形象。", | ||||
|   "accountSettings": "帐号设置", | ||||
|   "accountSettingsSubtitle": "管理你的帐号并让它更好的服务你。", | ||||
|   "accountProfileEdit": "编辑资料", | ||||
|   "accountProfileEditSubtitle": "使你的 Solarpass 账户更像你。", | ||||
|   "accountWallet": "钱包", | ||||
|   "accountWalletSubtitle": "查看你的余额和交易记录。", | ||||
|   "factorSettings": "验证因子", | ||||
|   "factorSettingsSubtitle": "管理你的登陆验证方式。", | ||||
|   "accountProfileEditApplied": "个人资料修改已被应用。", | ||||
|   "publishersNew": "新发布者", | ||||
|   "publisherNewSubtitle": "创建一个新的公共身份。", | ||||
| @@ -177,6 +197,9 @@ | ||||
|     "other": "{} 条评论" | ||||
|   }, | ||||
|   "settingsAppearance": "外观", | ||||
|   "settingsDisplayLanguage": "显示语言", | ||||
|   "settingsDisplayLanguageDescription": "设置应用程序使用的语言", | ||||
|   "settingsDisplayLanguageSystem": "跟随系统", | ||||
|   "settingsBackgroundImage": "背景图片", | ||||
|   "settingsBackgroundImageDescription": "设置应用全局生效的的背景图片。", | ||||
|   "settingsBackgroundImageClear": "清除现存背景图", | ||||
| @@ -529,6 +552,9 @@ | ||||
|   "postImageShareAds": "来 Solar Network 探索更多有趣帖子", | ||||
|   "postShare": "分享", | ||||
|   "postShareImage": "分享帖图", | ||||
|   "postGetInsight": "获取见解", | ||||
|   "postGetInsightTitle": "AI 见解", | ||||
|   "postGetInsightDescription": "AI 可能会出错,检查信息真实性。", | ||||
|   "appInitializing": "正在初始化", | ||||
|   "poweredBy": "由 {} 提供支持", | ||||
|   "shareIntent": "分享", | ||||
| @@ -556,5 +582,25 @@ | ||||
|   "postCategoryKnowledge": "知识", | ||||
|   "postCategoryLiterature": "文学", | ||||
|   "postCategoryFunny": "搞笑", | ||||
|   "postCategoryUncategorized": "未分类" | ||||
|   "postCategoryUncategorized": "未分类", | ||||
|   "newsAllSources": "所有新闻", | ||||
|   "newsReadingProviderSwap": "切换", | ||||
|   "newsReadingFromReader": "你正在从 HyperNet.Reader 阅读文章", | ||||
|   "newsReadingFromOriginal": "你正在阅读原始文章", | ||||
|   "newsDisclaimer": "本文由 HyperNet.Reader 从互联网上获取,我们不担保其内容的真实性,请自行判断。本文章的所有内容版权归原作者所有。", | ||||
|   "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": "{} 源点" | ||||
|   }, | ||||
|   "aiThinkingProcess": "AI 思考过程" | ||||
| } | ||||
|   | ||||
| @@ -15,6 +15,10 @@ | ||||
|   "screenAccountProfileEdit": "編輯資料", | ||||
|   "screenAbuseReport": "濫用檢舉", | ||||
|   "screenSettings": "設置", | ||||
|   "screenAccountSettings": "賬號設置", | ||||
|   "screenFactorSettings": "驗證因子", | ||||
|   "screenAccountWallet": "錢包", | ||||
|   "screenNews": "新聞", | ||||
|   "screenAlbum": "相冊", | ||||
|   "screenChat": "聊天", | ||||
|   "screenChatManage": "編輯聊天頻道", | ||||
| @@ -87,8 +91,18 @@ | ||||
|   }, | ||||
|   "loginEnterPassword": "驗證代碼", | ||||
|   "loginSuccess": "登錄為 {}", | ||||
|   "authFactorDelete": "刪除驗證因子", | ||||
|   "authFactorDeleteDescription": "你確定要刪除 {} 驗證因子嗎?", | ||||
|   "authFactorPassword": "密碼", | ||||
|   "authFactorPasswordDescription": "註冊時選擇設置的密碼。", | ||||
|   "authFactorEmail": "電郵一次性驗證碼", | ||||
|   "authFactorEmailDescription": "由我們生成併發送到綁定的的電子郵箱的一次性驗證碼。", | ||||
|   "authFactorTOTP": "時序驗證碼", | ||||
|   "authFactorTOTPDescription": "使用 Google Authenticator 或 Authy 等驗證器生成的一次性驗證碼。", | ||||
|   "authFactorInAppNotify": "應用內通知驗證碼", | ||||
|   "authFactorInAppNotifyDescription": "通過站內通知推送的一次性驗證碼。", | ||||
|   "authFactorAdd": "添加新驗證因子", | ||||
|   "authFactorAddSubtitle": "給你的帳户登陸時提供另一個方案。", | ||||
|   "accountIntroTitle": "喜歡您來!", | ||||
|   "accountIntroSubtitle": "登陸以探索更廣大的世界。", | ||||
|   "accountLogout": "退出登錄", | ||||
| @@ -97,8 +111,14 @@ | ||||
|   "accountLogoutConfirm": "您需要重新輸入賬號密碼,甚至可能需要多步驗證來再次登陸。", | ||||
|   "accountPublishers": "你的發佈者", | ||||
|   "accountPublishersSubtitle": "管理你的公共形象。", | ||||
|   "accountSettings": "帳號設置", | ||||
|   "accountSettingsSubtitle": "管理你的帳號並讓它更好的服務你。", | ||||
|   "accountProfileEdit": "編輯資料", | ||||
|   "accountProfileEditSubtitle": "使你的 Solarpass 賬户更像你。", | ||||
|   "accountWallet": "錢包", | ||||
|   "accountWalletSubtitle": "查看你的餘額和交易記錄。", | ||||
|   "factorSettings": "驗證因子", | ||||
|   "factorSettingsSubtitle": "管理你的登陸驗證方式。", | ||||
|   "accountProfileEditApplied": "個人資料修改已被應用。", | ||||
|   "publishersNew": "新發布者", | ||||
|   "publisherNewSubtitle": "創建一個新的公共身份。", | ||||
| @@ -177,6 +197,9 @@ | ||||
|     "other": "{} 條評論" | ||||
|   }, | ||||
|   "settingsAppearance": "外觀", | ||||
|   "settingsDisplayLanguage": "顯示語言", | ||||
|   "settingsDisplayLanguageDescription": "設置應用程序使用的語言", | ||||
|   "settingsDisplayLanguageSystem": "跟隨系統", | ||||
|   "settingsBackgroundImage": "背景圖片", | ||||
|   "settingsBackgroundImageDescription": "設置應用全局生效的的背景圖片。", | ||||
|   "settingsBackgroundImageClear": "清除現存背景圖", | ||||
| @@ -194,6 +217,10 @@ | ||||
|   "settingsFeatures": "功能", | ||||
|   "settingsNotifyWithHaptic": "新通知時振動", | ||||
|   "settingsNotifyWithHapticDescription": "在應用在前台時收到新通知出現時出發輕量的振動。", | ||||
|   "settingsExpandPostLink": "展開帖子鏈接", | ||||
|   "settingsExpandPostLinkDescription": "在帖子列表中展開顯示帖子中的鏈接。", | ||||
|   "settingsExpandChatLink": "展開聊天鏈接", | ||||
|   "settingsExpandChatLinkDescription": "在聊天信息中展開顯示內容中的鏈接。", | ||||
|   "settingsNetwork": "網絡", | ||||
|   "settingsNetworkServer": "HyperNet 服務器", | ||||
|   "settingsNetworkServerDescription": "設置 HyperNet 服務器地址,選擇我們提供的,或者自己搭建。", | ||||
| @@ -525,6 +552,9 @@ | ||||
|   "postImageShareAds": "來 Solar Network 探索更多有趣帖子", | ||||
|   "postShare": "分享", | ||||
|   "postShareImage": "分享帖圖", | ||||
|   "postGetInsight": "獲取見解", | ||||
|   "postGetInsightTitle": "AI 見解", | ||||
|   "postGetInsightDescription": "AI 可能會出錯,檢查信息真實性。", | ||||
|   "appInitializing": "正在初始化", | ||||
|   "poweredBy": "由 {} 提供支持", | ||||
|   "shareIntent": "分享", | ||||
| @@ -552,5 +582,25 @@ | ||||
|   "postCategoryKnowledge": "知識", | ||||
|   "postCategoryLiterature": "文學", | ||||
|   "postCategoryFunny": "搞笑", | ||||
|   "postCategoryUncategorized": "未分類" | ||||
|   "postCategoryUncategorized": "未分類", | ||||
|   "newsAllSources": "所有新聞", | ||||
|   "newsReadingProviderSwap": "切換", | ||||
|   "newsReadingFromReader": "你正在從 HyperNet.Reader 閲讀文章", | ||||
|   "newsReadingFromOriginal": "你正在閲讀原始文章", | ||||
|   "newsDisclaimer": "本文由 HyperNet.Reader 從互聯網上獲取,我們不擔保其內容的真實性,請自行判斷。本文章的所有內容版權歸原作者所有。", | ||||
|   "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": "{} 源點" | ||||
|   }, | ||||
|   "aiThinkingProcess": "AI 思考過程" | ||||
| } | ||||
|   | ||||
| @@ -15,6 +15,10 @@ | ||||
|   "screenAccountProfileEdit": "編輯資料", | ||||
|   "screenAbuseReport": "濫用檢舉", | ||||
|   "screenSettings": "設置", | ||||
|   "screenAccountSettings": "賬號設置", | ||||
|   "screenFactorSettings": "驗證因子", | ||||
|   "screenAccountWallet": "錢包", | ||||
|   "screenNews": "新聞", | ||||
|   "screenAlbum": "相冊", | ||||
|   "screenChat": "聊天", | ||||
|   "screenChatManage": "編輯聊天頻道", | ||||
| @@ -87,8 +91,18 @@ | ||||
|   }, | ||||
|   "loginEnterPassword": "驗證代碼", | ||||
|   "loginSuccess": "登錄為 {}", | ||||
|   "authFactorDelete": "刪除驗證因子", | ||||
|   "authFactorDeleteDescription": "你確定要刪除 {} 驗證因子嗎?", | ||||
|   "authFactorPassword": "密碼", | ||||
|   "authFactorPasswordDescription": "註冊時選擇設置的密碼。", | ||||
|   "authFactorEmail": "電郵一次性驗證碼", | ||||
|   "authFactorEmailDescription": "由我們生成併發送到綁定的的電子郵箱的一次性驗證碼。", | ||||
|   "authFactorTOTP": "時序驗證碼", | ||||
|   "authFactorTOTPDescription": "使用 Google Authenticator 或 Authy 等驗證器生成的一次性驗證碼。", | ||||
|   "authFactorInAppNotify": "應用內通知驗證碼", | ||||
|   "authFactorInAppNotifyDescription": "通過站內通知推送的一次性驗證碼。", | ||||
|   "authFactorAdd": "添加新驗證因子", | ||||
|   "authFactorAddSubtitle": "給你的帳戶登陸時提供另一個方案。", | ||||
|   "accountIntroTitle": "喜歡您來!", | ||||
|   "accountIntroSubtitle": "登陸以探索更廣大的世界。", | ||||
|   "accountLogout": "退出登錄", | ||||
| @@ -97,8 +111,14 @@ | ||||
|   "accountLogoutConfirm": "您需要重新輸入賬號密碼,甚至可能需要多步驗證來再次登陸。", | ||||
|   "accountPublishers": "你的發佈者", | ||||
|   "accountPublishersSubtitle": "管理你的公共形象。", | ||||
|   "accountSettings": "帳號設置", | ||||
|   "accountSettingsSubtitle": "管理你的帳號並讓它更好的服務你。", | ||||
|   "accountProfileEdit": "編輯資料", | ||||
|   "accountProfileEditSubtitle": "使你的 Solarpass 賬戶更像你。", | ||||
|   "accountWallet": "錢包", | ||||
|   "accountWalletSubtitle": "查看你的餘額和交易記錄。", | ||||
|   "factorSettings": "驗證因子", | ||||
|   "factorSettingsSubtitle": "管理你的登陸驗證方式。", | ||||
|   "accountProfileEditApplied": "個人資料修改已被應用。", | ||||
|   "publishersNew": "新發布者", | ||||
|   "publisherNewSubtitle": "創建一個新的公共身份。", | ||||
| @@ -177,6 +197,9 @@ | ||||
|     "other": "{} 條評論" | ||||
|   }, | ||||
|   "settingsAppearance": "外觀", | ||||
|   "settingsDisplayLanguage": "顯示語言", | ||||
|   "settingsDisplayLanguageDescription": "設置應用程序使用的語言", | ||||
|   "settingsDisplayLanguageSystem": "跟隨系統", | ||||
|   "settingsBackgroundImage": "背景圖片", | ||||
|   "settingsBackgroundImageDescription": "設置應用全局生效的的背景圖片。", | ||||
|   "settingsBackgroundImageClear": "清除現存背景圖", | ||||
| @@ -194,6 +217,10 @@ | ||||
|   "settingsFeatures": "功能", | ||||
|   "settingsNotifyWithHaptic": "新通知時振動", | ||||
|   "settingsNotifyWithHapticDescription": "在應用在前臺時收到新通知出現時出發輕量的振動。", | ||||
|   "settingsExpandPostLink": "展開帖子鏈接", | ||||
|   "settingsExpandPostLinkDescription": "在帖子列表中展開顯示帖子中的鏈接。", | ||||
|   "settingsExpandChatLink": "展開聊天鏈接", | ||||
|   "settingsExpandChatLinkDescription": "在聊天信息中展開顯示內容中的鏈接。", | ||||
|   "settingsNetwork": "網絡", | ||||
|   "settingsNetworkServer": "HyperNet 服務器", | ||||
|   "settingsNetworkServerDescription": "設置 HyperNet 服務器地址,選擇我們提供的,或者自己搭建。", | ||||
| @@ -525,6 +552,9 @@ | ||||
|   "postImageShareAds": "來 Solar Network 探索更多有趣帖子", | ||||
|   "postShare": "分享", | ||||
|   "postShareImage": "分享帖圖", | ||||
|   "postGetInsight": "獲取見解", | ||||
|   "postGetInsightTitle": "AI 見解", | ||||
|   "postGetInsightDescription": "AI 可能會出錯,檢查信息真實性。", | ||||
|   "appInitializing": "正在初始化", | ||||
|   "poweredBy": "由 {} 提供支持", | ||||
|   "shareIntent": "分享", | ||||
| @@ -552,5 +582,25 @@ | ||||
|   "postCategoryKnowledge": "知識", | ||||
|   "postCategoryLiterature": "文學", | ||||
|   "postCategoryFunny": "搞笑", | ||||
|   "postCategoryUncategorized": "未分類" | ||||
|   "postCategoryUncategorized": "未分類", | ||||
|   "newsAllSources": "所有新聞", | ||||
|   "newsReadingProviderSwap": "切換", | ||||
|   "newsReadingFromReader": "你正在從 HyperNet.Reader 閱讀文章", | ||||
|   "newsReadingFromOriginal": "你正在閱讀原始文章", | ||||
|   "newsDisclaimer": "本文由 HyperNet.Reader 從互聯網上獲取,我們不擔保其內容的真實性,請自行判斷。本文章的所有內容版權歸原作者所有。", | ||||
|   "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": "{} 源點" | ||||
|   }, | ||||
|   "aiThinkingProcess": "AI 思考過程" | ||||
| } | ||||
|   | ||||
| @@ -53,14 +53,14 @@ PODS: | ||||
|   - Firebase/Messaging (11.6.0): | ||||
|     - Firebase/CoreOnly | ||||
|     - FirebaseMessaging (~> 11.6.0) | ||||
|   - firebase_analytics (11.4.0): | ||||
|   - firebase_analytics (11.4.1): | ||||
|     - Firebase/Analytics (= 11.6.0) | ||||
|     - firebase_core | ||||
|     - Flutter | ||||
|   - firebase_core (3.10.0): | ||||
|   - firebase_core (3.10.1): | ||||
|     - Firebase/CoreOnly (= 11.6.0) | ||||
|     - Flutter | ||||
|   - firebase_messaging (15.2.0): | ||||
|   - firebase_messaging (15.2.1): | ||||
|     - Firebase/Messaging (= 11.6.0) | ||||
|     - firebase_core | ||||
|     - Flutter | ||||
| @@ -105,6 +105,13 @@ PODS: | ||||
|   - Flutter (1.0.0) | ||||
|   - flutter_app_update (0.0.1): | ||||
|     - Flutter | ||||
|   - flutter_inappwebview_ios (0.0.1): | ||||
|     - Flutter | ||||
|     - flutter_inappwebview_ios/Core (= 0.0.1) | ||||
|     - OrderedSet (~> 6.0.3) | ||||
|   - flutter_inappwebview_ios/Core (0.0.1): | ||||
|     - Flutter | ||||
|     - OrderedSet (~> 6.0.3) | ||||
|   - flutter_native_splash (2.4.3): | ||||
|     - Flutter | ||||
|   - flutter_udid (0.0.1): | ||||
| @@ -188,6 +195,7 @@ PODS: | ||||
|     - nanopb/encode (= 3.30910.0) | ||||
|   - nanopb/decode (3.30910.0) | ||||
|   - nanopb/encode (3.30910.0) | ||||
|   - OrderedSet (6.0.3) | ||||
|   - package_info_plus (0.4.5): | ||||
|     - Flutter | ||||
|   - pasteboard (0.0.1): | ||||
| @@ -239,6 +247,7 @@ DEPENDENCIES: | ||||
|   - firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`) | ||||
|   - Flutter (from `Flutter`) | ||||
|   - flutter_app_update (from `.symlinks/plugins/flutter_app_update/ios`) | ||||
|   - flutter_inappwebview_ios (from `.symlinks/plugins/flutter_inappwebview_ios/ios`) | ||||
|   - flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`) | ||||
|   - flutter_udid (from `.symlinks/plugins/flutter_udid/ios`) | ||||
|   - flutter_webrtc (from `.symlinks/plugins/flutter_webrtc/ios`) | ||||
| @@ -282,6 +291,7 @@ SPEC REPOS: | ||||
|     - GoogleUtilities | ||||
|     - Kingfisher | ||||
|     - nanopb | ||||
|     - OrderedSet | ||||
|     - PromisesObjC | ||||
|     - SAMKeychain | ||||
|     - SDWebImage | ||||
| @@ -309,6 +319,8 @@ EXTERNAL SOURCES: | ||||
|     :path: Flutter | ||||
|   flutter_app_update: | ||||
|     :path: ".symlinks/plugins/flutter_app_update/ios" | ||||
|   flutter_inappwebview_ios: | ||||
|     :path: ".symlinks/plugins/flutter_inappwebview_ios/ios" | ||||
|   flutter_native_splash: | ||||
|     :path: ".symlinks/plugins/flutter_native_splash/ios" | ||||
|   flutter_udid: | ||||
| @@ -367,12 +379,12 @@ SPEC CHECKSUMS: | ||||
|   device_info_plus: bf2e3232933866d73fe290f2942f2156cdd10342 | ||||
|   DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c | ||||
|   DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60 | ||||
|   file_picker: 09aa5ec1ab24135ccd7a1621c46c84134bfd6655 | ||||
|   file_picker: b159e0c068aef54932bb15dc9fd1571818edaf49 | ||||
|   file_saver: 503e386464dbe118f630e17b4c2e1190fa0cf808 | ||||
|   Firebase: 374a441a91ead896215703a674d58cdb3e9d772b | ||||
|   firebase_analytics: 07bd7cfbac54bfcdccf2bb2530f9a65486f7ef3f | ||||
|   firebase_core: feb37e79f775c2bd08dd35e02d83678291317e10 | ||||
|   firebase_messaging: e2f0ba891b1509668c07f5099761518a5af8fe3c | ||||
|   firebase_analytics: 13ea4ad8a42c5060bad7e6694304dabb8b02fe7e | ||||
|   firebase_core: e2aa06dbd854d961f8ce46c2e20933bee1bf2d2b | ||||
|   firebase_messaging: 96cf6d67121b3f39746b2a4f29a26c0eee4af70e | ||||
|   FirebaseAnalytics: 7114c698cac995602e3b1b96663473e50d54d6e7 | ||||
|   FirebaseCore: 48b0dd707581cf9c1a1220da68223fb0a562afaa | ||||
|   FirebaseCoreInternal: d98ab91e2d80a56d7b246856a8885443b302c0c2 | ||||
| @@ -380,6 +392,7 @@ SPEC CHECKSUMS: | ||||
|   FirebaseMessaging: e1aca1fcc23e8b9eddb0e33f375ff90944623021 | ||||
|   Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 | ||||
|   flutter_app_update: 65f61da626cb111d1b24674abc4b01728d7723bc | ||||
|   flutter_inappwebview_ios: 6f63631e2c62a7c350263b13fa5427aedefe81d4 | ||||
|   flutter_native_splash: f71420956eb811e6d310720fee915f1d42852e7a | ||||
|   flutter_udid: b2417673f287ee62817a1de3d1643f47b9f508ab | ||||
|   flutter_webrtc: 90260f83024b1b96d239a575ea4e3708e79344d1 | ||||
| @@ -396,6 +409,7 @@ SPEC CHECKSUMS: | ||||
|   media_kit_native_event_loop: e6b2ab20cf0746eb1c33be961fcf79667304fa2a | ||||
|   media_kit_video: 5da63f157170e5bf303bf85453b7ef6971218a2e | ||||
|   nanopb: fad817b59e0457d11a5dfbde799381cd727c1275 | ||||
|   OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94 | ||||
|   package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4 | ||||
|   pasteboard: 982969ebaa7c78af3e6cc7761e8f5e77565d9ce0 | ||||
|   path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 | ||||
|   | ||||
| @@ -260,7 +260,7 @@ class _AppSplashScreenState extends State<_AppSplashScreen> { | ||||
|     try { | ||||
|       final cfg = context.read<ConfigProvider>(); | ||||
|       WidgetsBinding.instance.addPostFrameCallback((_) { | ||||
|         cfg.calcDrawerSize(context, withMediaQuery: true); | ||||
|         cfg.calcDrawerSize(context); | ||||
|       }); | ||||
|       final home = context.read<HomeWidgetProvider>(); | ||||
|       await home.initialize(); | ||||
|   | ||||
| @@ -50,7 +50,7 @@ class ConfigProvider extends ChangeNotifier { | ||||
|     } else { | ||||
|       final rpb = ResponsiveBreakpoints.of(context); | ||||
|       newDrawerIsCollapsed = rpb.smallerOrEqualTo(MOBILE); | ||||
|       newDrawerIsCollapsed = rpb.largerThan(TABLET) | ||||
|       newDrawerIsExpanded = rpb.largerThan(TABLET) | ||||
|           ? (prefs.getBool(kAppDrawerPreferCollapse) ?? false) | ||||
|               ? false | ||||
|               : true | ||||
|   | ||||
| @@ -58,6 +58,11 @@ class NavigationProvider extends ChangeNotifier { | ||||
|       screen: 'realm', | ||||
|       label: 'screenRealm', | ||||
|     ), | ||||
|     AppNavDestination( | ||||
|       icon: Icon(Symbols.newspaper, weight: 400, opticalSize: 20), | ||||
|       screen: 'news', | ||||
|       label: 'screenNews', | ||||
|     ), | ||||
|     AppNavDestination( | ||||
|       icon: Icon(Symbols.photo_library, weight: 400, opticalSize: 20), | ||||
|       screen: 'album', | ||||
| @@ -83,8 +88,7 @@ class NavigationProvider extends ChangeNotifier { | ||||
|  | ||||
|   List<AppNavDestination> destinations = []; | ||||
|  | ||||
|   int get pinnedDestinationCount => | ||||
|       destinations.where((ele) => ele.isPinned).length; | ||||
|   int get pinnedDestinationCount => destinations.where((ele) => ele.isPinned).length; | ||||
|  | ||||
|   NavigationProvider() { | ||||
|     buildDestinations(kDefaultPinnedDestination); | ||||
| @@ -113,17 +117,13 @@ class NavigationProvider extends ChangeNotifier { | ||||
|   } | ||||
|  | ||||
|   bool isIndexInRange(int min, int max) { | ||||
|     return _currentIndex != null && | ||||
|         _currentIndex! >= min && | ||||
|         _currentIndex! < max; | ||||
|     return _currentIndex != null && _currentIndex! >= min && _currentIndex! < max; | ||||
|   } | ||||
|  | ||||
|   void autoDetectIndex(GoRouter? state) { | ||||
|     if (state == null) return; | ||||
|     final idx = destinations.indexWhere( | ||||
|       (ele) => | ||||
|           ele.screen == | ||||
|           state.routerDelegate.currentConfiguration.last.route.name, | ||||
|       (ele) => ele.screen == state.routerDelegate.currentConfiguration.last.route.name, | ||||
|     ); | ||||
|     _currentIndex = idx == -1 ? null : idx; | ||||
|     notifyListeners(); | ||||
|   | ||||
							
								
								
									
										169
									
								
								lib/router.dart
									
									
									
									
									
								
							
							
						
						
									
										169
									
								
								lib/router.dart
									
									
									
									
									
								
							| @@ -3,6 +3,8 @@ import 'package:flutter/material.dart'; | ||||
| import 'package:go_router/go_router.dart'; | ||||
| import 'package:surface/screens/abuse_report.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_edit.dart'; | ||||
| import 'package:surface/screens/account/publishers/publisher_edit.dart'; | ||||
| @@ -19,6 +21,8 @@ import 'package:surface/screens/chat/room.dart'; | ||||
| import 'package:surface/screens/explore.dart'; | ||||
| import 'package:surface/screens/friend.dart'; | ||||
| import 'package:surface/screens/home.dart'; | ||||
| import 'package:surface/screens/news/news_detail.dart'; | ||||
| import 'package:surface/screens/news/news_list.dart'; | ||||
| import 'package:surface/screens/notification.dart'; | ||||
| import 'package:surface/screens/post/post_detail.dart'; | ||||
| import 'package:surface/screens/post/post_editor.dart'; | ||||
| @@ -29,9 +33,9 @@ import 'package:surface/screens/realm/manage.dart'; | ||||
| import 'package:surface/screens/realm/realm_detail.dart'; | ||||
| import 'package:surface/screens/settings.dart'; | ||||
| import 'package:surface/screens/sharing.dart'; | ||||
| import 'package:surface/screens/wallet.dart'; | ||||
| import 'package:surface/types/post.dart'; | ||||
| import 'package:surface/widgets/about.dart'; | ||||
| import 'package:surface/widgets/navigation/app_background.dart'; | ||||
| import 'package:surface/widgets/navigation/app_scaffold.dart'; | ||||
|  | ||||
| Widget _fadeThroughTransition( | ||||
| @@ -48,18 +52,12 @@ final _appRoutes = [ | ||||
|   GoRoute( | ||||
|     path: '/', | ||||
|     name: 'home', | ||||
|     pageBuilder: (context, state) => CustomTransitionPage( | ||||
|       transitionsBuilder: _fadeThroughTransition, | ||||
|       child: const HomeScreen(), | ||||
|     ), | ||||
|     builder: (context, state) => const HomeScreen(), | ||||
|   ), | ||||
|   GoRoute( | ||||
|     path: '/posts', | ||||
|     name: 'explore', | ||||
|     pageBuilder: (context, state) => CustomTransitionPage( | ||||
|       transitionsBuilder: _fadeThroughTransition, | ||||
|       child: const ExploreScreen(), | ||||
|     ), | ||||
|     builder: (context, state) => const ExploreScreen(), | ||||
|     routes: [ | ||||
|       GoRoute( | ||||
|         path: '/write/:mode', | ||||
| @@ -101,67 +99,86 @@ final _appRoutes = [ | ||||
|       ), | ||||
|     ], | ||||
|   ), | ||||
|   GoRoute( | ||||
|     path: '/account', | ||||
|     name: 'account', | ||||
|     pageBuilder: (context, state) => CustomTransitionPage( | ||||
|       transitionsBuilder: _fadeThroughTransition, | ||||
|       child: const AccountScreen(), | ||||
|   GoRoute(path: '/account', name: 'account', builder: (context, state) => const AccountScreen(), routes: [ | ||||
|     GoRoute( | ||||
|       path: '/wallet', | ||||
|       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( | ||||
|     path: '/chat', | ||||
|     name: 'chat', | ||||
|     pageBuilder: (context, state) => CustomTransitionPage( | ||||
|       transitionsBuilder: _fadeThroughTransition, | ||||
|       child: const ChatScreen(), | ||||
|     ), | ||||
|     builder: (context, state) => const ChatScreen(), | ||||
|     routes: [ | ||||
|       GoRoute( | ||||
|         path: '/:scope/:alias', | ||||
|         name: 'chatRoom', | ||||
|         builder: (context, state) => AppBackground( | ||||
|           child: ChatRoomScreen( | ||||
|             scope: state.pathParameters['scope']!, | ||||
|             alias: state.pathParameters['alias']!, | ||||
|           ), | ||||
|         builder: (context, state) => ChatRoomScreen( | ||||
|           scope: state.pathParameters['scope']!, | ||||
|           alias: state.pathParameters['alias']!, | ||||
|         ), | ||||
|       ), | ||||
|       GoRoute( | ||||
|         path: '/:scope/:alias/call', | ||||
|         name: 'chatCallRoom', | ||||
|         builder: (context, state) => AppBackground( | ||||
|           child: CallRoomScreen( | ||||
|             scope: state.pathParameters['scope']!, | ||||
|             alias: state.pathParameters['alias']!, | ||||
|           ), | ||||
|         builder: (context, state) => CallRoomScreen( | ||||
|           scope: state.pathParameters['scope']!, | ||||
|           alias: state.pathParameters['alias']!, | ||||
|         ), | ||||
|       ), | ||||
|       GoRoute( | ||||
|         path: '/:scope/:alias/detail', | ||||
|         name: 'channelDetail', | ||||
|         builder: (context, state) => AppBackground( | ||||
|           child: ChannelDetailScreen( | ||||
|             scope: state.pathParameters['scope']!, | ||||
|             alias: state.pathParameters['alias']!, | ||||
|           ), | ||||
|         builder: (context, state) => ChannelDetailScreen( | ||||
|           scope: state.pathParameters['scope']!, | ||||
|           alias: state.pathParameters['alias']!, | ||||
|         ), | ||||
|       ), | ||||
|       GoRoute( | ||||
|         path: '/manage', | ||||
|         name: 'chatManage', | ||||
|         pageBuilder: (context, state) => CustomTransitionPage( | ||||
|           child: ChatManageScreen( | ||||
|             editingChannelAlias: state.uri.queryParameters['editing'], | ||||
|           ), | ||||
|           transitionsBuilder: (context, animation, secondaryAnimation, child) { | ||||
|             return FadeThroughTransition( | ||||
|               animation: animation, | ||||
|               secondaryAnimation: secondaryAnimation, | ||||
|               fillColor: Colors.transparent, | ||||
|               child: child, | ||||
|             ); | ||||
|           }, | ||||
|         builder: (context, state) => ChatManageScreen( | ||||
|           editingChannelAlias: state.uri.queryParameters['editing'], | ||||
|         ), | ||||
|       ), | ||||
|     ], | ||||
| @@ -182,36 +199,35 @@ final _appRoutes = [ | ||||
|       GoRoute( | ||||
|         path: '/manage', | ||||
|         name: 'realmManage', | ||||
|         pageBuilder: (context, state) => CustomTransitionPage( | ||||
|           transitionsBuilder: _fadeThroughTransition, | ||||
|           child: RealmManageScreen( | ||||
|             editingRealmAlias: state.uri.queryParameters['editing'], | ||||
|           ), | ||||
|         builder: (context, state) => RealmManageScreen( | ||||
|           editingRealmAlias: state.uri.queryParameters['editing'], | ||||
|         ), | ||||
|       ), | ||||
|     ], | ||||
|   ), | ||||
|   GoRoute(path: '/news', name: 'news', builder: (context, state) => const NewsScreen(), routes: [ | ||||
|     GoRoute( | ||||
|       path: '/:hash', | ||||
|       name: 'newsDetail', | ||||
|       builder: (context, state) => NewsDetailScreen( | ||||
|         hash: state.pathParameters['hash']!, | ||||
|       ), | ||||
|     ), | ||||
|   ]), | ||||
|   GoRoute( | ||||
|     path: '/album', | ||||
|     name: 'album', | ||||
|     pageBuilder: (context, state) => CustomTransitionPage( | ||||
|       transitionsBuilder: _fadeThroughTransition, | ||||
|       child: const AlbumScreen(), | ||||
|     ), | ||||
|     builder: (context, state) => const AlbumScreen(), | ||||
|   ), | ||||
|   GoRoute( | ||||
|     path: '/friend', | ||||
|     name: 'friend', | ||||
|     pageBuilder: (context, state) => NoTransitionPage( | ||||
|       child: const FriendScreen(), | ||||
|     ), | ||||
|     builder: (context, state) => const FriendScreen(), | ||||
|   ), | ||||
|   GoRoute( | ||||
|     path: '/notification', | ||||
|     name: 'notification', | ||||
|     pageBuilder: (context, state) => NoTransitionPage( | ||||
|       child: const NotificationScreen(), | ||||
|     ), | ||||
|     builder: (context, state) => const NotificationScreen(), | ||||
|   ), | ||||
|   GoRoute( | ||||
|     path: '/auth/login', | ||||
| @@ -228,35 +244,6 @@ final _appRoutes = [ | ||||
|     name: 'abuseReport', | ||||
|     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( | ||||
|     path: '/settings', | ||||
|     name: 'settings', | ||||
|   | ||||
| @@ -1,3 +1,5 @@ | ||||
| import 'dart:ui'; | ||||
|  | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:flutter/material.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/dialog.dart'; | ||||
| import 'package:surface/widgets/navigation/app_scaffold.dart'; | ||||
| import 'package:surface/widgets/universal_image.dart'; | ||||
|  | ||||
| class AccountScreen extends StatelessWidget { | ||||
|   const AccountScreen({super.key}); | ||||
| @@ -20,11 +23,39 @@ class AccountScreen extends StatelessWidget { | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     final ua = context.watch<UserProvider>(); | ||||
|     final sn = context.read<SnNetworkProvider>(); | ||||
|  | ||||
|     return AppScaffold( | ||||
|       appBar: AppBar( | ||||
|         leading: AutoAppBarLeading(), | ||||
|         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: [ | ||||
|           IconButton( | ||||
|             icon: const Icon(Symbols.settings, fill: 1), | ||||
| @@ -83,16 +114,6 @@ class _AuthorizedAccountScreen extends StatelessWidget { | ||||
|             ); | ||||
|           }).padding(all: 20), | ||||
|         ).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( | ||||
|           title: Text('accountPublishers').tr(), | ||||
|           subtitle: Text('accountPublishersSubtitle').tr(), | ||||
| @@ -113,6 +134,36 @@ class _AuthorizedAccountScreen extends StatelessWidget { | ||||
|             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( | ||||
|           title: Text('accountLogout').tr(), | ||||
|           subtitle: Text('accountLogoutSubtitle').tr(), | ||||
| @@ -134,33 +185,6 @@ class _AuthorizedAccountScreen extends StatelessWidget { | ||||
|             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:surface/providers/sn_network.dart'; | ||||
| import 'package:surface/providers/userinfo.dart'; | ||||
| import 'package:surface/screens/account/factor_settings.dart'; | ||||
| import 'package:surface/types/auth.dart'; | ||||
| import 'package:surface/widgets/dialog.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'; | ||||
|  | ||||
| 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 { | ||||
|   const LoginScreen({super.key}); | ||||
|  | ||||
| @@ -212,7 +208,9 @@ class _LoginCheckScreenState extends State<_LoginCheckScreen> { | ||||
|           controller: _passwordController, | ||||
|           obscureText: true, | ||||
|           autofillHints: [ | ||||
|             (_factorLabelMap[widget.factor!.type]?.$3 ?? true) ? AutofillHints.password : AutofillHints.oneTimeCode | ||||
|             widget.factor!.type == 0 | ||||
|                 ? AutofillHints.password | ||||
|                 : AutofillHints.oneTimeCode | ||||
|           ], | ||||
|           decoration: InputDecoration( | ||||
|             isDense: true, | ||||
| @@ -267,7 +265,8 @@ class _LoginPickerScreenState extends State<_LoginPickerScreen> { | ||||
|   bool _isBusy = false; | ||||
|   int? _factorPicked; | ||||
|  | ||||
|   Color get _unFocusColor => Theme.of(context).colorScheme.onSurface.withAlpha((255 * 0.75).round()); | ||||
|   Color get _unFocusColor => | ||||
|       Theme.of(context).colorScheme.onSurface.withAlpha((255 * 0.75).round()); | ||||
|  | ||||
|   void _performGetFactorCode() async { | ||||
|     if (_factorPicked == null) return; | ||||
| @@ -328,11 +327,11 @@ class _LoginPickerScreenState extends State<_LoginPickerScreen> { | ||||
|                           ), | ||||
|                         ), | ||||
|                         secondary: Icon( | ||||
|                           _factorLabelMap[x.type]?.$2 ?? Symbols.question_mark, | ||||
|                           kFactorTypes[x.type]?.$3 ?? Symbols.question_mark, | ||||
|                         ), | ||||
|                         title: Text( | ||||
|                           _factorLabelMap[x.type]?.$1 ?? 'unknown'.tr(), | ||||
|                         ), | ||||
|                           kFactorTypes[x.type]?.$1 ?? 'unknown', | ||||
|                         ).tr(), | ||||
|                         enabled: !widget.ticket!.factorTrail.contains(x.id), | ||||
|                         value: _factorPicked == x.id, | ||||
|                         onChanged: (value) { | ||||
| @@ -408,11 +407,14 @@ class _LoginLookupScreenState extends State<_LoginLookupScreen> { | ||||
|  | ||||
|     try { | ||||
|       final sn = context.read<SnNetworkProvider>(); | ||||
|       final lookupResp = await sn.client.get('/cgi/id/users/lookup?probe=$username'); | ||||
|       final lookupResp = | ||||
|           await sn.client.get('/cgi/id/users/lookup?probe=$username'); | ||||
|       await sn.client.post('/cgi/id/users/me/password-reset', data: { | ||||
|         'user_id': lookupResp.data['id'], | ||||
|       }); | ||||
|       if (mounted) context.showModalDialog('done'.tr(), 'signinResetPasswordSent'.tr()); | ||||
|       if (mounted) { | ||||
|         context.showModalDialog('done'.tr(), 'signinResetPasswordSent'.tr()); | ||||
|       } | ||||
|     } catch (err) { | ||||
|       if (mounted) context.showErrorDialog(err); | ||||
|     } finally { | ||||
| @@ -437,7 +439,8 @@ class _LoginLookupScreenState extends State<_LoginLookupScreen> { | ||||
|       widget.onTicket(result.ticket); | ||||
|  | ||||
|       // Pull factors | ||||
|       final factorResp = await sn.client.get('/cgi/id/auth/factors', queryParameters: { | ||||
|       final factorResp = | ||||
|           await sn.client.get('/cgi/id/auth/factors', queryParameters: { | ||||
|         'ticketId': result.ticket!.id.toString(), | ||||
|       }); | ||||
|       widget.onFactor( | ||||
| @@ -531,7 +534,10 @@ class _LoginLookupScreenState extends State<_LoginLookupScreen> { | ||||
|                     'termAcceptNextWithAgree'.tr(), | ||||
|                     textAlign: TextAlign.end, | ||||
|                     style: Theme.of(context).textTheme.bodySmall!.copyWith( | ||||
|                           color: Theme.of(context).colorScheme.onSurface.withAlpha((255 * 0.75).round()), | ||||
|                           color: Theme.of(context) | ||||
|                               .colorScheme | ||||
|                               .onSurface | ||||
|                               .withAlpha((255 * 0.75).round()), | ||||
|                         ), | ||||
|                   ), | ||||
|                   Material( | ||||
|   | ||||
| @@ -7,6 +7,7 @@ import 'package:go_router/go_router.dart'; | ||||
| import 'package:material_symbols_icons/symbols.dart'; | ||||
| import 'package:provider/provider.dart'; | ||||
| import 'package:styled_widget/styled_widget.dart'; | ||||
| import 'package:surface/providers/config.dart'; | ||||
| import 'package:surface/providers/post.dart'; | ||||
| import 'package:surface/providers/sn_network.dart'; | ||||
| import 'package:surface/screens/post/post_detail.dart'; | ||||
| @@ -96,6 +97,8 @@ class _ExploreScreenState extends State<ExploreScreen> { | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     final cfg = context.read<ConfigProvider>(); | ||||
|  | ||||
|     return AppScaffold( | ||||
|       floatingActionButtonLocation: ExpandableFab.location, | ||||
|       floatingActionButton: ExpandableFab( | ||||
| @@ -243,8 +246,10 @@ class _ExploreScreenState extends State<ExploreScreen> { | ||||
|                     ), | ||||
|                     openColor: Colors.transparent, | ||||
|                     openElevation: 0, | ||||
|                     closedColor: Theme.of(context).colorScheme.surfaceContainerLow.withOpacity(0.75), | ||||
|                     transitionType: ContainerTransitionType.fade, | ||||
|                     closedColor: Theme.of(context).colorScheme.surfaceContainerLow.withOpacity( | ||||
|                           cfg.prefs.getBool(kAppBackgroundStoreKey) == true ? 0.75 : 1, | ||||
|                         ), | ||||
|                     closedShape: const RoundedRectangleBorder( | ||||
|                       borderRadius: BorderRadius.all(Radius.circular(16)), | ||||
|                     ), | ||||
|   | ||||
| @@ -9,6 +9,7 @@ import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; | ||||
| import 'package:gap/gap.dart'; | ||||
| import 'package:go_router/go_router.dart'; | ||||
| import 'package:google_fonts/google_fonts.dart'; | ||||
| import 'package:html/parser.dart'; | ||||
| import 'package:material_symbols_icons/symbols.dart'; | ||||
| import 'package:provider/provider.dart'; | ||||
| import 'package:relative_time/relative_time.dart'; | ||||
| @@ -22,6 +23,7 @@ import 'package:surface/providers/special_day.dart'; | ||||
| import 'package:surface/providers/userinfo.dart'; | ||||
| import 'package:surface/providers/widget.dart'; | ||||
| import 'package:surface/types/check_in.dart'; | ||||
| import 'package:surface/types/news.dart'; | ||||
| import 'package:surface/types/post.dart'; | ||||
| import 'package:surface/widgets/app_bar_leading.dart'; | ||||
| import 'package:surface/widgets/dialog.dart'; | ||||
| @@ -49,12 +51,12 @@ class HomeScreen extends StatefulWidget { | ||||
| } | ||||
|  | ||||
| class _HomeScreenState extends State<HomeScreen> { | ||||
|   static const List<HomeScreenDashEntry> kCards = [ | ||||
|   late final List<HomeScreenDashEntry> kCards = [ | ||||
|     HomeScreenDashEntry( | ||||
|       name: 'dashEntryRecommendation', | ||||
|       cols: 2, | ||||
|       rows: 2, | ||||
|       child: _HomeDashRecommendationPostWidget(), | ||||
|       rows: 2, | ||||
|       cols: 2, | ||||
|     ), | ||||
|     HomeScreenDashEntry( | ||||
|       name: 'dashEntryCheckIn', | ||||
| @@ -64,6 +66,11 @@ class _HomeScreenState extends State<HomeScreen> { | ||||
|       name: 'dashEntryNotification', | ||||
|       child: _HomeDashNotificationWidget(), | ||||
|     ), | ||||
|     HomeScreenDashEntry( | ||||
|       name: 'dashEntryTodayNews', | ||||
|       child: _HomeDashTodayNews(), | ||||
|       cols: MediaQuery.of(context).size.width >= 640 ? 3 : 2, | ||||
|     ), | ||||
|   ]; | ||||
|  | ||||
|   @override | ||||
| @@ -230,6 +237,105 @@ class _HomeDashSpecialDayWidgetState extends State<_HomeDashSpecialDayWidget> { | ||||
|   } | ||||
| } | ||||
|  | ||||
| class _HomeDashTodayNews extends StatefulWidget { | ||||
|   const _HomeDashTodayNews(); | ||||
|  | ||||
|   @override | ||||
|   State<_HomeDashTodayNews> createState() => _HomeDashTodayNewsState(); | ||||
| } | ||||
|  | ||||
| class _HomeDashTodayNewsState extends State<_HomeDashTodayNews> { | ||||
|   SnNewsArticle? _article; | ||||
|  | ||||
|   Future<void> _fetchArticle() async { | ||||
|     try { | ||||
|       final sn = context.read<SnNetworkProvider>(); | ||||
|       final resp = await sn.client.get('/cgi/re/news/today'); | ||||
|       _article = SnNewsArticle.fromJson(resp.data['data']); | ||||
|     } catch (err) { | ||||
|       if (!mounted) return; | ||||
|       context.showErrorDialog(err); | ||||
|       rethrow; | ||||
|     } finally { | ||||
|       setState(() {}); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   initState() { | ||||
|     super.initState(); | ||||
|     _fetchArticle(); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return Card( | ||||
|       child: Column( | ||||
|         crossAxisAlignment: CrossAxisAlignment.start, | ||||
|         children: [ | ||||
|           Row( | ||||
|             children: [ | ||||
|               const Icon(Symbols.newspaper), | ||||
|               const Gap(8), | ||||
|               Text( | ||||
|                 'newsToday', | ||||
|                 style: Theme.of(context).textTheme.titleLarge, | ||||
|               ).tr() | ||||
|             ], | ||||
|           ).padding(horizontal: 18, top: 12, bottom: 8), | ||||
|           if (_article != null) | ||||
|             Expanded( | ||||
|               child: InkWell( | ||||
|                 borderRadius: BorderRadius.all(Radius.circular(8)), | ||||
|                 child: Column( | ||||
|                   spacing: 4, | ||||
|                   children: [ | ||||
|                     Text( | ||||
|                       _article!.title, | ||||
|                       style: Theme.of(context).textTheme.titleMedium!.copyWith(fontSize: 18), | ||||
|                       maxLines: MediaQuery.of(context).size.width >= 640 ? 2 : 1, | ||||
|                       overflow: TextOverflow.ellipsis, | ||||
|                     ), | ||||
|                     Text( | ||||
|                       parse(_article!.description).children.map((e) => e.text.trim()).join(), | ||||
|                       maxLines: 3, | ||||
|                       overflow: TextOverflow.ellipsis, | ||||
|                       style: Theme.of(context).textTheme.bodyMedium, | ||||
|                     ), | ||||
|                     Builder(builder: (context) { | ||||
|                       final date = _article!.publishedAt ?? _article!.createdAt; | ||||
|                       return Row( | ||||
|                         crossAxisAlignment: CrossAxisAlignment.center, | ||||
|                         spacing: 2, | ||||
|                         children: [ | ||||
|                           Text(DateFormat().format(date)).textStyle(Theme.of(context).textTheme.bodySmall!), | ||||
|                           Text(' · ').textStyle(Theme.of(context).textTheme.bodySmall!).bold(), | ||||
|                           Text(RelativeTime(context).format(date)).textStyle(Theme.of(context).textTheme.bodySmall!), | ||||
|                         ], | ||||
|                       ).opacity(0.75); | ||||
|                     }), | ||||
|                   ], | ||||
|                 ).padding(horizontal: 16), | ||||
|                 onTap: () { | ||||
|                   GoRouter.of(context).pushNamed( | ||||
|                     'newsDetail', | ||||
|                     pathParameters: {'hash': _article!.hash}, | ||||
|                   ); | ||||
|                 }, | ||||
|               ), | ||||
|             ) | ||||
|           else | ||||
|             Expanded( | ||||
|               child: Center( | ||||
|                 child: CircularProgressIndicator(), | ||||
|               ), | ||||
|             ) | ||||
|         ], | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|  | ||||
| class _HomeDashCheckInWidget extends StatefulWidget { | ||||
|   const _HomeDashCheckInWidget(); | ||||
|  | ||||
| @@ -407,6 +513,11 @@ class _HomeDashCheckInWidgetState extends State<_HomeDashCheckInWidget> { | ||||
|                           '+${_todayRecord!.resultExperience} EXP', | ||||
|                           style: Theme.of(context).textTheme.bodyLarge, | ||||
|                         ), | ||||
|                         if (_todayRecord!.resultCoin >= 0) | ||||
|                           Text( | ||||
|                             '+${_todayRecord!.resultCoin} ${'walletCurrencyShort'.tr()}', | ||||
|                             style: Theme.of(context).textTheme.bodyLarge, | ||||
|                           ) | ||||
|                       ], | ||||
|                     ), | ||||
|             ), | ||||
|   | ||||
							
								
								
									
										241
									
								
								lib/screens/news/news_detail.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										241
									
								
								lib/screens/news/news_detail.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,241 @@ | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:flutter/gestures.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:gap/gap.dart'; | ||||
| import 'package:html/dom.dart' as dom; | ||||
| import 'package:html/parser.dart'; | ||||
| import 'package:provider/provider.dart'; | ||||
| import 'package:relative_time/relative_time.dart'; | ||||
| import 'package:styled_widget/styled_widget.dart'; | ||||
| import 'package:surface/providers/sn_network.dart'; | ||||
| import 'package:surface/types/news.dart'; | ||||
| import 'package:surface/widgets/dialog.dart'; | ||||
| import 'package:surface/widgets/navigation/app_scaffold.dart'; | ||||
| import 'package:flutter_inappwebview/flutter_inappwebview.dart'; | ||||
| import 'package:surface/widgets/universal_image.dart'; | ||||
| import 'package:url_launcher/url_launcher_string.dart'; | ||||
|  | ||||
| class NewsDetailScreen extends StatefulWidget { | ||||
|   final String hash; | ||||
|  | ||||
|   const NewsDetailScreen({super.key, required this.hash}); | ||||
|  | ||||
|   @override | ||||
|   State<NewsDetailScreen> createState() => _NewsDetailScreenState(); | ||||
| } | ||||
|  | ||||
| class _NewsDetailScreenState extends State<NewsDetailScreen> { | ||||
|   SnNewsArticle? _article; | ||||
|   dom.Document? _articleFragment; | ||||
|  | ||||
|   Future<void> _fetchArticle() async { | ||||
|     try { | ||||
|       final sn = context.read<SnNetworkProvider>(); | ||||
|       final resp = await sn.client.get('/cgi/re/news/${widget.hash}'); | ||||
|       _article = SnNewsArticle.fromJson(resp.data); | ||||
|       _articleFragment = parse(_article!.content); | ||||
|     } catch (err) { | ||||
|       if (!mounted) return; | ||||
|       context.showErrorDialog(err).then((_) { | ||||
|         if (!mounted) return; | ||||
|         Navigator.pop(context); | ||||
|       }); | ||||
|     } finally { | ||||
|       setState(() {}); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   List<Widget> _parseHtmlToWidgets(Iterable<dom.Element>? elements) { | ||||
|     if (elements == null) return []; | ||||
|  | ||||
|     final List<Widget> widgets = []; | ||||
|  | ||||
|     for (final node in elements) { | ||||
|       switch (node.localName) { | ||||
|         case 'h1': | ||||
|         case 'h2': | ||||
|         case 'h3': | ||||
|         case 'h4': | ||||
|         case 'h5': | ||||
|         case 'h6': | ||||
|           widgets.add(Text(node.text.trim(), style: Theme.of(context).textTheme.titleMedium)); | ||||
|           break; | ||||
|         case 'p': | ||||
|           if (node.text.trim().isEmpty) continue; | ||||
|           widgets.add( | ||||
|             Text.rich( | ||||
|               TextSpan( | ||||
|                 text: node.text.trim(), | ||||
|                 children: [ | ||||
|                   for (final child in node.children) | ||||
|                     switch (child.localName) { | ||||
|                       'a' => TextSpan( | ||||
|                           text: child.text.trim(), | ||||
|                           style: const TextStyle(decoration: TextDecoration.underline), | ||||
|                           recognizer: TapGestureRecognizer() | ||||
|                             ..onTap = () { | ||||
|                               launchUrlString(child.attributes['href']!); | ||||
|                             }, | ||||
|                         ), | ||||
|                       _ => TextSpan(text: child.text.trim()), | ||||
|                     }, | ||||
|                 ], | ||||
|               ), | ||||
|               style: Theme.of(context).textTheme.bodyLarge, | ||||
|             ), | ||||
|           ); | ||||
|           break; | ||||
|         case 'a': | ||||
|           // drop single link | ||||
|           break; | ||||
|         case 'div': | ||||
|           // ignore div text, normally it is not meaningful | ||||
|           widgets.addAll(_parseHtmlToWidgets(node.children)); | ||||
|           break; | ||||
|         case 'hr': | ||||
|           widgets.add(const Divider()); | ||||
|           break; | ||||
|         case 'img': | ||||
|           var src = node.attributes['src']; | ||||
|           if (src == null) break; | ||||
|           final width = double.tryParse(node.attributes['width'] ?? 'null'); | ||||
|           final height = double.tryParse(node.attributes['height'] ?? 'null'); | ||||
|           final ratio = width != null && height != null ? width / height : 1.0; | ||||
|           if (src.startsWith('//')) { | ||||
|             src = 'https:$src'; | ||||
|           } else if (!src.startsWith('http')) { | ||||
|             final baseUri = Uri.parse(_article!.url); | ||||
|             final baseUrl = '${baseUri.scheme}://${baseUri.host}'; | ||||
|             src = '$baseUrl/$src'; | ||||
|           } | ||||
|           widgets.add( | ||||
|             AspectRatio( | ||||
|               aspectRatio: ratio, | ||||
|               child: Container( | ||||
|                 decoration: BoxDecoration( | ||||
|                   borderRadius: BorderRadius.all(Radius.circular(8)), | ||||
|                   border: Border.all( | ||||
|                     color: Theme.of(context).dividerColor, | ||||
|                     width: 1, | ||||
|                   ), | ||||
|                 ), | ||||
|                 height: height ?? double.infinity, | ||||
|                 child: ClipRRect( | ||||
|                   borderRadius: BorderRadius.all(Radius.circular(8)), | ||||
|                   child: Container( | ||||
|                     color: Theme.of(context).colorScheme.surfaceContainer, | ||||
|                     child: AutoResizeUniversalImage( | ||||
|                       src, | ||||
|                       fit: width != null && height != null ? BoxFit.cover : BoxFit.contain, | ||||
|                     ), | ||||
|                   ), | ||||
|                 ), | ||||
|               ), | ||||
|             ), | ||||
|           ); | ||||
|           break; | ||||
|         default: | ||||
|           widgets.addAll(_parseHtmlToWidgets(node.children)); | ||||
|           break; | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     return widgets; | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   void initState() { | ||||
|     super.initState(); | ||||
|     _fetchArticle(); | ||||
|   } | ||||
|  | ||||
|   bool _isReadingFromReader = true; | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return AppScaffold( | ||||
|       appBar: AppBar( | ||||
|         leading: const PageBackButton(), | ||||
|         title: Text(_article?.title ?? 'loading'.tr()), | ||||
|       ), | ||||
|       body: Column( | ||||
|         children: [ | ||||
|           MaterialBanner( | ||||
|             dividerColor: Colors.transparent, | ||||
|             leading: const Icon(Icons.info), | ||||
|             content: Text(_isReadingFromReader ? 'newsReadingFromReader'.tr() : 'newsReadingFromOriginal'.tr()), | ||||
|             actions: [ | ||||
|               TextButton( | ||||
|                 child: Text('newsReadingProviderSwap').tr(), | ||||
|                 onPressed: () { | ||||
|                   setState(() => _isReadingFromReader = !_isReadingFromReader); | ||||
|                 }, | ||||
|               ), | ||||
|             ], | ||||
|           ), | ||||
|           if (_articleFragment != null && _isReadingFromReader) | ||||
|             Expanded( | ||||
|               child: Container( | ||||
|                 constraints: BoxConstraints(maxWidth: 640), | ||||
|                 child: SingleChildScrollView( | ||||
|                   child: Column( | ||||
|                     crossAxisAlignment: CrossAxisAlignment.start, | ||||
|                     spacing: 8, | ||||
|                     children: [ | ||||
|                       Text(_article!.title, style: Theme.of(context).textTheme.titleLarge), | ||||
|                       Builder(builder: (context) { | ||||
|                         final htmlDescription = parse(_article!.description); | ||||
|                         return Text( | ||||
|                           htmlDescription.children.map((ele) => ele.text.trim()).join(), | ||||
|                           style: Theme.of(context).textTheme.bodyMedium, | ||||
|                         ); | ||||
|                       }), | ||||
|                       Builder(builder: (context) { | ||||
|                         final date = _article!.publishedAt ?? _article!.createdAt; | ||||
|                         return Row( | ||||
|                           spacing: 2, | ||||
|                           children: [ | ||||
|                             Text(DateFormat().format(date)).textStyle(Theme.of(context).textTheme.bodySmall!), | ||||
|                             Text(' · ').textStyle(Theme.of(context).textTheme.bodySmall!).bold(), | ||||
|                             Text(RelativeTime(context).format(date)).textStyle(Theme.of(context).textTheme.bodySmall!), | ||||
|                           ], | ||||
|                         ).opacity(0.75); | ||||
|                       }), | ||||
|                       Text('newsDisclaimer').tr().textStyle(Theme.of(context).textTheme.bodySmall!).opacity(0.75), | ||||
|                       const Divider(), | ||||
|                       ..._parseHtmlToWidgets(_articleFragment!.children), | ||||
|                       const Divider(), | ||||
|                       InkWell( | ||||
|                         child: Row( | ||||
|                           mainAxisSize: MainAxisSize.min, | ||||
|                           children: [ | ||||
|                             Text( | ||||
|                               'Reference from original website', | ||||
|                               style: TextStyle(decoration: TextDecoration.underline), | ||||
|                             ), | ||||
|                             const Gap(4), | ||||
|                             Icon(Icons.launch, size: 16), | ||||
|                           ], | ||||
|                         ).opacity(0.85), | ||||
|                         onTap: () { | ||||
|                           launchUrlString(_article!.url); | ||||
|                         }, | ||||
|                       ), | ||||
|                       Gap(MediaQuery.of(context).padding.bottom), | ||||
|                     ], | ||||
|                   ).padding(horizontal: 12, vertical: 16), | ||||
|                 ), | ||||
|               ).center(), | ||||
|             ) | ||||
|           else if (_article != null) | ||||
|             Expanded( | ||||
|               child: InAppWebView( | ||||
|                 key: GlobalKey(), | ||||
|                 initialUrlRequest: URLRequest(url: WebUri(_article!.url)), | ||||
|               ), | ||||
|             ), | ||||
|         ], | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										239
									
								
								lib/screens/news/news_list.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										239
									
								
								lib/screens/news/news_list.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,239 @@ | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:gap/gap.dart'; | ||||
| import 'package:go_router/go_router.dart'; | ||||
| import 'package:html/parser.dart'; | ||||
| import 'package:provider/provider.dart'; | ||||
| import 'package:relative_time/relative_time.dart'; | ||||
| import 'package:styled_widget/styled_widget.dart'; | ||||
| import 'package:surface/providers/sn_network.dart'; | ||||
| import 'package:surface/types/news.dart'; | ||||
| import 'package:surface/widgets/app_bar_leading.dart'; | ||||
| import 'package:surface/widgets/dialog.dart'; | ||||
| import 'package:surface/widgets/navigation/app_scaffold.dart'; | ||||
| import 'package:surface/widgets/universal_image.dart'; | ||||
| import 'package:very_good_infinite_list/very_good_infinite_list.dart'; | ||||
|  | ||||
| class NewsScreen extends StatefulWidget { | ||||
|   const NewsScreen({super.key}); | ||||
|  | ||||
|   @override | ||||
|   State<NewsScreen> createState() => _NewsScreenState(); | ||||
| } | ||||
|  | ||||
| class _NewsScreenState extends State<NewsScreen> { | ||||
|   List<SnNewsSource>? _sources; | ||||
|  | ||||
|   @override | ||||
|   initState() { | ||||
|     super.initState(); | ||||
|     _fetchSources(); | ||||
|   } | ||||
|  | ||||
|   Future<void> _fetchSources() async { | ||||
|     try { | ||||
|       final sn = context.read<SnNetworkProvider>(); | ||||
|       final resp = await sn.client.get('/cgi/re/well-known/sources'); | ||||
|       _sources = List<SnNewsSource>.from( | ||||
|         resp.data?.map((e) => SnNewsSource.fromJson(e)) ?? [], | ||||
|       ); | ||||
|     } catch (err) { | ||||
|       if (!mounted) return; | ||||
|       context.showErrorDialog(err); | ||||
|     } finally { | ||||
|       setState(() {}); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     if (_sources == null) { | ||||
|       return AppScaffold( | ||||
|         appBar: AppBar( | ||||
|           leading: AutoAppBarLeading(), | ||||
|           title: Text('screenNews').tr(), | ||||
|         ), | ||||
|         body: Center( | ||||
|           child: CircularProgressIndicator(), | ||||
|         ), | ||||
|       ); | ||||
|     } | ||||
|  | ||||
|     return DefaultTabController( | ||||
|       length: _sources!.length + 1, | ||||
|       child: AppScaffold( | ||||
|         body: NestedScrollView( | ||||
|           headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) { | ||||
|             return <Widget>[ | ||||
|               SliverOverlapAbsorber( | ||||
|                 handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), | ||||
|                 sliver: SliverAppBar( | ||||
|                   leading: AutoAppBarLeading(), | ||||
|                   title: Text('screenNews').tr(), | ||||
|                   floating: true, | ||||
|                   snap: true, | ||||
|                   bottom: TabBar( | ||||
|                     isScrollable: true, | ||||
|                     tabs: [ | ||||
|                       Tab(child: Text('newsAllSources'.tr()).textColor(Theme.of(context).appBarTheme.foregroundColor)), | ||||
|                       for (final source in _sources!) | ||||
|                         Tab( | ||||
|                           child: Text(source.label).textColor(Theme.of(context).appBarTheme.foregroundColor), | ||||
|                         ), | ||||
|                     ], | ||||
|                   ), | ||||
|                 ), | ||||
|               ), | ||||
|             ]; | ||||
|           }, | ||||
|           body: TabBarView( | ||||
|             children: [ | ||||
|               _NewsArticleListWidget(allSources: _sources!), | ||||
|               for (final source in _sources!) | ||||
|                 _NewsArticleListWidget( | ||||
|                   source: source.id, | ||||
|                   allSources: _sources!, | ||||
|                 ), | ||||
|             ], | ||||
|           ), | ||||
|         ), | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|  | ||||
| class _NewsArticleListWidget extends StatefulWidget { | ||||
|   final String? source; | ||||
|   final List<SnNewsSource> allSources; | ||||
|  | ||||
|   const _NewsArticleListWidget({this.source, required this.allSources}); | ||||
|  | ||||
|   @override | ||||
|   State<_NewsArticleListWidget> createState() => _NewsArticleListWidgetState(); | ||||
| } | ||||
|  | ||||
| class _NewsArticleListWidgetState extends State<_NewsArticleListWidget> { | ||||
|   bool _isBusy = false; | ||||
|  | ||||
|   int? _totalCount; | ||||
|   final List<SnNewsArticle> _articles = List.empty(growable: true); | ||||
|  | ||||
|   Future<void> _fetchArticles() async { | ||||
|     setState(() => _isBusy = true); | ||||
|  | ||||
|     try { | ||||
|       final sn = context.read<SnNetworkProvider>(); | ||||
|       final resp = await sn.client.get('/cgi/re/news', queryParameters: { | ||||
|         'take': 10, | ||||
|         'offset': _articles.length, | ||||
|         if (widget.source != null) 'source': widget.source, | ||||
|       }); | ||||
|       _totalCount = resp.data['count']; | ||||
|       _articles.addAll(List<SnNewsArticle>.from( | ||||
|         resp.data['data']?.map((e) => SnNewsArticle.fromJson(e)) ?? [], | ||||
|       )); | ||||
|     } catch (err) { | ||||
|       if (!mounted) return; | ||||
|       context.showErrorDialog(err); | ||||
|     } finally { | ||||
|       setState(() => _isBusy = false); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   void initState() { | ||||
|     super.initState(); | ||||
|     _fetchArticles(); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return MediaQuery.removePadding( | ||||
|       context: context, | ||||
|       removeTop: true, | ||||
|       child: Center( | ||||
|         child: Container( | ||||
|           constraints: BoxConstraints(maxWidth: 640), | ||||
|           child: RefreshIndicator( | ||||
|             onRefresh: _fetchArticles, | ||||
|             child: InfiniteList( | ||||
|               isLoading: _isBusy, | ||||
|               itemCount: _articles.length, | ||||
|               hasReachedMax: _totalCount != null && _articles.length >= _totalCount!, | ||||
|               onFetchData: () { | ||||
|                 _fetchArticles(); | ||||
|               }, | ||||
|               itemBuilder: (context, index) { | ||||
|                 final article = _articles[index]; | ||||
|  | ||||
|                 final baseUri = Uri.parse(article.url); | ||||
|                 final baseUrl = '${baseUri.scheme}://${baseUri.host}'; | ||||
|  | ||||
|                 final htmlDescription = parse(article.description); | ||||
|                 final date = article.publishedAt ?? article.createdAt; | ||||
|  | ||||
|                 return Card( | ||||
|                   child: InkWell( | ||||
|                     radius: 8, | ||||
|                     onTap: () { | ||||
|                       GoRouter.of(context).pushNamed( | ||||
|                         'newsDetail', | ||||
|                         pathParameters: {'hash': article.hash}, | ||||
|                       ); | ||||
|                     }, | ||||
|                     child: Column( | ||||
|                       crossAxisAlignment: CrossAxisAlignment.start, | ||||
|                       children: [ | ||||
|                         if (article.thumbnail.isNotEmpty && !article.thumbnail.endsWith('.svg')) | ||||
|                           ClipRRect( | ||||
|                             borderRadius: BorderRadius.only( | ||||
|                               topRight: Radius.circular(8), | ||||
|                               topLeft: Radius.circular(8), | ||||
|                             ), | ||||
|                             child: AspectRatio( | ||||
|                               aspectRatio: 16 / 9, | ||||
|                               child: Container( | ||||
|                                 color: Theme.of(context).colorScheme.surfaceContainer, | ||||
|                                 child: AutoResizeUniversalImage( | ||||
|                                   article.thumbnail.startsWith('http') | ||||
|                                       ? article.thumbnail | ||||
|                                       : '$baseUrl/${article.thumbnail}', | ||||
|                                 ), | ||||
|                               ), | ||||
|                             ), | ||||
|                           ), | ||||
|                         const Gap(16), | ||||
|                         Text(article.title).textStyle(Theme.of(context).textTheme.titleLarge!).padding(horizontal: 16), | ||||
|                         const Gap(8), | ||||
|                         Text(htmlDescription.children.map((ele) => ele.text.trim()).join()) | ||||
|                             .textStyle(Theme.of(context).textTheme.bodyMedium!) | ||||
|                             .padding(horizontal: 16), | ||||
|                         const Gap(8), | ||||
|                         Row( | ||||
|                           spacing: 2, | ||||
|                           children: [ | ||||
|                             Text(widget.allSources.where((x) => x.id == article.source).first.label) | ||||
|                                 .textStyle(Theme.of(context).textTheme.bodySmall!), | ||||
|                           ], | ||||
|                         ).opacity(0.75).padding(horizontal: 16), | ||||
|                         Row( | ||||
|                           spacing: 2, | ||||
|                           children: [ | ||||
|                             Text(DateFormat().format(date)).textStyle(Theme.of(context).textTheme.bodySmall!), | ||||
|                             Text(' · ').textStyle(Theme.of(context).textTheme.bodySmall!).bold(), | ||||
|                             Text(RelativeTime(context).format(date)).textStyle(Theme.of(context).textTheme.bodySmall!), | ||||
|                           ], | ||||
|                         ).opacity(0.75).padding(horizontal: 16), | ||||
|                         const Gap(16), | ||||
|                       ], | ||||
|                     ), | ||||
|                   ), | ||||
|                 ); | ||||
|               }, | ||||
|             ), | ||||
|           ), | ||||
|         ), | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| @@ -38,9 +38,11 @@ class _NotificationScreenState extends State<NotificationScreen> { | ||||
|  | ||||
|   static const Map<String, IconData> kNotificationTopicIcons = { | ||||
|     'passport.security.alert': Symbols.gpp_maybe, | ||||
|     'passport.security.otp': Symbols.password, | ||||
|     'interactive.subscription': Symbols.subscriptions, | ||||
|     'interactive.feedback': Symbols.add_reaction, | ||||
|     'messaging.callStart': Symbols.call_received, | ||||
|     'wallet.transaction.new': Symbols.receipt, | ||||
|   }; | ||||
|  | ||||
|   Future<void> _fetchNotifications() async { | ||||
|   | ||||
| @@ -82,6 +82,48 @@ class _SettingsScreenState extends State<SettingsScreen> { | ||||
|               crossAxisAlignment: CrossAxisAlignment.start, | ||||
|               children: [ | ||||
|                 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) | ||||
|                   ListTile( | ||||
|                     title: Text('settingsBackgroundImage').tr(), | ||||
| @@ -147,30 +189,31 @@ class _SettingsScreenState extends State<SettingsScreen> { | ||||
|                     Color pickerColor = Color(_prefs.getInt(kAppColorSchemeStoreKey) ?? Colors.indigo.value); | ||||
|                     final color = await showDialog<Color?>( | ||||
|                       context: context, | ||||
|                       builder: (context) => AlertDialog( | ||||
|                         content: SingleChildScrollView( | ||||
|                           child: ColorPicker( | ||||
|                             pickerColor: pickerColor, | ||||
|                             onColorChanged: (color) => pickerColor = color, | ||||
|                             enableAlpha: false, | ||||
|                             hexInputBar: true, | ||||
|                       builder: (context) => | ||||
|                           AlertDialog( | ||||
|                             content: SingleChildScrollView( | ||||
|                               child: ColorPicker( | ||||
|                                 pickerColor: pickerColor, | ||||
|                                 onColorChanged: (color) => pickerColor = color, | ||||
|                                 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; | ||||
| @@ -206,11 +249,13 @@ class _SettingsScreenState extends State<SettingsScreen> { | ||||
|                       value: _prefs.getInt(kAppColorSchemeStoreKey) == null | ||||
|                           ? 1 | ||||
|                           : kColorSchemes.values | ||||
|                               .toList() | ||||
|                               .indexWhere((ele) => ele.value == _prefs.getInt(kAppColorSchemeStoreKey)), | ||||
|                           .toList() | ||||
|                           .indexWhere((ele) => ele.value == _prefs.getInt(kAppColorSchemeStoreKey)), | ||||
|                       onChanged: (int? value) { | ||||
|                         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>(); | ||||
|                           th.reloadTheme(seedColorOverride: kColorSchemes.values.elementAt(value)); | ||||
|                           setState(() {}); | ||||
| @@ -342,7 +387,8 @@ class _SettingsScreenState extends State<SettingsScreen> { | ||||
|                           ('Custom', _serverUrlController.text), | ||||
|                       ] | ||||
|                           .map( | ||||
|                             (item) => DropdownMenuItem<String>( | ||||
|                             (item) => | ||||
|                             DropdownMenuItem<String>( | ||||
|                               value: item.$2, | ||||
|                               child: Column( | ||||
|                                 mainAxisSize: MainAxisSize.max, | ||||
| @@ -354,7 +400,7 @@ class _SettingsScreenState extends State<SettingsScreen> { | ||||
|                                 ], | ||||
|                               ), | ||||
|                             ), | ||||
|                           ) | ||||
|                       ) | ||||
|                           .toList(), | ||||
|                       value: _serverUrlController.text, | ||||
|                       onChanged: (String? value) { | ||||
| @@ -409,11 +455,12 @@ class _SettingsScreenState extends State<SettingsScreen> { | ||||
|                       isExpanded: true, | ||||
|                       items: kImageQualityLevel.entries | ||||
|                           .map( | ||||
|                             (item) => DropdownMenuItem<FilterQuality>( | ||||
|                             (item) => | ||||
|                             DropdownMenuItem<FilterQuality>( | ||||
|                               value: item.value, | ||||
|                               child: Text(item.key).tr().fontSize(14), | ||||
|                             ), | ||||
|                           ) | ||||
|                       ) | ||||
|                           .toList(), | ||||
|                       onChanged: (FilterQuality? value) { | ||||
|                         if (value == null) return; | ||||
|   | ||||
							
								
								
									
										279
									
								
								lib/screens/wallet.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										279
									
								
								lib/screens/wallet.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,279 @@ | ||||
| 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? confirmedAt, | ||||
|     required List<SnAccountContact>? contacts, | ||||
|     required String avatar, | ||||
|     required String banner, | ||||
|     @Default("") String avatar, | ||||
|     @Default("") String banner, | ||||
|     required String description, | ||||
|     required String name, | ||||
|     required String nick, | ||||
|   | ||||
| @@ -367,8 +367,8 @@ class _$SnAccountImpl extends _SnAccount { | ||||
|       required this.deletedAt, | ||||
|       required this.confirmedAt, | ||||
|       required final List<SnAccountContact>? contacts, | ||||
|       required this.avatar, | ||||
|       required this.banner, | ||||
|       this.avatar = "", | ||||
|       this.banner = "", | ||||
|       required this.description, | ||||
|       required this.name, | ||||
|       required this.nick, | ||||
| @@ -410,8 +410,10 @@ class _$SnAccountImpl extends _SnAccount { | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   @JsonKey() | ||||
|   final String avatar; | ||||
|   @override | ||||
|   @JsonKey() | ||||
|   final String banner; | ||||
|   @override | ||||
|   final String description; | ||||
| @@ -540,8 +542,8 @@ abstract class _SnAccount extends SnAccount { | ||||
|       required final DateTime? deletedAt, | ||||
|       required final DateTime? confirmedAt, | ||||
|       required final List<SnAccountContact>? contacts, | ||||
|       required final String avatar, | ||||
|       required final String banner, | ||||
|       final String avatar, | ||||
|       final String banner, | ||||
|       required final String description, | ||||
|       required final String name, | ||||
|       required final String nick, | ||||
|   | ||||
| @@ -20,8 +20,8 @@ _$SnAccountImpl _$$SnAccountImplFromJson(Map<String, dynamic> json) => | ||||
|       contacts: (json['contacts'] as List<dynamic>?) | ||||
|           ?.map((e) => SnAccountContact.fromJson(e as Map<String, dynamic>)) | ||||
|           .toList(), | ||||
|       avatar: json['avatar'] as String, | ||||
|       banner: json['banner'] as String, | ||||
|       avatar: json['avatar'] as String? ?? "", | ||||
|       banner: json['banner'] as String? ?? "", | ||||
|       description: json['description'] as String, | ||||
|       name: json['name'] as String, | ||||
|       nick: json['nick'] as String, | ||||
|   | ||||
| @@ -16,6 +16,7 @@ class SnCheckInRecord with _$SnCheckInRecord { | ||||
|     required DateTime? deletedAt, | ||||
|     required int resultTier, | ||||
|     required int resultExperience, | ||||
|     required double resultCoin, | ||||
|     required List<int> resultModifiers, | ||||
|     required int accountId, | ||||
|   }) = _SnCheckInRecord; | ||||
|   | ||||
| @@ -26,6 +26,7 @@ mixin _$SnCheckInRecord { | ||||
|   DateTime? get deletedAt => throw _privateConstructorUsedError; | ||||
|   int get resultTier => throw _privateConstructorUsedError; | ||||
|   int get resultExperience => throw _privateConstructorUsedError; | ||||
|   double get resultCoin => throw _privateConstructorUsedError; | ||||
|   List<int> get resultModifiers => throw _privateConstructorUsedError; | ||||
|   int get accountId => throw _privateConstructorUsedError; | ||||
|  | ||||
| @@ -52,6 +53,7 @@ abstract class $SnCheckInRecordCopyWith<$Res> { | ||||
|       DateTime? deletedAt, | ||||
|       int resultTier, | ||||
|       int resultExperience, | ||||
|       double resultCoin, | ||||
|       List<int> resultModifiers, | ||||
|       int accountId}); | ||||
| } | ||||
| @@ -77,6 +79,7 @@ class _$SnCheckInRecordCopyWithImpl<$Res, $Val extends SnCheckInRecord> | ||||
|     Object? deletedAt = freezed, | ||||
|     Object? resultTier = null, | ||||
|     Object? resultExperience = null, | ||||
|     Object? resultCoin = null, | ||||
|     Object? resultModifiers = null, | ||||
|     Object? accountId = null, | ||||
|   }) { | ||||
| @@ -105,6 +108,10 @@ class _$SnCheckInRecordCopyWithImpl<$Res, $Val extends SnCheckInRecord> | ||||
|           ? _value.resultExperience | ||||
|           : resultExperience // ignore: cast_nullable_to_non_nullable | ||||
|               as int, | ||||
|       resultCoin: null == resultCoin | ||||
|           ? _value.resultCoin | ||||
|           : resultCoin // ignore: cast_nullable_to_non_nullable | ||||
|               as double, | ||||
|       resultModifiers: null == resultModifiers | ||||
|           ? _value.resultModifiers | ||||
|           : resultModifiers // ignore: cast_nullable_to_non_nullable | ||||
| @@ -132,6 +139,7 @@ abstract class _$$SnCheckInRecordImplCopyWith<$Res> | ||||
|       DateTime? deletedAt, | ||||
|       int resultTier, | ||||
|       int resultExperience, | ||||
|       double resultCoin, | ||||
|       List<int> resultModifiers, | ||||
|       int accountId}); | ||||
| } | ||||
| @@ -155,6 +163,7 @@ class __$$SnCheckInRecordImplCopyWithImpl<$Res> | ||||
|     Object? deletedAt = freezed, | ||||
|     Object? resultTier = null, | ||||
|     Object? resultExperience = null, | ||||
|     Object? resultCoin = null, | ||||
|     Object? resultModifiers = null, | ||||
|     Object? accountId = null, | ||||
|   }) { | ||||
| @@ -183,6 +192,10 @@ class __$$SnCheckInRecordImplCopyWithImpl<$Res> | ||||
|           ? _value.resultExperience | ||||
|           : resultExperience // ignore: cast_nullable_to_non_nullable | ||||
|               as int, | ||||
|       resultCoin: null == resultCoin | ||||
|           ? _value.resultCoin | ||||
|           : resultCoin // ignore: cast_nullable_to_non_nullable | ||||
|               as double, | ||||
|       resultModifiers: null == resultModifiers | ||||
|           ? _value._resultModifiers | ||||
|           : resultModifiers // ignore: cast_nullable_to_non_nullable | ||||
| @@ -205,6 +218,7 @@ class _$SnCheckInRecordImpl extends _SnCheckInRecord { | ||||
|       required this.deletedAt, | ||||
|       required this.resultTier, | ||||
|       required this.resultExperience, | ||||
|       required this.resultCoin, | ||||
|       required final List<int> resultModifiers, | ||||
|       required this.accountId}) | ||||
|       : _resultModifiers = resultModifiers, | ||||
| @@ -225,6 +239,8 @@ class _$SnCheckInRecordImpl extends _SnCheckInRecord { | ||||
|   final int resultTier; | ||||
|   @override | ||||
|   final int resultExperience; | ||||
|   @override | ||||
|   final double resultCoin; | ||||
|   final List<int> _resultModifiers; | ||||
|   @override | ||||
|   List<int> get resultModifiers { | ||||
| @@ -238,7 +254,7 @@ class _$SnCheckInRecordImpl extends _SnCheckInRecord { | ||||
|  | ||||
|   @override | ||||
|   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 | ||||
| @@ -257,6 +273,8 @@ class _$SnCheckInRecordImpl extends _SnCheckInRecord { | ||||
|                 other.resultTier == resultTier) && | ||||
|             (identical(other.resultExperience, resultExperience) || | ||||
|                 other.resultExperience == resultExperience) && | ||||
|             (identical(other.resultCoin, resultCoin) || | ||||
|                 other.resultCoin == resultCoin) && | ||||
|             const DeepCollectionEquality() | ||||
|                 .equals(other._resultModifiers, _resultModifiers) && | ||||
|             (identical(other.accountId, accountId) || | ||||
| @@ -273,6 +291,7 @@ class _$SnCheckInRecordImpl extends _SnCheckInRecord { | ||||
|       deletedAt, | ||||
|       resultTier, | ||||
|       resultExperience, | ||||
|       resultCoin, | ||||
|       const DeepCollectionEquality().hash(_resultModifiers), | ||||
|       accountId); | ||||
|  | ||||
| @@ -301,6 +320,7 @@ abstract class _SnCheckInRecord extends SnCheckInRecord { | ||||
|       required final DateTime? deletedAt, | ||||
|       required final int resultTier, | ||||
|       required final int resultExperience, | ||||
|       required final double resultCoin, | ||||
|       required final List<int> resultModifiers, | ||||
|       required final int accountId}) = _$SnCheckInRecordImpl; | ||||
|   const _SnCheckInRecord._() : super._(); | ||||
| @@ -321,6 +341,8 @@ abstract class _SnCheckInRecord extends SnCheckInRecord { | ||||
|   @override | ||||
|   int get resultExperience; | ||||
|   @override | ||||
|   double get resultCoin; | ||||
|   @override | ||||
|   List<int> get resultModifiers; | ||||
|   @override | ||||
|   int get accountId; | ||||
|   | ||||
| @@ -17,6 +17,7 @@ _$SnCheckInRecordImpl _$$SnCheckInRecordImplFromJson( | ||||
|           : DateTime.parse(json['deleted_at'] as String), | ||||
|       resultTier: (json['result_tier'] as num).toInt(), | ||||
|       resultExperience: (json['result_experience'] as num).toInt(), | ||||
|       resultCoin: (json['result_coin'] as num).toDouble(), | ||||
|       resultModifiers: (json['result_modifiers'] as List<dynamic>) | ||||
|           .map((e) => (e as num).toInt()) | ||||
|           .toList(), | ||||
| @@ -32,6 +33,7 @@ Map<String, dynamic> _$$SnCheckInRecordImplToJson( | ||||
|       'deleted_at': instance.deletedAt?.toIso8601String(), | ||||
|       'result_tier': instance.resultTier, | ||||
|       'result_experience': instance.resultExperience, | ||||
|       'result_coin': instance.resultCoin, | ||||
|       'result_modifiers': instance.resultModifiers, | ||||
|       'account_id': instance.accountId, | ||||
|     }; | ||||
|   | ||||
							
								
								
									
										38
									
								
								lib/types/news.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								lib/types/news.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | ||||
| import 'package:freezed_annotation/freezed_annotation.dart'; | ||||
|  | ||||
| part 'news.freezed.dart'; | ||||
| part 'news.g.dart'; | ||||
|  | ||||
| @freezed | ||||
| class SnNewsSource with _$SnNewsSource { | ||||
|   const factory SnNewsSource({ | ||||
|     required String id, | ||||
|     required String label, | ||||
|     required String type, | ||||
|     required String source, | ||||
|     required int depth, | ||||
|     required bool enabled, | ||||
|   }) = _SnNewsSource; | ||||
|  | ||||
|   factory SnNewsSource.fromJson(Map<String, dynamic> json) => _$SnNewsSourceFromJson(json); | ||||
| } | ||||
|  | ||||
| @freezed | ||||
| class SnNewsArticle with _$SnNewsArticle { | ||||
|   const factory SnNewsArticle({ | ||||
|     required int id, | ||||
|     required DateTime createdAt, | ||||
|     required DateTime updatedAt, | ||||
|     required dynamic deletedAt, | ||||
|     required String thumbnail, | ||||
|     required String title, | ||||
|     required String description, | ||||
|     required String content, | ||||
|     required String url, | ||||
|     required String hash, | ||||
|     required String source, | ||||
|     required DateTime? publishedAt, | ||||
|   }) = _SnNewsArticle; | ||||
|  | ||||
|   factory SnNewsArticle.fromJson(Map<String, dynamic> json) => _$SnNewsArticleFromJson(json); | ||||
| } | ||||
							
								
								
									
										660
									
								
								lib/types/news.freezed.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										660
									
								
								lib/types/news.freezed.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,660 @@ | ||||
| // coverage:ignore-file | ||||
| // GENERATED CODE - DO NOT MODIFY BY HAND | ||||
| // ignore_for_file: type=lint | ||||
| // ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark | ||||
|  | ||||
| part of 'news.dart'; | ||||
|  | ||||
| // ************************************************************************** | ||||
| // FreezedGenerator | ||||
| // ************************************************************************** | ||||
|  | ||||
| T _$identity<T>(T value) => value; | ||||
|  | ||||
| final _privateConstructorUsedError = UnsupportedError( | ||||
|     'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); | ||||
|  | ||||
| SnNewsSource _$SnNewsSourceFromJson(Map<String, dynamic> json) { | ||||
|   return _SnNewsSource.fromJson(json); | ||||
| } | ||||
|  | ||||
| /// @nodoc | ||||
| mixin _$SnNewsSource { | ||||
|   String get id => throw _privateConstructorUsedError; | ||||
|   String get label => throw _privateConstructorUsedError; | ||||
|   String get type => throw _privateConstructorUsedError; | ||||
|   String get source => throw _privateConstructorUsedError; | ||||
|   int get depth => throw _privateConstructorUsedError; | ||||
|   bool get enabled => throw _privateConstructorUsedError; | ||||
|  | ||||
|   /// Serializes this SnNewsSource to a JSON map. | ||||
|   Map<String, dynamic> toJson() => throw _privateConstructorUsedError; | ||||
|  | ||||
|   /// Create a copy of SnNewsSource | ||||
|   /// with the given fields replaced by the non-null parameter values. | ||||
|   @JsonKey(includeFromJson: false, includeToJson: false) | ||||
|   $SnNewsSourceCopyWith<SnNewsSource> get copyWith => | ||||
|       throw _privateConstructorUsedError; | ||||
| } | ||||
|  | ||||
| /// @nodoc | ||||
| abstract class $SnNewsSourceCopyWith<$Res> { | ||||
|   factory $SnNewsSourceCopyWith( | ||||
|           SnNewsSource value, $Res Function(SnNewsSource) then) = | ||||
|       _$SnNewsSourceCopyWithImpl<$Res, SnNewsSource>; | ||||
|   @useResult | ||||
|   $Res call( | ||||
|       {String id, | ||||
|       String label, | ||||
|       String type, | ||||
|       String source, | ||||
|       int depth, | ||||
|       bool enabled}); | ||||
| } | ||||
|  | ||||
| /// @nodoc | ||||
| class _$SnNewsSourceCopyWithImpl<$Res, $Val extends SnNewsSource> | ||||
|     implements $SnNewsSourceCopyWith<$Res> { | ||||
|   _$SnNewsSourceCopyWithImpl(this._value, this._then); | ||||
|  | ||||
|   // ignore: unused_field | ||||
|   final $Val _value; | ||||
|   // ignore: unused_field | ||||
|   final $Res Function($Val) _then; | ||||
|  | ||||
|   /// Create a copy of SnNewsSource | ||||
|   /// with the given fields replaced by the non-null parameter values. | ||||
|   @pragma('vm:prefer-inline') | ||||
|   @override | ||||
|   $Res call({ | ||||
|     Object? id = null, | ||||
|     Object? label = null, | ||||
|     Object? type = null, | ||||
|     Object? source = null, | ||||
|     Object? depth = null, | ||||
|     Object? enabled = null, | ||||
|   }) { | ||||
|     return _then(_value.copyWith( | ||||
|       id: null == id | ||||
|           ? _value.id | ||||
|           : id // ignore: cast_nullable_to_non_nullable | ||||
|               as String, | ||||
|       label: null == label | ||||
|           ? _value.label | ||||
|           : label // ignore: cast_nullable_to_non_nullable | ||||
|               as String, | ||||
|       type: null == type | ||||
|           ? _value.type | ||||
|           : type // ignore: cast_nullable_to_non_nullable | ||||
|               as String, | ||||
|       source: null == source | ||||
|           ? _value.source | ||||
|           : source // ignore: cast_nullable_to_non_nullable | ||||
|               as String, | ||||
|       depth: null == depth | ||||
|           ? _value.depth | ||||
|           : depth // ignore: cast_nullable_to_non_nullable | ||||
|               as int, | ||||
|       enabled: null == enabled | ||||
|           ? _value.enabled | ||||
|           : enabled // ignore: cast_nullable_to_non_nullable | ||||
|               as bool, | ||||
|     ) as $Val); | ||||
|   } | ||||
| } | ||||
|  | ||||
| /// @nodoc | ||||
| abstract class _$$SnNewsSourceImplCopyWith<$Res> | ||||
|     implements $SnNewsSourceCopyWith<$Res> { | ||||
|   factory _$$SnNewsSourceImplCopyWith( | ||||
|           _$SnNewsSourceImpl value, $Res Function(_$SnNewsSourceImpl) then) = | ||||
|       __$$SnNewsSourceImplCopyWithImpl<$Res>; | ||||
|   @override | ||||
|   @useResult | ||||
|   $Res call( | ||||
|       {String id, | ||||
|       String label, | ||||
|       String type, | ||||
|       String source, | ||||
|       int depth, | ||||
|       bool enabled}); | ||||
| } | ||||
|  | ||||
| /// @nodoc | ||||
| class __$$SnNewsSourceImplCopyWithImpl<$Res> | ||||
|     extends _$SnNewsSourceCopyWithImpl<$Res, _$SnNewsSourceImpl> | ||||
|     implements _$$SnNewsSourceImplCopyWith<$Res> { | ||||
|   __$$SnNewsSourceImplCopyWithImpl( | ||||
|       _$SnNewsSourceImpl _value, $Res Function(_$SnNewsSourceImpl) _then) | ||||
|       : super(_value, _then); | ||||
|  | ||||
|   /// Create a copy of SnNewsSource | ||||
|   /// with the given fields replaced by the non-null parameter values. | ||||
|   @pragma('vm:prefer-inline') | ||||
|   @override | ||||
|   $Res call({ | ||||
|     Object? id = null, | ||||
|     Object? label = null, | ||||
|     Object? type = null, | ||||
|     Object? source = null, | ||||
|     Object? depth = null, | ||||
|     Object? enabled = null, | ||||
|   }) { | ||||
|     return _then(_$SnNewsSourceImpl( | ||||
|       id: null == id | ||||
|           ? _value.id | ||||
|           : id // ignore: cast_nullable_to_non_nullable | ||||
|               as String, | ||||
|       label: null == label | ||||
|           ? _value.label | ||||
|           : label // ignore: cast_nullable_to_non_nullable | ||||
|               as String, | ||||
|       type: null == type | ||||
|           ? _value.type | ||||
|           : type // ignore: cast_nullable_to_non_nullable | ||||
|               as String, | ||||
|       source: null == source | ||||
|           ? _value.source | ||||
|           : source // ignore: cast_nullable_to_non_nullable | ||||
|               as String, | ||||
|       depth: null == depth | ||||
|           ? _value.depth | ||||
|           : depth // ignore: cast_nullable_to_non_nullable | ||||
|               as int, | ||||
|       enabled: null == enabled | ||||
|           ? _value.enabled | ||||
|           : enabled // ignore: cast_nullable_to_non_nullable | ||||
|               as bool, | ||||
|     )); | ||||
|   } | ||||
| } | ||||
|  | ||||
| /// @nodoc | ||||
| @JsonSerializable() | ||||
| class _$SnNewsSourceImpl implements _SnNewsSource { | ||||
|   const _$SnNewsSourceImpl( | ||||
|       {required this.id, | ||||
|       required this.label, | ||||
|       required this.type, | ||||
|       required this.source, | ||||
|       required this.depth, | ||||
|       required this.enabled}); | ||||
|  | ||||
|   factory _$SnNewsSourceImpl.fromJson(Map<String, dynamic> json) => | ||||
|       _$$SnNewsSourceImplFromJson(json); | ||||
|  | ||||
|   @override | ||||
|   final String id; | ||||
|   @override | ||||
|   final String label; | ||||
|   @override | ||||
|   final String type; | ||||
|   @override | ||||
|   final String source; | ||||
|   @override | ||||
|   final int depth; | ||||
|   @override | ||||
|   final bool enabled; | ||||
|  | ||||
|   @override | ||||
|   String toString() { | ||||
|     return 'SnNewsSource(id: $id, label: $label, type: $type, source: $source, depth: $depth, enabled: $enabled)'; | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   bool operator ==(Object other) { | ||||
|     return identical(this, other) || | ||||
|         (other.runtimeType == runtimeType && | ||||
|             other is _$SnNewsSourceImpl && | ||||
|             (identical(other.id, id) || other.id == id) && | ||||
|             (identical(other.label, label) || other.label == label) && | ||||
|             (identical(other.type, type) || other.type == type) && | ||||
|             (identical(other.source, source) || other.source == source) && | ||||
|             (identical(other.depth, depth) || other.depth == depth) && | ||||
|             (identical(other.enabled, enabled) || other.enabled == enabled)); | ||||
|   } | ||||
|  | ||||
|   @JsonKey(includeFromJson: false, includeToJson: false) | ||||
|   @override | ||||
|   int get hashCode => | ||||
|       Object.hash(runtimeType, id, label, type, source, depth, enabled); | ||||
|  | ||||
|   /// Create a copy of SnNewsSource | ||||
|   /// with the given fields replaced by the non-null parameter values. | ||||
|   @JsonKey(includeFromJson: false, includeToJson: false) | ||||
|   @override | ||||
|   @pragma('vm:prefer-inline') | ||||
|   _$$SnNewsSourceImplCopyWith<_$SnNewsSourceImpl> get copyWith => | ||||
|       __$$SnNewsSourceImplCopyWithImpl<_$SnNewsSourceImpl>(this, _$identity); | ||||
|  | ||||
|   @override | ||||
|   Map<String, dynamic> toJson() { | ||||
|     return _$$SnNewsSourceImplToJson( | ||||
|       this, | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|  | ||||
| abstract class _SnNewsSource implements SnNewsSource { | ||||
|   const factory _SnNewsSource( | ||||
|       {required final String id, | ||||
|       required final String label, | ||||
|       required final String type, | ||||
|       required final String source, | ||||
|       required final int depth, | ||||
|       required final bool enabled}) = _$SnNewsSourceImpl; | ||||
|  | ||||
|   factory _SnNewsSource.fromJson(Map<String, dynamic> json) = | ||||
|       _$SnNewsSourceImpl.fromJson; | ||||
|  | ||||
|   @override | ||||
|   String get id; | ||||
|   @override | ||||
|   String get label; | ||||
|   @override | ||||
|   String get type; | ||||
|   @override | ||||
|   String get source; | ||||
|   @override | ||||
|   int get depth; | ||||
|   @override | ||||
|   bool get enabled; | ||||
|  | ||||
|   /// Create a copy of SnNewsSource | ||||
|   /// with the given fields replaced by the non-null parameter values. | ||||
|   @override | ||||
|   @JsonKey(includeFromJson: false, includeToJson: false) | ||||
|   _$$SnNewsSourceImplCopyWith<_$SnNewsSourceImpl> get copyWith => | ||||
|       throw _privateConstructorUsedError; | ||||
| } | ||||
|  | ||||
| SnNewsArticle _$SnNewsArticleFromJson(Map<String, dynamic> json) { | ||||
|   return _SnNewsArticle.fromJson(json); | ||||
| } | ||||
|  | ||||
| /// @nodoc | ||||
| mixin _$SnNewsArticle { | ||||
|   int get id => throw _privateConstructorUsedError; | ||||
|   DateTime get createdAt => throw _privateConstructorUsedError; | ||||
|   DateTime get updatedAt => throw _privateConstructorUsedError; | ||||
|   dynamic get deletedAt => throw _privateConstructorUsedError; | ||||
|   String get thumbnail => throw _privateConstructorUsedError; | ||||
|   String get title => throw _privateConstructorUsedError; | ||||
|   String get description => throw _privateConstructorUsedError; | ||||
|   String get content => throw _privateConstructorUsedError; | ||||
|   String get url => throw _privateConstructorUsedError; | ||||
|   String get hash => throw _privateConstructorUsedError; | ||||
|   String get source => throw _privateConstructorUsedError; | ||||
|   DateTime? get publishedAt => throw _privateConstructorUsedError; | ||||
|  | ||||
|   /// Serializes this SnNewsArticle to a JSON map. | ||||
|   Map<String, dynamic> toJson() => throw _privateConstructorUsedError; | ||||
|  | ||||
|   /// Create a copy of SnNewsArticle | ||||
|   /// with the given fields replaced by the non-null parameter values. | ||||
|   @JsonKey(includeFromJson: false, includeToJson: false) | ||||
|   $SnNewsArticleCopyWith<SnNewsArticle> get copyWith => | ||||
|       throw _privateConstructorUsedError; | ||||
| } | ||||
|  | ||||
| /// @nodoc | ||||
| abstract class $SnNewsArticleCopyWith<$Res> { | ||||
|   factory $SnNewsArticleCopyWith( | ||||
|           SnNewsArticle value, $Res Function(SnNewsArticle) then) = | ||||
|       _$SnNewsArticleCopyWithImpl<$Res, SnNewsArticle>; | ||||
|   @useResult | ||||
|   $Res call( | ||||
|       {int id, | ||||
|       DateTime createdAt, | ||||
|       DateTime updatedAt, | ||||
|       dynamic deletedAt, | ||||
|       String thumbnail, | ||||
|       String title, | ||||
|       String description, | ||||
|       String content, | ||||
|       String url, | ||||
|       String hash, | ||||
|       String source, | ||||
|       DateTime? publishedAt}); | ||||
| } | ||||
|  | ||||
| /// @nodoc | ||||
| class _$SnNewsArticleCopyWithImpl<$Res, $Val extends SnNewsArticle> | ||||
|     implements $SnNewsArticleCopyWith<$Res> { | ||||
|   _$SnNewsArticleCopyWithImpl(this._value, this._then); | ||||
|  | ||||
|   // ignore: unused_field | ||||
|   final $Val _value; | ||||
|   // ignore: unused_field | ||||
|   final $Res Function($Val) _then; | ||||
|  | ||||
|   /// Create a copy of SnNewsArticle | ||||
|   /// with the given fields replaced by the non-null parameter values. | ||||
|   @pragma('vm:prefer-inline') | ||||
|   @override | ||||
|   $Res call({ | ||||
|     Object? id = null, | ||||
|     Object? createdAt = null, | ||||
|     Object? updatedAt = null, | ||||
|     Object? deletedAt = freezed, | ||||
|     Object? thumbnail = null, | ||||
|     Object? title = null, | ||||
|     Object? description = null, | ||||
|     Object? content = null, | ||||
|     Object? url = null, | ||||
|     Object? hash = null, | ||||
|     Object? source = null, | ||||
|     Object? publishedAt = freezed, | ||||
|   }) { | ||||
|     return _then(_value.copyWith( | ||||
|       id: null == id | ||||
|           ? _value.id | ||||
|           : id // ignore: cast_nullable_to_non_nullable | ||||
|               as int, | ||||
|       createdAt: null == createdAt | ||||
|           ? _value.createdAt | ||||
|           : createdAt // ignore: cast_nullable_to_non_nullable | ||||
|               as DateTime, | ||||
|       updatedAt: null == updatedAt | ||||
|           ? _value.updatedAt | ||||
|           : updatedAt // ignore: cast_nullable_to_non_nullable | ||||
|               as DateTime, | ||||
|       deletedAt: freezed == deletedAt | ||||
|           ? _value.deletedAt | ||||
|           : deletedAt // ignore: cast_nullable_to_non_nullable | ||||
|               as dynamic, | ||||
|       thumbnail: null == thumbnail | ||||
|           ? _value.thumbnail | ||||
|           : thumbnail // ignore: cast_nullable_to_non_nullable | ||||
|               as String, | ||||
|       title: null == title | ||||
|           ? _value.title | ||||
|           : title // ignore: cast_nullable_to_non_nullable | ||||
|               as String, | ||||
|       description: null == description | ||||
|           ? _value.description | ||||
|           : description // ignore: cast_nullable_to_non_nullable | ||||
|               as String, | ||||
|       content: null == content | ||||
|           ? _value.content | ||||
|           : content // ignore: cast_nullable_to_non_nullable | ||||
|               as String, | ||||
|       url: null == url | ||||
|           ? _value.url | ||||
|           : url // ignore: cast_nullable_to_non_nullable | ||||
|               as String, | ||||
|       hash: null == hash | ||||
|           ? _value.hash | ||||
|           : hash // ignore: cast_nullable_to_non_nullable | ||||
|               as String, | ||||
|       source: null == source | ||||
|           ? _value.source | ||||
|           : source // ignore: cast_nullable_to_non_nullable | ||||
|               as String, | ||||
|       publishedAt: freezed == publishedAt | ||||
|           ? _value.publishedAt | ||||
|           : publishedAt // ignore: cast_nullable_to_non_nullable | ||||
|               as DateTime?, | ||||
|     ) as $Val); | ||||
|   } | ||||
| } | ||||
|  | ||||
| /// @nodoc | ||||
| abstract class _$$SnNewsArticleImplCopyWith<$Res> | ||||
|     implements $SnNewsArticleCopyWith<$Res> { | ||||
|   factory _$$SnNewsArticleImplCopyWith( | ||||
|           _$SnNewsArticleImpl value, $Res Function(_$SnNewsArticleImpl) then) = | ||||
|       __$$SnNewsArticleImplCopyWithImpl<$Res>; | ||||
|   @override | ||||
|   @useResult | ||||
|   $Res call( | ||||
|       {int id, | ||||
|       DateTime createdAt, | ||||
|       DateTime updatedAt, | ||||
|       dynamic deletedAt, | ||||
|       String thumbnail, | ||||
|       String title, | ||||
|       String description, | ||||
|       String content, | ||||
|       String url, | ||||
|       String hash, | ||||
|       String source, | ||||
|       DateTime? publishedAt}); | ||||
| } | ||||
|  | ||||
| /// @nodoc | ||||
| class __$$SnNewsArticleImplCopyWithImpl<$Res> | ||||
|     extends _$SnNewsArticleCopyWithImpl<$Res, _$SnNewsArticleImpl> | ||||
|     implements _$$SnNewsArticleImplCopyWith<$Res> { | ||||
|   __$$SnNewsArticleImplCopyWithImpl( | ||||
|       _$SnNewsArticleImpl _value, $Res Function(_$SnNewsArticleImpl) _then) | ||||
|       : super(_value, _then); | ||||
|  | ||||
|   /// Create a copy of SnNewsArticle | ||||
|   /// with the given fields replaced by the non-null parameter values. | ||||
|   @pragma('vm:prefer-inline') | ||||
|   @override | ||||
|   $Res call({ | ||||
|     Object? id = null, | ||||
|     Object? createdAt = null, | ||||
|     Object? updatedAt = null, | ||||
|     Object? deletedAt = freezed, | ||||
|     Object? thumbnail = null, | ||||
|     Object? title = null, | ||||
|     Object? description = null, | ||||
|     Object? content = null, | ||||
|     Object? url = null, | ||||
|     Object? hash = null, | ||||
|     Object? source = null, | ||||
|     Object? publishedAt = freezed, | ||||
|   }) { | ||||
|     return _then(_$SnNewsArticleImpl( | ||||
|       id: null == id | ||||
|           ? _value.id | ||||
|           : id // ignore: cast_nullable_to_non_nullable | ||||
|               as int, | ||||
|       createdAt: null == createdAt | ||||
|           ? _value.createdAt | ||||
|           : createdAt // ignore: cast_nullable_to_non_nullable | ||||
|               as DateTime, | ||||
|       updatedAt: null == updatedAt | ||||
|           ? _value.updatedAt | ||||
|           : updatedAt // ignore: cast_nullable_to_non_nullable | ||||
|               as DateTime, | ||||
|       deletedAt: freezed == deletedAt | ||||
|           ? _value.deletedAt | ||||
|           : deletedAt // ignore: cast_nullable_to_non_nullable | ||||
|               as dynamic, | ||||
|       thumbnail: null == thumbnail | ||||
|           ? _value.thumbnail | ||||
|           : thumbnail // ignore: cast_nullable_to_non_nullable | ||||
|               as String, | ||||
|       title: null == title | ||||
|           ? _value.title | ||||
|           : title // ignore: cast_nullable_to_non_nullable | ||||
|               as String, | ||||
|       description: null == description | ||||
|           ? _value.description | ||||
|           : description // ignore: cast_nullable_to_non_nullable | ||||
|               as String, | ||||
|       content: null == content | ||||
|           ? _value.content | ||||
|           : content // ignore: cast_nullable_to_non_nullable | ||||
|               as String, | ||||
|       url: null == url | ||||
|           ? _value.url | ||||
|           : url // ignore: cast_nullable_to_non_nullable | ||||
|               as String, | ||||
|       hash: null == hash | ||||
|           ? _value.hash | ||||
|           : hash // ignore: cast_nullable_to_non_nullable | ||||
|               as String, | ||||
|       source: null == source | ||||
|           ? _value.source | ||||
|           : source // ignore: cast_nullable_to_non_nullable | ||||
|               as String, | ||||
|       publishedAt: freezed == publishedAt | ||||
|           ? _value.publishedAt | ||||
|           : publishedAt // ignore: cast_nullable_to_non_nullable | ||||
|               as DateTime?, | ||||
|     )); | ||||
|   } | ||||
| } | ||||
|  | ||||
| /// @nodoc | ||||
| @JsonSerializable() | ||||
| class _$SnNewsArticleImpl implements _SnNewsArticle { | ||||
|   const _$SnNewsArticleImpl( | ||||
|       {required this.id, | ||||
|       required this.createdAt, | ||||
|       required this.updatedAt, | ||||
|       required this.deletedAt, | ||||
|       required this.thumbnail, | ||||
|       required this.title, | ||||
|       required this.description, | ||||
|       required this.content, | ||||
|       required this.url, | ||||
|       required this.hash, | ||||
|       required this.source, | ||||
|       required this.publishedAt}); | ||||
|  | ||||
|   factory _$SnNewsArticleImpl.fromJson(Map<String, dynamic> json) => | ||||
|       _$$SnNewsArticleImplFromJson(json); | ||||
|  | ||||
|   @override | ||||
|   final int id; | ||||
|   @override | ||||
|   final DateTime createdAt; | ||||
|   @override | ||||
|   final DateTime updatedAt; | ||||
|   @override | ||||
|   final dynamic deletedAt; | ||||
|   @override | ||||
|   final String thumbnail; | ||||
|   @override | ||||
|   final String title; | ||||
|   @override | ||||
|   final String description; | ||||
|   @override | ||||
|   final String content; | ||||
|   @override | ||||
|   final String url; | ||||
|   @override | ||||
|   final String hash; | ||||
|   @override | ||||
|   final String source; | ||||
|   @override | ||||
|   final DateTime? publishedAt; | ||||
|  | ||||
|   @override | ||||
|   String toString() { | ||||
|     return 'SnNewsArticle(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, thumbnail: $thumbnail, title: $title, description: $description, content: $content, url: $url, hash: $hash, source: $source, publishedAt: $publishedAt)'; | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   bool operator ==(Object other) { | ||||
|     return identical(this, other) || | ||||
|         (other.runtimeType == runtimeType && | ||||
|             other is _$SnNewsArticleImpl && | ||||
|             (identical(other.id, id) || other.id == id) && | ||||
|             (identical(other.createdAt, createdAt) || | ||||
|                 other.createdAt == createdAt) && | ||||
|             (identical(other.updatedAt, updatedAt) || | ||||
|                 other.updatedAt == updatedAt) && | ||||
|             const DeepCollectionEquality().equals(other.deletedAt, deletedAt) && | ||||
|             (identical(other.thumbnail, thumbnail) || | ||||
|                 other.thumbnail == thumbnail) && | ||||
|             (identical(other.title, title) || other.title == title) && | ||||
|             (identical(other.description, description) || | ||||
|                 other.description == description) && | ||||
|             (identical(other.content, content) || other.content == content) && | ||||
|             (identical(other.url, url) || other.url == url) && | ||||
|             (identical(other.hash, hash) || other.hash == hash) && | ||||
|             (identical(other.source, source) || other.source == source) && | ||||
|             (identical(other.publishedAt, publishedAt) || | ||||
|                 other.publishedAt == publishedAt)); | ||||
|   } | ||||
|  | ||||
|   @JsonKey(includeFromJson: false, includeToJson: false) | ||||
|   @override | ||||
|   int get hashCode => Object.hash( | ||||
|       runtimeType, | ||||
|       id, | ||||
|       createdAt, | ||||
|       updatedAt, | ||||
|       const DeepCollectionEquality().hash(deletedAt), | ||||
|       thumbnail, | ||||
|       title, | ||||
|       description, | ||||
|       content, | ||||
|       url, | ||||
|       hash, | ||||
|       source, | ||||
|       publishedAt); | ||||
|  | ||||
|   /// Create a copy of SnNewsArticle | ||||
|   /// with the given fields replaced by the non-null parameter values. | ||||
|   @JsonKey(includeFromJson: false, includeToJson: false) | ||||
|   @override | ||||
|   @pragma('vm:prefer-inline') | ||||
|   _$$SnNewsArticleImplCopyWith<_$SnNewsArticleImpl> get copyWith => | ||||
|       __$$SnNewsArticleImplCopyWithImpl<_$SnNewsArticleImpl>(this, _$identity); | ||||
|  | ||||
|   @override | ||||
|   Map<String, dynamic> toJson() { | ||||
|     return _$$SnNewsArticleImplToJson( | ||||
|       this, | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|  | ||||
| abstract class _SnNewsArticle implements SnNewsArticle { | ||||
|   const factory _SnNewsArticle( | ||||
|       {required final int id, | ||||
|       required final DateTime createdAt, | ||||
|       required final DateTime updatedAt, | ||||
|       required final dynamic deletedAt, | ||||
|       required final String thumbnail, | ||||
|       required final String title, | ||||
|       required final String description, | ||||
|       required final String content, | ||||
|       required final String url, | ||||
|       required final String hash, | ||||
|       required final String source, | ||||
|       required final DateTime? publishedAt}) = _$SnNewsArticleImpl; | ||||
|  | ||||
|   factory _SnNewsArticle.fromJson(Map<String, dynamic> json) = | ||||
|       _$SnNewsArticleImpl.fromJson; | ||||
|  | ||||
|   @override | ||||
|   int get id; | ||||
|   @override | ||||
|   DateTime get createdAt; | ||||
|   @override | ||||
|   DateTime get updatedAt; | ||||
|   @override | ||||
|   dynamic get deletedAt; | ||||
|   @override | ||||
|   String get thumbnail; | ||||
|   @override | ||||
|   String get title; | ||||
|   @override | ||||
|   String get description; | ||||
|   @override | ||||
|   String get content; | ||||
|   @override | ||||
|   String get url; | ||||
|   @override | ||||
|   String get hash; | ||||
|   @override | ||||
|   String get source; | ||||
|   @override | ||||
|   DateTime? get publishedAt; | ||||
|  | ||||
|   /// Create a copy of SnNewsArticle | ||||
|   /// with the given fields replaced by the non-null parameter values. | ||||
|   @override | ||||
|   @JsonKey(includeFromJson: false, includeToJson: false) | ||||
|   _$$SnNewsArticleImplCopyWith<_$SnNewsArticleImpl> get copyWith => | ||||
|       throw _privateConstructorUsedError; | ||||
| } | ||||
							
								
								
									
										61
									
								
								lib/types/news.g.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								lib/types/news.g.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,61 @@ | ||||
| // GENERATED CODE - DO NOT MODIFY BY HAND | ||||
|  | ||||
| part of 'news.dart'; | ||||
|  | ||||
| // ************************************************************************** | ||||
| // JsonSerializableGenerator | ||||
| // ************************************************************************** | ||||
|  | ||||
| _$SnNewsSourceImpl _$$SnNewsSourceImplFromJson(Map<String, dynamic> json) => | ||||
|     _$SnNewsSourceImpl( | ||||
|       id: json['id'] as String, | ||||
|       label: json['label'] as String, | ||||
|       type: json['type'] as String, | ||||
|       source: json['source'] as String, | ||||
|       depth: (json['depth'] as num).toInt(), | ||||
|       enabled: json['enabled'] as bool, | ||||
|     ); | ||||
|  | ||||
| Map<String, dynamic> _$$SnNewsSourceImplToJson(_$SnNewsSourceImpl instance) => | ||||
|     <String, dynamic>{ | ||||
|       'id': instance.id, | ||||
|       'label': instance.label, | ||||
|       'type': instance.type, | ||||
|       'source': instance.source, | ||||
|       'depth': instance.depth, | ||||
|       'enabled': instance.enabled, | ||||
|     }; | ||||
|  | ||||
| _$SnNewsArticleImpl _$$SnNewsArticleImplFromJson(Map<String, dynamic> json) => | ||||
|     _$SnNewsArticleImpl( | ||||
|       id: (json['id'] as num).toInt(), | ||||
|       createdAt: DateTime.parse(json['created_at'] as String), | ||||
|       updatedAt: DateTime.parse(json['updated_at'] as String), | ||||
|       deletedAt: json['deleted_at'], | ||||
|       thumbnail: json['thumbnail'] as String, | ||||
|       title: json['title'] as String, | ||||
|       description: json['description'] as String, | ||||
|       content: json['content'] as String, | ||||
|       url: json['url'] as String, | ||||
|       hash: json['hash'] as String, | ||||
|       source: json['source'] as String, | ||||
|       publishedAt: json['published_at'] == null | ||||
|           ? null | ||||
|           : DateTime.parse(json['published_at'] as String), | ||||
|     ); | ||||
|  | ||||
| Map<String, dynamic> _$$SnNewsArticleImplToJson(_$SnNewsArticleImpl instance) => | ||||
|     <String, dynamic>{ | ||||
|       'id': instance.id, | ||||
|       'created_at': instance.createdAt.toIso8601String(), | ||||
|       'updated_at': instance.updatedAt.toIso8601String(), | ||||
|       'deleted_at': instance.deletedAt, | ||||
|       'thumbnail': instance.thumbnail, | ||||
|       'title': instance.title, | ||||
|       'description': instance.description, | ||||
|       'content': instance.content, | ||||
|       'url': instance.url, | ||||
|       'hash': instance.hash, | ||||
|       'source': instance.source, | ||||
|       'published_at': instance.publishedAt?.toIso8601String(), | ||||
|     }; | ||||
							
								
								
									
										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( | ||||
|           constraints: BoxConstraints(maxHeight: constraints.maxHeight), | ||||
|           child: ScrollConfiguration( | ||||
|             behavior: _AttachmentListScrollBehavior(), | ||||
|             child: ListView.separated( | ||||
|               padding: widget.padding, | ||||
|               shrinkWrap: true, | ||||
|               itemCount: widget.data.length, | ||||
|               itemBuilder: (context, idx) { | ||||
|                 return Container( | ||||
|                   constraints: constraints.copyWith(maxWidth: widget.maxWidth), | ||||
|                   child: AspectRatio( | ||||
|                     aspectRatio: (widget.data[idx]?.data['ratio'] ?? 1).toDouble(), | ||||
|                     child: GestureDetector( | ||||
|                       onTap: () { | ||||
|                         if (widget.data[idx]?.mediaType != SnMediaType.image) return; | ||||
|                         context.pushTransparentRoute( | ||||
|                           AttachmentZoomView( | ||||
|                             data: widget.data.where((ele) => ele != null && ele.mediaType == SnMediaType.image).cast(), | ||||
|                             initialIndex: idx, | ||||
|                             heroTags: heroTags, | ||||
|                           ), | ||||
|                           backgroundColor: Colors.black.withOpacity(0.7), | ||||
|                           rootNavigator: true, | ||||
|                         ); | ||||
|                       }, | ||||
|                       child: Stack( | ||||
|                         fit: StackFit.expand, | ||||
|                         children: [ | ||||
|                           Container( | ||||
|                             decoration: BoxDecoration( | ||||
|                               color: backgroundColor, | ||||
|                               border: Border( | ||||
|                                 top: borderSide, | ||||
|                                 bottom: borderSide, | ||||
|                               ), | ||||
|                               borderRadius: AttachmentList.kDefaultRadius, | ||||
|         return AspectRatio( | ||||
|           aspectRatio: widget.data[0]?.data['ratio']?.toDouble() ?? 1, | ||||
|           child: Container( | ||||
|             constraints: BoxConstraints(maxHeight: constraints.maxHeight), | ||||
|             child: ScrollConfiguration( | ||||
|               behavior: _AttachmentListScrollBehavior(), | ||||
|               child: ListView.separated( | ||||
|                 padding: widget.padding, | ||||
|                 shrinkWrap: true, | ||||
|                 itemCount: widget.data.length, | ||||
|                 itemBuilder: (context, idx) { | ||||
|                   return Container( | ||||
|                     constraints: constraints.copyWith(maxWidth: widget.maxWidth), | ||||
|                     child: AspectRatio( | ||||
|                       aspectRatio: (widget.data[idx]?.data['ratio'] ?? 1).toDouble(), | ||||
|                       child: GestureDetector( | ||||
|                         onTap: () { | ||||
|                           if (widget.data[idx]?.mediaType != SnMediaType.image) return; | ||||
|                           context.pushTransparentRoute( | ||||
|                             AttachmentZoomView( | ||||
|                               data: widget.data.where((ele) => ele != null && ele.mediaType == SnMediaType.image).cast(), | ||||
|                               initialIndex: idx, | ||||
|                               heroTags: heroTags, | ||||
|                             ), | ||||
|                             child: ClipRRect( | ||||
|                               borderRadius: AttachmentList.kDefaultRadius, | ||||
|                               child: AttachmentItem( | ||||
|                                 data: widget.data[idx], | ||||
|                                 heroTag: heroTags[idx], | ||||
|                             backgroundColor: Colors.black.withOpacity(0.7), | ||||
|                             rootNavigator: true, | ||||
|                           ); | ||||
|                         }, | ||||
|                         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( | ||||
|                             right: 8, | ||||
|                             bottom: 8, | ||||
|                             child: Chip( | ||||
|                               label: Text('${idx + 1}/${widget.data.length}'), | ||||
|                             Positioned( | ||||
|                               right: 8, | ||||
|                               bottom: 8, | ||||
|                               child: Chip( | ||||
|                                 label: Text('${idx + 1}/${widget.data.length}'), | ||||
|                               ), | ||||
|                             ), | ||||
|                           ), | ||||
|                         ], | ||||
|                           ], | ||||
|                         ), | ||||
|                       ), | ||||
|                     ), | ||||
|                   ), | ||||
|                 ); | ||||
|               }, | ||||
|               separatorBuilder: (context, index) => const Gap(8), | ||||
|               physics: const BouncingScrollPhysics(), | ||||
|               scrollDirection: Axis.horizontal, | ||||
|                   ); | ||||
|                 }, | ||||
|                 separatorBuilder: (context, index) => const Gap(8), | ||||
|                 physics: const BouncingScrollPhysics(), | ||||
|                 scrollDirection: Axis.horizontal, | ||||
|               ), | ||||
|             ), | ||||
|           ), | ||||
|         ); | ||||
|   | ||||
| @@ -2,7 +2,9 @@ import 'dart:math' as math; | ||||
|  | ||||
| import 'package:dio/dio.dart'; | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:flutter/gestures.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:url_launcher/url_launcher_string.dart'; | ||||
|  | ||||
| extension AppPromptExtension on BuildContext { | ||||
|   void showSnackbar(String content, {SnackBarAction? action}) { | ||||
| @@ -111,7 +113,34 @@ extension AppPromptExtension on BuildContext { | ||||
|       context: this, | ||||
|       builder: (ctx) => AlertDialog( | ||||
|         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: [ | ||||
|           TextButton( | ||||
|             onPressed: () => Navigator.pop(ctx), | ||||
| @@ -128,17 +157,7 @@ extension ByteFormatter on int { | ||||
|     if (this == 0) return '0 Bytes'; | ||||
|     const k = 1024; | ||||
|     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(); | ||||
|     return '${(this / math.pow(k, i)).toStringAsFixed(dm)} ${sizes[i]}'; | ||||
|   } | ||||
|   | ||||
| @@ -1,6 +1,8 @@ | ||||
| import 'dart:developer'; | ||||
| import 'dart:io'; | ||||
| import 'dart:math' as math; | ||||
|  | ||||
| import 'package:dio/dio.dart'; | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:file_saver/file_saver.dart'; | ||||
| import 'package:flutter/foundation.dart'; | ||||
| @@ -34,6 +36,7 @@ import 'package:surface/widgets/post/post_meta_editor.dart'; | ||||
| import 'package:surface/widgets/post/post_reaction.dart'; | ||||
| import 'package:surface/widgets/post/publisher_popover.dart'; | ||||
| import 'package:surface/widgets/universal_image.dart'; | ||||
| import 'package:xml/xml.dart'; | ||||
|  | ||||
| class PostItem extends StatelessWidget { | ||||
|   final SnPost data; | ||||
| @@ -817,6 +820,22 @@ class _PostContentHeader extends StatelessWidget { | ||||
|                 }, | ||||
|               ), | ||||
|               const PopupMenuDivider(), | ||||
|               PopupMenuItem( | ||||
|                 child: Row( | ||||
|                   children: [ | ||||
|                     const Icon(Symbols.book_4_spark), | ||||
|                     const Gap(16), | ||||
|                     Text('postGetInsight').tr(), | ||||
|                   ], | ||||
|                 ), | ||||
|                 onTap: () { | ||||
|                   showModalBottomSheet( | ||||
|                     context: context, | ||||
|                     builder: (context) => _PostGetInsightSheet(postId: data.id), | ||||
|                   ); | ||||
|                 }, | ||||
|               ), | ||||
|               const PopupMenuDivider(), | ||||
|               PopupMenuItem( | ||||
|                 onTap: onShare, | ||||
|                 child: Row( | ||||
| @@ -1181,3 +1200,96 @@ class _PostAbuseReportDialogState extends State<_PostAbuseReportDialog> { | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|  | ||||
| class _PostGetInsightSheet extends StatefulWidget { | ||||
|   final int postId; | ||||
|  | ||||
|   const _PostGetInsightSheet({required this.postId}); | ||||
|  | ||||
|   @override | ||||
|   State<_PostGetInsightSheet> createState() => _PostGetInsightSheetState(); | ||||
| } | ||||
|  | ||||
| class _PostGetInsightSheetState extends State<_PostGetInsightSheet> { | ||||
|   String? _response; | ||||
|   String? _thinkingProcess; | ||||
|  | ||||
|   Future<void> _fetchResponse() async { | ||||
|     try { | ||||
|       final sn = context.read<SnNetworkProvider>(); | ||||
|       final resp = await sn.client.get('/cgi/co/posts/${widget.postId}/insight', | ||||
|           options: Options( | ||||
|             sendTimeout: const Duration(minutes: 10), | ||||
|             receiveTimeout: const Duration(minutes: 10), | ||||
|           )); | ||||
|       final out = resp.data['response'] as String; | ||||
|       final document = XmlDocument.parse(out); | ||||
|       _thinkingProcess = document.getElement('think')?.innerText.trim(); | ||||
|       RegExp cleanThinkingRegExp = RegExp(r'<think>[\s\S]*?</think>'); | ||||
|       setState(() => _response = out.replaceAll(cleanThinkingRegExp, '').trim()); | ||||
|     } catch (err) { | ||||
|       if (!mounted) return; | ||||
|       context.showErrorDialog(err); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   void initState() { | ||||
|     super.initState(); | ||||
|     _fetchResponse(); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return Column( | ||||
|       crossAxisAlignment: CrossAxisAlignment.start, | ||||
|       children: [ | ||||
|         Row( | ||||
|           crossAxisAlignment: CrossAxisAlignment.center, | ||||
|           children: [ | ||||
|             const Icon(Symbols.book_4_spark, size: 24), | ||||
|             const Gap(16), | ||||
|             Text('postGetInsightTitle', style: Theme.of(context).textTheme.titleLarge).tr(), | ||||
|           ], | ||||
|         ).padding(horizontal: 20, top: 16, bottom: 12), | ||||
|         const Gap(4), | ||||
|         Text('postGetInsightDescription', style: Theme.of(context).textTheme.bodySmall).tr().padding(horizontal: 20), | ||||
|         const Gap(4), | ||||
|         if (_response == null) | ||||
|           Expanded( | ||||
|             child: Center( | ||||
|               child: CircularProgressIndicator(), | ||||
|             ), | ||||
|           ) | ||||
|         else | ||||
|           Expanded( | ||||
|             child: SingleChildScrollView( | ||||
|               child: Column( | ||||
|                 children: [ | ||||
|                   if (_thinkingProcess != null && _thinkingProcess!.isNotEmpty) | ||||
|                     ExpansionTile( | ||||
|                       leading: const Icon(Symbols.info), | ||||
|                       title: Text('aiThinkingProcess'.tr()), | ||||
|                       tilePadding: const EdgeInsets.symmetric(horizontal: 20), | ||||
|                       collapsedBackgroundColor: Theme.of(context).colorScheme.surfaceContainerHigh, | ||||
|                       minTileHeight: 32, | ||||
|                       children: [ | ||||
|                         SelectableText( | ||||
|                           _thinkingProcess!, | ||||
|                           style: Theme.of(context).textTheme.bodyMedium!.copyWith(fontStyle: FontStyle.italic), | ||||
|                         ).padding(horizontal: 20, vertical: 8), | ||||
|                       ], | ||||
|                     ).padding(vertical: 8), | ||||
|                   SelectionArea( | ||||
|                     child: MarkdownTextContent( | ||||
|                       content: _response!, | ||||
|                     ), | ||||
|                   ).padding(horizontal: 20, top: 8), | ||||
|                 ], | ||||
|               ), | ||||
|             ), | ||||
|           ), | ||||
|       ], | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -5,10 +5,9 @@ import 'package:material_symbols_icons/symbols.dart'; | ||||
| import 'package:provider/provider.dart'; | ||||
| import 'package:styled_widget/styled_widget.dart'; | ||||
| import 'package:flutter_animate/flutter_animate.dart'; | ||||
|  | ||||
| import 'package:surface/providers/config.dart'; | ||||
| // 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:surface/providers/config.dart'; | ||||
|  | ||||
| class UniversalImage extends StatelessWidget { | ||||
|   final String url; | ||||
|   | ||||
| @@ -8,11 +8,13 @@ import Foundation | ||||
| import bitsdojo_window_macos | ||||
| import connectivity_plus | ||||
| import device_info_plus | ||||
| import file_picker | ||||
| import file_saver | ||||
| import file_selector_macos | ||||
| import firebase_analytics | ||||
| import firebase_core | ||||
| import firebase_messaging | ||||
| import flutter_inappwebview_macos | ||||
| import flutter_udid | ||||
| import flutter_webrtc | ||||
| import gal | ||||
| @@ -35,11 +37,13 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { | ||||
|   BitsdojoWindowPlugin.register(with: registry.registrar(forPlugin: "BitsdojoWindowPlugin")) | ||||
|   ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin")) | ||||
|   DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) | ||||
|   FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin")) | ||||
|   FileSaverPlugin.register(with: registry.registrar(forPlugin: "FileSaverPlugin")) | ||||
|   FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) | ||||
|   FLTFirebaseAnalyticsPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAnalyticsPlugin")) | ||||
|   FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin")) | ||||
|   FLTFirebaseMessagingPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseMessagingPlugin")) | ||||
|   InAppWebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "InAppWebViewFlutterPlugin")) | ||||
|   FlutterUdidPlugin.register(with: registry.registrar(forPlugin: "FlutterUdidPlugin")) | ||||
|   FlutterWebRTCPlugin.register(with: registry.registrar(forPlugin: "FlutterWebRTCPlugin")) | ||||
|   GalPlugin.register(with: registry.registrar(forPlugin: "GalPlugin")) | ||||
|   | ||||
| @@ -8,6 +8,8 @@ PODS: | ||||
|     - FlutterMacOS | ||||
|   - device_info_plus (0.0.1): | ||||
|     - FlutterMacOS | ||||
|   - file_picker (0.0.1): | ||||
|     - FlutterMacOS | ||||
|   - file_saver (0.0.1): | ||||
|     - FlutterMacOS | ||||
|   - file_selector_macos (0.0.1): | ||||
| @@ -22,14 +24,14 @@ PODS: | ||||
|   - Firebase/Messaging (11.6.0): | ||||
|     - Firebase/CoreOnly | ||||
|     - FirebaseMessaging (~> 11.6.0) | ||||
|   - firebase_analytics (11.4.0): | ||||
|   - firebase_analytics (11.4.1): | ||||
|     - Firebase/Analytics (= 11.6.0) | ||||
|     - firebase_core | ||||
|     - FlutterMacOS | ||||
|   - firebase_core (3.10.0): | ||||
|   - firebase_core (3.10.1): | ||||
|     - Firebase/CoreOnly (~> 11.6.0) | ||||
|     - FlutterMacOS | ||||
|   - firebase_messaging (15.2.0): | ||||
|   - firebase_messaging (15.2.1): | ||||
|     - Firebase/CoreOnly (~> 11.6.0) | ||||
|     - Firebase/Messaging (~> 11.6.0) | ||||
|     - firebase_core | ||||
| @@ -72,6 +74,9 @@ PODS: | ||||
|     - GoogleUtilities/Reachability (~> 8.0) | ||||
|     - GoogleUtilities/UserDefaults (~> 8.0) | ||||
|     - nanopb (~> 3.30910.0) | ||||
|   - flutter_inappwebview_macos (0.0.1): | ||||
|     - FlutterMacOS | ||||
|     - OrderedSet (~> 6.0.3) | ||||
|   - flutter_udid (0.0.1): | ||||
|     - FlutterMacOS | ||||
|     - SAMKeychain | ||||
| @@ -149,6 +154,7 @@ PODS: | ||||
|     - nanopb/encode (= 3.30910.0) | ||||
|   - nanopb/decode (3.30910.0) | ||||
|   - nanopb/encode (3.30910.0) | ||||
|   - OrderedSet (6.0.3) | ||||
|   - package_info_plus (0.0.1): | ||||
|     - FlutterMacOS | ||||
|   - pasteboard (0.0.1): | ||||
| @@ -181,11 +187,13 @@ DEPENDENCIES: | ||||
|   - connectivity_plus (from `Flutter/ephemeral/.symlinks/plugins/connectivity_plus/darwin`) | ||||
|   - croppy (from `Flutter/ephemeral/.symlinks/plugins/croppy/macos`) | ||||
|   - device_info_plus (from `Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos`) | ||||
|   - file_picker (from `Flutter/ephemeral/.symlinks/plugins/file_picker/macos`) | ||||
|   - file_saver (from `Flutter/ephemeral/.symlinks/plugins/file_saver/macos`) | ||||
|   - file_selector_macos (from `Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos`) | ||||
|   - firebase_analytics (from `Flutter/ephemeral/.symlinks/plugins/firebase_analytics/macos`) | ||||
|   - firebase_core (from `Flutter/ephemeral/.symlinks/plugins/firebase_core/macos`) | ||||
|   - firebase_messaging (from `Flutter/ephemeral/.symlinks/plugins/firebase_messaging/macos`) | ||||
|   - flutter_inappwebview_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_inappwebview_macos/macos`) | ||||
|   - flutter_udid (from `Flutter/ephemeral/.symlinks/plugins/flutter_udid/macos`) | ||||
|   - flutter_webrtc (from `Flutter/ephemeral/.symlinks/plugins/flutter_webrtc/macos`) | ||||
|   - FlutterMacOS (from `Flutter/ephemeral`) | ||||
| @@ -218,6 +226,7 @@ SPEC REPOS: | ||||
|     - GoogleDataTransport | ||||
|     - GoogleUtilities | ||||
|     - nanopb | ||||
|     - OrderedSet | ||||
|     - PromisesObjC | ||||
|     - SAMKeychain | ||||
|     - WebRTC-SDK | ||||
| @@ -231,6 +240,8 @@ EXTERNAL SOURCES: | ||||
|     :path: Flutter/ephemeral/.symlinks/plugins/croppy/macos | ||||
|   device_info_plus: | ||||
|     :path: Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos | ||||
|   file_picker: | ||||
|     :path: Flutter/ephemeral/.symlinks/plugins/file_picker/macos | ||||
|   file_saver: | ||||
|     :path: Flutter/ephemeral/.symlinks/plugins/file_saver/macos | ||||
|   file_selector_macos: | ||||
| @@ -241,6 +252,8 @@ EXTERNAL SOURCES: | ||||
|     :path: Flutter/ephemeral/.symlinks/plugins/firebase_core/macos | ||||
|   firebase_messaging: | ||||
|     :path: Flutter/ephemeral/.symlinks/plugins/firebase_messaging/macos | ||||
|   flutter_inappwebview_macos: | ||||
|     :path: Flutter/ephemeral/.symlinks/plugins/flutter_inappwebview_macos/macos | ||||
|   flutter_udid: | ||||
|     :path: Flutter/ephemeral/.symlinks/plugins/flutter_udid/macos | ||||
|   flutter_webrtc: | ||||
| @@ -285,17 +298,19 @@ SPEC CHECKSUMS: | ||||
|   connectivity_plus: 18382e7311ba19efcaee94442b23b32507b20695 | ||||
|   croppy: 25a638bd7d05411d8c697f481568f261037694fc | ||||
|   device_info_plus: 1b14eed9bf95428983aed283a8d51cce3d8c4215 | ||||
|   file_picker: e716a70a9fe5fd9e09ebc922d7541464289443af | ||||
|   file_saver: 44e6fbf666677faf097302460e214e977fdd977b | ||||
|   file_selector_macos: cc3858c981fe6889f364731200d6232dac1d812d | ||||
|   Firebase: 374a441a91ead896215703a674d58cdb3e9d772b | ||||
|   firebase_analytics: 5249f87da6fed852901581aab2602e0280ec2fdb | ||||
|   firebase_core: 6d9bb8b0ea817e8fe0d928177d42275b45fdba6f | ||||
|   firebase_messaging: ae8e88b586e4d50abc7cac5bacf74d21967fd226 | ||||
|   firebase_analytics: 91efc58e8e37964469fdd59ad11ba36bc97e75d6 | ||||
|   firebase_core: 75e003524565fb5bd80c9960bc5892e8475821cd | ||||
|   firebase_messaging: 082a385eb98b5bb843a566cb30404859c4bd6e25 | ||||
|   FirebaseAnalytics: 7114c698cac995602e3b1b96663473e50d54d6e7 | ||||
|   FirebaseCore: 48b0dd707581cf9c1a1220da68223fb0a562afaa | ||||
|   FirebaseCoreInternal: d98ab91e2d80a56d7b246856a8885443b302c0c2 | ||||
|   FirebaseInstallations: efc0946fc756e4d22d8113f7c761948120322e8c | ||||
|   FirebaseMessaging: e1aca1fcc23e8b9eddb0e33f375ff90944623021 | ||||
|   flutter_inappwebview_macos: bdf207b8f4ebd58e86ae06cd96b147de99a67c9b | ||||
|   flutter_udid: 2e7b3da4b5fdfba86a396b97898f5fe8f4ec1a52 | ||||
|   flutter_webrtc: d55fd3f5c75b42940b6b4b2cf376a5797398d1f8 | ||||
|   FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 | ||||
| @@ -309,6 +324,7 @@ SPEC CHECKSUMS: | ||||
|   media_kit_native_event_loop: 81fd5b45192b72f8b5b69eaf5b540f45777eb8d5 | ||||
|   media_kit_video: c75b07f14d59706c775778e4dd47dd027de8d1e5 | ||||
|   nanopb: fad817b59e0457d11a5dfbde799381cd727c1275 | ||||
|   OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94 | ||||
|   package_info_plus: 12f1c5c2cfe8727ca46cbd0b26677728972d9a5b | ||||
|   pasteboard: 9b69dba6fedbb04866be632205d532fe2f6b1d99 | ||||
|   path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 | ||||
|   | ||||
							
								
								
									
										168
									
								
								pubspec.lock
									
									
									
									
									
								
							
							
						
						
									
										168
									
								
								pubspec.lock
									
									
									
									
									
								
							| @@ -13,10 +13,10 @@ packages: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: _flutterfire_internals | ||||
|       sha256: "27899c95f9e7ec06c8310e6e0eac967707714b9f1450c4a58fa00ca011a4a8ae" | ||||
|       sha256: e4f2a7ef31b0ab2c89d2bde35ef3e6e6aff1dce5e66069c6540b0e9cfe33ee6b | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "1.3.49" | ||||
|     version: "1.3.50" | ||||
|   _macros: | ||||
|     dependency: transitive | ||||
|     description: dart | ||||
| @@ -338,10 +338,10 @@ packages: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: dart_style | ||||
|       sha256: "7856d364b589d1f08986e140938578ed36ed948581fbc3bc9aef1805039ac5ab" | ||||
|       sha256: "7306ab8a2359a48d22310ad823521d723acfed60ee1f7e37388e8986853b6820" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "2.3.7" | ||||
|     version: "2.3.8" | ||||
|   dart_webrtc: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
| @@ -362,10 +362,10 @@ packages: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       name: device_info_plus | ||||
|       sha256: b37d37c2f912ad4e8ec694187de87d05de2a3cb82b465ff1f65f65a2d05de544 | ||||
|       sha256: e3fc9a65820fef83035af8ee8c09004a719d5d1d54e6de978fcb0d84bbeb241a | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "11.2.1" | ||||
|     version: "11.2.2" | ||||
|   device_info_plus_platform_interface: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -378,10 +378,10 @@ packages: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       name: dio | ||||
|       sha256: "5598aa796bbf4699afd5c67c0f5f6e2ed542afc956884b9cd58c306966efc260" | ||||
|       sha256: "253a18bbd4851fecba42f7343a1df3a9a4c1d31a2c1b37e221086b4fa8c8dbc9" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "5.7.0" | ||||
|     version: "5.8.0+1" | ||||
|   dio_smart_retry: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
| @@ -394,10 +394,10 @@ packages: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: dio_web_adapter | ||||
|       sha256: "33259a9276d6cea88774a0000cfae0d861003497755969c92faa223108620dc8" | ||||
|       sha256: e485c7a39ff2b384fa1d7e09b4e25f755804de8384358049124830b04fc4f93a | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "2.0.0" | ||||
|     version: "2.1.0" | ||||
|   dismissible_page: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
| @@ -418,10 +418,10 @@ packages: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       name: easy_localization | ||||
|       sha256: fa59bcdbbb911a764aa6acf96bbb6fa7a5cf8234354fc45ec1a43a0349ef0201 | ||||
|       sha256: "0f5239c7b8ab06c66440cfb0e9aa4b4640429c6668d5a42fe389c5de42220b12" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "3.0.7" | ||||
|     version: "3.0.7+1" | ||||
|   easy_localization_loader: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
| @@ -490,10 +490,10 @@ packages: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       name: file_picker | ||||
|       sha256: c904b4ab56d53385563c7c39d8e9fa9af086f91495dfc48717ad84a42c3cf204 | ||||
|       sha256: c9943dd7d702ab4199d199bc151a2d79c86db031a02ad84566dab58c494d2adc | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "8.1.7" | ||||
|     version: "8.3.1" | ||||
|   file_saver: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
| @@ -538,34 +538,34 @@ packages: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       name: firebase_analytics | ||||
|       sha256: "498c6cb8468e348a556709c745d92a52173ab3a9b906aa0593393f0787f201ea" | ||||
|       sha256: eac382bbcd5ae78c1d1ce5619d13f5a7424429f4bf55df9e3ad5110da34d1060 | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "11.4.0" | ||||
|     version: "11.4.1" | ||||
|   firebase_analytics_platform_interface: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: firebase_analytics_platform_interface | ||||
|       sha256: ccbb350554e98afdb4b59852689292d194d31232a2647b5012a66622b3711df9 | ||||
|       sha256: a34db46c367265c4c961626e4b128bfb7b7e50958e7add4c27ba103f5f81b9b0 | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "4.3.0" | ||||
|     version: "4.3.1" | ||||
|   firebase_analytics_web: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: firebase_analytics_web | ||||
|       sha256: "68e1f18fc16482c211c658e739c25f015b202a260d9ad8249c6d3d7963b8105f" | ||||
|       sha256: b6b4cef08e45e4c7d48476d9fc49fe9577081809a59026fe95b1a1b1eea165fa | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "0.5.10+6" | ||||
|     version: "0.5.10+7" | ||||
|   firebase_core: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       name: firebase_core | ||||
|       sha256: "0307c1fde82e2b8b97e0be2dab93612aff9a72f31ebe9bfac66ed8b37ef7c568" | ||||
|       sha256: d851c1ca98fd5a4c07c747f8c65dacc2edd84a4d9ac055d32a5f0342529069f5 | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "3.10.0" | ||||
|     version: "3.10.1" | ||||
|   firebase_core_platform_interface: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -586,26 +586,26 @@ packages: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       name: firebase_messaging | ||||
|       sha256: "48a8a59197c1c5174060ba9aa1e0036e9b5a0d28a0cc22d19c1fcabc67fafe3c" | ||||
|       sha256: e20ea2a0ecf9b0971575ab3ab42a6e285a94e50092c555b090c1a588a81b4d54 | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "15.2.0" | ||||
|     version: "15.2.1" | ||||
|   firebase_messaging_platform_interface: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: firebase_messaging_platform_interface | ||||
|       sha256: "9770a8e91f54296829dcaa61ce9b7c2f9ae9abbf99976dd3103a60470d5264dd" | ||||
|       sha256: c57a92b5ae1857ef4fe4ae2e73452b44d32e984e15ab8b53415ea1bb514bdabd | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "4.6.0" | ||||
|     version: "4.6.1" | ||||
|   firebase_messaging_web: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: firebase_messaging_web | ||||
|       sha256: "329ca4ef45ec616abe6f1d5e58feed0934a50840a65aa327052354ad3c64ed77" | ||||
|       sha256: "83694a990d8525d6b01039240b97757298369622ca0253ad0ebcfed221bf8ee0" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "3.10.0" | ||||
|     version: "3.10.1" | ||||
|   fixnum: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -675,6 +675,70 @@ packages: | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "2.3.0" | ||||
|   flutter_inappwebview: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       name: flutter_inappwebview | ||||
|       sha256: "80092d13d3e29b6227e25b67973c67c7210bd5e35c4b747ca908e31eb71a46d5" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "6.1.5" | ||||
|   flutter_inappwebview_android: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: flutter_inappwebview_android | ||||
|       sha256: "62557c15a5c2db5d195cb3892aab74fcaec266d7b86d59a6f0027abd672cddba" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "1.1.3" | ||||
|   flutter_inappwebview_internal_annotations: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: flutter_inappwebview_internal_annotations | ||||
|       sha256: "787171d43f8af67864740b6f04166c13190aa74a1468a1f1f1e9ee5b90c359cd" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "1.2.0" | ||||
|   flutter_inappwebview_ios: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: flutter_inappwebview_ios | ||||
|       sha256: "5818cf9b26cf0cbb0f62ff50772217d41ea8d3d9cc00279c45f8aabaa1b4025d" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "1.1.2" | ||||
|   flutter_inappwebview_macos: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: flutter_inappwebview_macos | ||||
|       sha256: c1fbb86af1a3738e3541364d7d1866315ffb0468a1a77e34198c9be571287da1 | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "1.1.2" | ||||
|   flutter_inappwebview_platform_interface: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: flutter_inappwebview_platform_interface | ||||
|       sha256: cf5323e194096b6ede7a1ca808c3e0a078e4b33cc3f6338977d75b4024ba2500 | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "1.3.0+1" | ||||
|   flutter_inappwebview_web: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: flutter_inappwebview_web | ||||
|       sha256: "55f89c83b0a0d3b7893306b3bb545ba4770a4df018204917148ebb42dc14a598" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "1.1.2" | ||||
|   flutter_inappwebview_windows: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: flutter_inappwebview_windows | ||||
|       sha256: "8b4d3a46078a2cdc636c4a3d10d10f2a16882f6be607962dbfff8874d1642055" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "0.6.0" | ||||
|   flutter_launcher_icons: | ||||
|     dependency: "direct dev" | ||||
|     description: | ||||
| @@ -766,10 +830,10 @@ packages: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       name: flutter_webrtc | ||||
|       sha256: "188401cc3275bc4f1f965babdff6cac612a4b46572f1e49f49db8af5361d5712" | ||||
|       sha256: "4c76069f724f79dc6e8739c8f16c2ee00ca30a1f8e3aa75c0e830f8183278f03" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "0.12.6" | ||||
|     version: "0.12.7" | ||||
|   freezed: | ||||
|     dependency: "direct dev" | ||||
|     description: | ||||
| @@ -814,18 +878,18 @@ packages: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: glob | ||||
|       sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" | ||||
|       sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "2.1.2" | ||||
|     version: "2.1.3" | ||||
|   go_router: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       name: go_router | ||||
|       sha256: "7c2d40b59890a929824f30d442e810116caf5088482629c894b9e4478c67472d" | ||||
|       sha256: "9b736a9fa879d8ad6df7932cbdcc58237c173ab004ef90d8377923d7ad731eaa" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "14.6.3" | ||||
|     version: "14.7.2" | ||||
|   google_fonts: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
| @@ -875,7 +939,7 @@ packages: | ||||
|     source: hosted | ||||
|     version: "0.7.0" | ||||
|   html: | ||||
|     dependency: transitive | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       name: html | ||||
|       sha256: "1fc58edeaec4307368c60d59b7e15b9d658b57d7f3125098b6294153c75337ec" | ||||
| @@ -886,10 +950,10 @@ packages: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: http | ||||
|       sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010 | ||||
|       sha256: fe7ab022b76f3034adc518fb6ea04a82387620e19977665ea18d30a1cf43442f | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "1.2.2" | ||||
|     version: "1.3.0" | ||||
|   http_multi_server: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -966,10 +1030,10 @@ packages: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: image_picker_macos | ||||
|       sha256: "3f5ad1e8112a9a6111c46d0b57a7be2286a9a07fc6e1976fdf5be2bd31d4ff62" | ||||
|       sha256: "1b90ebbd9dcf98fb6c1d01427e49a55bd96b5d67b8c67cf955d60a5de74207c1" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "0.2.1+1" | ||||
|     version: "0.2.1+2" | ||||
|   image_picker_platform_interface: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -1270,10 +1334,10 @@ packages: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       name: package_info_plus | ||||
|       sha256: "739e0a5c3c4055152520fa321d0645ee98e932718b4c8efeeb51451968fe0790" | ||||
|       sha256: b15fad91c4d3d1f2b48c053dd41cb82da007c27407dc9ab5f9aa59881d0e39d4 | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "8.1.3" | ||||
|     version: "8.1.4" | ||||
|   package_info_plus_platform_interface: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -1646,18 +1710,18 @@ packages: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       name: shared_preferences | ||||
|       sha256: a752ce92ea7540fc35a0d19722816e04d0e72828a4200e83a98cf1a1eb524c9a | ||||
|       sha256: "688ee90fbfb6989c980254a56cb26ebe9bb30a3a2dff439a78894211f73de67a" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "2.3.5" | ||||
|     version: "2.5.1" | ||||
|   shared_preferences_android: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: shared_preferences_android | ||||
|       sha256: "138b7bbbc7f59c56236e426c37afb8f78cbc57b094ac64c440e0bb90e380a4f5" | ||||
|       sha256: "650584dcc0a39856f369782874e562efd002a9c94aec032412c9eb81419cce1f" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "2.4.2" | ||||
|     version: "2.4.4" | ||||
|   shared_preferences_foundation: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -1995,10 +2059,10 @@ packages: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: vector_graphics | ||||
|       sha256: "27d5fefe86fb9aace4a9f8375b56b3c292b64d8c04510df230f849850d912cb7" | ||||
|       sha256: "7ed22c21d7fdcc88dd6ba7860384af438cd220b251ad65dfc142ab722fabef61" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "1.1.15" | ||||
|     version: "1.1.16" | ||||
|   vector_graphics_codec: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -2107,10 +2171,10 @@ packages: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       name: web_socket_channel | ||||
|       sha256: "9f187088ed104edd8662ca07af4b124465893caf063ba29758f97af57e61da8f" | ||||
|       sha256: "0b8e2457400d8a859b7b2030786835a28a8e80836ef64402abef392ff4f1d0e5" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "3.0.1" | ||||
|     version: "3.0.2" | ||||
|   webrtc_interface: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -2123,10 +2187,10 @@ packages: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: win32 | ||||
|       sha256: "154360849a56b7b67331c21f09a386562d88903f90a1099c5987afc1912e1f29" | ||||
|       sha256: daf97c9d80197ed7b619040e86c8ab9a9dad285e7671ee7390f9180cc828a51e | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "5.10.0" | ||||
|     version: "5.10.1" | ||||
|   win32_registry: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -2152,7 +2216,7 @@ packages: | ||||
|     source: hosted | ||||
|     version: "1.1.0" | ||||
|   xml: | ||||
|     dependency: transitive | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       name: xml | ||||
|       sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 | ||||
|   | ||||
| @@ -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 | ||||
| # 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. | ||||
| version: 2.2.2+56 | ||||
| version: 2.2.2+60 | ||||
|  | ||||
| environment: | ||||
|   sdk: ^3.5.4 | ||||
| @@ -115,6 +115,9 @@ dependencies: | ||||
|   slide_countdown: ^2.0.2 | ||||
|   video_compress: ^3.1.3 | ||||
|   cached_network_image: ^3.4.1 | ||||
|   flutter_inappwebview: ^6.1.5 | ||||
|   html: ^0.15.5 | ||||
|   xml: ^6.5.0 | ||||
|  | ||||
| dev_dependencies: | ||||
|   flutter_test: | ||||
|   | ||||
| @@ -16,6 +16,8 @@ | ||||
|     --> | ||||
|     <base href="$FLUTTER_BASE_HREF"> | ||||
|  | ||||
|     <script type="application/javascript" src="/assets/packages/flutter_inappwebview_web/assets/web/web_support.js" defer></script> | ||||
|  | ||||
|     <meta charset="UTF-8"> | ||||
|     <meta content="IE=Edge" http-equiv="X-UA-Compatible"> | ||||
|     <meta name="description" content="A new Flutter project."> | ||||
|   | ||||
| @@ -11,6 +11,7 @@ | ||||
| #include <file_saver/file_saver_plugin.h> | ||||
| #include <file_selector_windows/file_selector_windows.h> | ||||
| #include <firebase_core/firebase_core_plugin_c_api.h> | ||||
| #include <flutter_inappwebview_windows/flutter_inappwebview_windows_plugin_c_api.h> | ||||
| #include <flutter_udid/flutter_udid_plugin_c_api.h> | ||||
| #include <flutter_webrtc/flutter_web_r_t_c_plugin.h> | ||||
| #include <gal/gal_plugin_c_api.h> | ||||
| @@ -34,6 +35,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { | ||||
|       registry->GetRegistrarForPlugin("FileSelectorWindows")); | ||||
|   FirebaseCorePluginCApiRegisterWithRegistrar( | ||||
|       registry->GetRegistrarForPlugin("FirebaseCorePluginCApi")); | ||||
|   FlutterInappwebviewWindowsPluginCApiRegisterWithRegistrar( | ||||
|       registry->GetRegistrarForPlugin("FlutterInappwebviewWindowsPluginCApi")); | ||||
|   FlutterUdidPluginCApiRegisterWithRegistrar( | ||||
|       registry->GetRegistrarForPlugin("FlutterUdidPluginCApi")); | ||||
|   FlutterWebRTCPluginRegisterWithRegistrar( | ||||
|   | ||||
| @@ -8,6 +8,7 @@ list(APPEND FLUTTER_PLUGIN_LIST | ||||
|   file_saver | ||||
|   file_selector_windows | ||||
|   firebase_core | ||||
|   flutter_inappwebview_windows | ||||
|   flutter_udid | ||||
|   flutter_webrtc | ||||
|   gal | ||||
|   | ||||
		Reference in New Issue
	
	Block a user