Compare commits
	
		
			57 Commits
		
	
	
		
			3.2.0+129
			...
			1cc34240da
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 1cc34240da | |||
| 013f7f02bc | |||
| 4e79e4100f | |||
| feda1f067f | |||
| fe0e192a43 | |||
| 93df294142 | |||
| 78d65c39f3 | |||
| 18b0dbd797 | |||
| 80cc8cbb40 | |||
| 646e95a9fc | |||
| 6f9d51673b | |||
| f8c6887769 | |||
| cd2a507b7f | |||
| 3cafce00a2 | |||
| 837f3fbe98 | |||
| ca7cc5d7ee | |||
| ef2c14daa2 | |||
| 3a17837cc6 | |||
| 2617a64acf | |||
| afe1e12a3b | |||
| be80f5ff85 | |||
| 3281d69eba | |||
| 77b6ce9937 | |||
| 39275f61b5 | |||
| 72193ba8f3 | |||
| 98dd9b6617 | |||
| a22b94a263 | |||
| 9c75eafdb3 | |||
| 28fda3d0c7 | |||
| 187c2ea43e | |||
| ae7d967461 | |||
| 1ce71f1fa1 | |||
| 9b68808c77 | |||
|  | 99b7bf8199 | ||
|  | eb9bb73c31 | ||
|  | a8c3830d67 | ||
|  | 07a5a19141 | ||
| ecc100ac45 | |||
| 573b76d3ff | |||
| f7dad5e419 | |||
| 9f2f1c0848 | |||
| 580d9fd979 | |||
| 3b375abc09 | |||
| c527b5e67c | |||
| e9f09bbe54 | |||
| 3aece9316c | |||
| a61c889c6c | |||
| 0dd3221a56 | |||
| 66918521f8 | |||
| bb1846e462 | |||
| a976a6eaf4 | |||
| 4252f66fd3 | |||
| f2d780b48f | |||
| 300541f9bb | |||
| 43787bb813 | |||
| 3417c51a3b | |||
| f98e603e82 | 
| @@ -62,4 +62,3 @@ If you want to build the release version, use the flutter build command. Learn m | |||||||
| ```bash | ```bash | ||||||
| flutter build <platform> | flutter build <platform> | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
|   | |||||||
| @@ -24,6 +24,8 @@ android { | |||||||
|     ndkVersion = "29.0.13113456" |     ndkVersion = "29.0.13113456" | ||||||
|  |  | ||||||
|     compileOptions { |     compileOptions { | ||||||
|  |         isCoreLibraryDesugaringEnabled = true | ||||||
|  |  | ||||||
|         sourceCompatibility = JavaVersion.VERSION_17 |         sourceCompatibility = JavaVersion.VERSION_17 | ||||||
|         targetCompatibility = JavaVersion.VERSION_17 |         targetCompatibility = JavaVersion.VERSION_17 | ||||||
|     } |     } | ||||||
| @@ -63,6 +65,8 @@ android { | |||||||
| } | } | ||||||
|  |  | ||||||
| dependencies { | dependencies { | ||||||
|  |     coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.4") | ||||||
|  |      | ||||||
|     implementation("com.google.android.material:material:1.12.0") |     implementation("com.google.android.material:material:1.12.0") | ||||||
|     implementation("com.github.bumptech.glide:glide:4.16.0") |     implementation("com.github.bumptech.glide:glide:4.16.0") | ||||||
|     implementation("com.squareup.okhttp3:okhttp:5.1.0") |     implementation("com.squareup.okhttp3:okhttp:5.1.0") | ||||||
|   | |||||||
| @@ -133,6 +133,25 @@ | |||||||
|     "other": "{} replies" |     "other": "{} replies" | ||||||
|   }, |   }, | ||||||
|   "forward": "Forward", |   "forward": "Forward", | ||||||
|  |   "award": "Award", | ||||||
|  |   "awardPost": "Award Post", | ||||||
|  |   "awardMessage": "Message", | ||||||
|  |   "awardMessageHint": "Enter your award message...", | ||||||
|  |   "awardAttitude": "Attitude", | ||||||
|  |   "awardAttitudePositive": "Positive", | ||||||
|  |   "awardAttitudeNegative": "Negative", | ||||||
|  |   "awardAmount": "Amount", | ||||||
|  |   "awardAmountHint": "Enter amount...", | ||||||
|  |   "awardAmountRequired": "Amount is required", | ||||||
|  |   "awardAmountInvalid": "Please enter a valid amount", | ||||||
|  |   "awardMessageTooLong": "Message is too long (max 4096 characters)", | ||||||
|  |   "awardSuccess": "Award sent successfully!", | ||||||
|  |   "awardSubmit": "Award", | ||||||
|  |   "awardPostPreview": "Post Preview", | ||||||
|  |   "awardNoContent": "No content available", | ||||||
|  |   "awardByPublisher": "By {}", | ||||||
|  |   "awardBenefits": "Award Benefits", | ||||||
|  |   "awardBenefitsDescription": "Awarding this post increases its value and visibility. Higher valued posts have a better chance of being featured and highlighted in the community.", | ||||||
|   "repliedTo": "Replied to", |   "repliedTo": "Replied to", | ||||||
|   "forwarded": "Forwarded", |   "forwarded": "Forwarded", | ||||||
|   "hasAttachments": { |   "hasAttachments": { | ||||||
| @@ -195,6 +214,7 @@ | |||||||
|   "checkInResultLevel2": "A Normal Day", |   "checkInResultLevel2": "A Normal Day", | ||||||
|   "checkInResultLevel3": "Good Luck", |   "checkInResultLevel3": "Good Luck", | ||||||
|   "checkInResultLevel4": "Best Luck", |   "checkInResultLevel4": "Best Luck", | ||||||
|  |   "checkInResultLevel5": "Happy Birthday 🥳", | ||||||
|   "checkInActivityTitle": "{} checked in on {} and got a {}", |   "checkInActivityTitle": "{} checked in on {} and got a {}", | ||||||
|   "eventCalander": "Event Calander", |   "eventCalander": "Event Calander", | ||||||
|   "eventCalanderEmpty": "No events on that day.", |   "eventCalanderEmpty": "No events on that day.", | ||||||
| @@ -228,6 +248,8 @@ | |||||||
|   "settings": "Settings", |   "settings": "Settings", | ||||||
|   "language": "Language", |   "language": "Language", | ||||||
|   "accountLanguageHint": "This language will be used for email and push notifications.", |   "accountLanguageHint": "This language will be used for email and push notifications.", | ||||||
|  |   "region": "Region", | ||||||
|  |   "accountRegionHint": "This region will be used for content delivery and localization.", | ||||||
|   "settingsDisplayLanguage": "Display Language", |   "settingsDisplayLanguage": "Display Language", | ||||||
|   "languageFollowSystem": "Follow System", |   "languageFollowSystem": "Follow System", | ||||||
|   "postsCreatedCount": "Posts", |   "postsCreatedCount": "Posts", | ||||||
| @@ -338,6 +360,7 @@ | |||||||
|   "notifications": "Notifications", |   "notifications": "Notifications", | ||||||
|   "posts": "Posts", |   "posts": "Posts", | ||||||
|   "settingsBackgroundImage": "Background Image", |   "settingsBackgroundImage": "Background Image", | ||||||
|  |   "settingsBackgroundImageEnable": "Show Background Image", | ||||||
|   "settingsBackgroundImageClear": "Clear Background Image", |   "settingsBackgroundImageClear": "Clear Background Image", | ||||||
|   "settingsBackgroundGenerateColor": "Generate color scheme from Bacground Image", |   "settingsBackgroundGenerateColor": "Generate color scheme from Bacground Image", | ||||||
|   "messageNone": "No content to display", |   "messageNone": "No content to display", | ||||||
| @@ -348,6 +371,8 @@ | |||||||
|   "chatBreakNone": "None", |   "chatBreakNone": "None", | ||||||
|   "settingsRealmCompactView": "Compact Realm View", |   "settingsRealmCompactView": "Compact Realm View", | ||||||
|   "settingsMixedFeed": "Mixed Feed", |   "settingsMixedFeed": "Mixed Feed", | ||||||
|  |   "settingsDataSavingMode": "Data Saving Mode", | ||||||
|  |   "dataSavingHint": "Data Saving Mode", | ||||||
|   "settingsAutoTranslate": "Auto Translate", |   "settingsAutoTranslate": "Auto Translate", | ||||||
|   "settingsHideBottomNav": "Hide Bottom Navigation", |   "settingsHideBottomNav": "Hide Bottom Navigation", | ||||||
|   "settingsSoundEffects": "Sound Effects", |   "settingsSoundEffects": "Sound Effects", | ||||||
| @@ -492,6 +517,10 @@ | |||||||
|   "contactMethodSetPrimary": "Set as Primary", |   "contactMethodSetPrimary": "Set as Primary", | ||||||
|   "contactMethodSetPrimaryHint": "Set this contact method as your primary contact method for account recovery and notifications", |   "contactMethodSetPrimaryHint": "Set this contact method as your primary contact method for account recovery and notifications", | ||||||
|   "contactMethodDeleteHint": "Are you sure to delete this contact method? This action cannot be undone.", |   "contactMethodDeleteHint": "Are you sure to delete this contact method? This action cannot be undone.", | ||||||
|  |   "contactMethodMakePublic": "Make Public", | ||||||
|  |   "contactMethodMakePrivate": "Make Private", | ||||||
|  |   "contactMethodPublic": "Public", | ||||||
|  |   "contactMethodPrivate": "Private", | ||||||
|   "chatNotifyLevel": "Notify Level", |   "chatNotifyLevel": "Notify Level", | ||||||
|   "chatNotifyLevelDescription": "Decide how many notifications you will receive.", |   "chatNotifyLevelDescription": "Decide how many notifications you will receive.", | ||||||
|   "chatNotifyLevelAll": "All", |   "chatNotifyLevelAll": "All", | ||||||
| @@ -634,8 +663,9 @@ | |||||||
|   "chatJoin": "Join the Chat", |   "chatJoin": "Join the Chat", | ||||||
|   "realmJoin": "Join the Realm", |   "realmJoin": "Join the Realm", | ||||||
|   "realmJoinSuccess": "Successfully joined the realm.", |   "realmJoinSuccess": "Successfully joined the realm.", | ||||||
|   "discoverRealms": "Discover realms", |   "discoverRealms": "Realms", | ||||||
|   "discoverPublishers": "Discover publishers", |   "discoverPublishers": "Publishers", | ||||||
|  |   "discoverShuffledPost": "Random Posts", | ||||||
|   "search": "Search", |   "search": "Search", | ||||||
|   "publisherMembers": "Collaborators", |   "publisherMembers": "Collaborators", | ||||||
|   "developerHub": "Developer Hub", |   "developerHub": "Developer Hub", | ||||||
| @@ -693,7 +723,7 @@ | |||||||
|   "publisherFeatureDevelopDescription": "Unlock development abilities for your publisher, including custom apps, API keys, and more.", |   "publisherFeatureDevelopDescription": "Unlock development abilities for your publisher, including custom apps, API keys, and more.", | ||||||
|   "publisherFeatureDevelopHint": "Currently, this feature is under active development, you need send a request to unlock this feature.", |   "publisherFeatureDevelopHint": "Currently, this feature is under active development, you need send a request to unlock this feature.", | ||||||
|   "learnMore": "Learn More", |   "learnMore": "Learn More", | ||||||
|   "discoverWebArticles": "Articles from external sites", |   "discoverWebArticles": "Web Feed Articles", | ||||||
|   "webArticlesStand": "Article Stand", |   "webArticlesStand": "Article Stand", | ||||||
|   "about": "About", |   "about": "About", | ||||||
|   "membershipCancel": "Cancel Membership", |   "membershipCancel": "Cancel Membership", | ||||||
| @@ -951,7 +981,6 @@ | |||||||
|   "chatBreak15m": "15m", |   "chatBreak15m": "15m", | ||||||
|   "chatBreak30m": "30m", |   "chatBreak30m": "30m", | ||||||
|   "chatBreakCustomMinutes": "Custom (minutes)", |   "chatBreakCustomMinutes": "Custom (minutes)", | ||||||
|   "chatBreakEnterMinutes": "Enter minutes", |  | ||||||
|   "errorGeneric": "Error: {}", |   "errorGeneric": "Error: {}", | ||||||
|   "searchMessages": "Search Messages", |   "searchMessages": "Search Messages", | ||||||
|   "messagesCount": "{} messages", |   "messagesCount": "{} messages", | ||||||
| @@ -960,5 +989,30 @@ | |||||||
|   "searchMessagesHint": "Search messages...", |   "searchMessagesHint": "Search messages...", | ||||||
|   "searchLinks": "Links", |   "searchLinks": "Links", | ||||||
|   "searchAttachments": "Attachments", |   "searchAttachments": "Attachments", | ||||||
|   "noMessagesFound": "No messages found" |   "noMessagesFound": "No messages found", | ||||||
|  |   "openInBrowser": "Open in Browser", | ||||||
|  |   "highlightPost": "Highlight Post", | ||||||
|  |   "filters": "Filters", | ||||||
|  |   "apply": "Apply", | ||||||
|  |   "pubName": "Pub Name", | ||||||
|  |   "realm": "Realm", | ||||||
|  |   "shuffle": "Shuffle", | ||||||
|  |   "pinned": "Pinned", | ||||||
|  |   "noResultsFound": "No results found", | ||||||
|  |   "toggleFilters": "Toggle filters", | ||||||
|  |   "notableDayNext": "{} is in", | ||||||
|  |   "expandPoll": "Expand Poll", | ||||||
|  |   "collapsePoll": "Collapse Poll", | ||||||
|  |   "embedView": "Embed View", | ||||||
|  |   "embedUri": "Embed URI", | ||||||
|  |   "aspectRatio": "Aspect Ratio", | ||||||
|  |   "renderer": "Renderer", | ||||||
|  |   "addEmbed": "Add Embed", | ||||||
|  |   "editEmbed": "Edit Embed", | ||||||
|  |   "deleteEmbed": "Delete Embed", | ||||||
|  |   "deleteEmbedConfirm": "Are you sure you want to delete this embed?", | ||||||
|  |   "currentEmbed": "Current Embed", | ||||||
|  |   "noEmbed": "No embed yet", | ||||||
|  |   "save": "Save", | ||||||
|  |   "webView": "Web View" | ||||||
| } | } | ||||||
| @@ -158,11 +158,12 @@ | |||||||
|   "checkIn": "签到", |   "checkIn": "签到", | ||||||
|   "checkInNone": "尚未签到", |   "checkInNone": "尚未签到", | ||||||
|   "checkInNoneHint": "通过签到获取您的财富提示和每日奖励。", |   "checkInNoneHint": "通过签到获取您的财富提示和每日奖励。", | ||||||
|   "checkInResultLevel0": "最差运气", |   "checkInResultLevel0": "大凶", | ||||||
|   "checkInResultLevel1": "坏运气", |   "checkInResultLevel1": "凶", | ||||||
|   "checkInResultLevel2": "一个普通的日常", |   "checkInResultLevel2": "中平", | ||||||
|   "checkInResultLevel3": "好运", |   "checkInResultLevel3": "吉", | ||||||
|   "checkInResultLevel4": "最佳运气", |   "checkInResultLevel4": "大吉", | ||||||
|  |   "checkInResultLevel5": "生日快乐 🥳", | ||||||
|   "checkInActivityTitle": "{} 在 {} 签到并获得了 {}", |   "checkInActivityTitle": "{} 在 {} 签到并获得了 {}", | ||||||
|   "eventCalander": "活动日历", |   "eventCalander": "活动日历", | ||||||
|   "eventCalanderEmpty": "该日无活动。", |   "eventCalanderEmpty": "该日无活动。", | ||||||
| @@ -304,6 +305,7 @@ | |||||||
|   "notifications": "通知", |   "notifications": "通知", | ||||||
|   "posts": "帖子", |   "posts": "帖子", | ||||||
|   "settingsBackgroundImage": "背景图片", |   "settingsBackgroundImage": "背景图片", | ||||||
|  |   "settingsBackgroundImageEnable": "显示背景图片", | ||||||
|   "settingsBackgroundImageClear": "清除背景图片", |   "settingsBackgroundImageClear": "清除背景图片", | ||||||
|   "settingsBackgroundGenerateColor": "从背景图像生成主题色", |   "settingsBackgroundGenerateColor": "从背景图像生成主题色", | ||||||
|   "messageNone": "没有内容可显示", |   "messageNone": "没有内容可显示", | ||||||
| @@ -314,6 +316,8 @@ | |||||||
|   "chatBreakNone": "无", |   "chatBreakNone": "无", | ||||||
|   "settingsRealmCompactView": "紧凑领域视图", |   "settingsRealmCompactView": "紧凑领域视图", | ||||||
|   "settingsMixedFeed": "混合动态", |   "settingsMixedFeed": "混合动态", | ||||||
|  |   "settingsDataSavingMode": "流量节省模式", | ||||||
|  |   "dataSavingHint": "流量节省模式", | ||||||
|   "settingsAutoTranslate": "自动翻译", |   "settingsAutoTranslate": "自动翻译", | ||||||
|   "settingsHideBottomNav": "隐藏底部导航", |   "settingsHideBottomNav": "隐藏底部导航", | ||||||
|   "settingsSoundEffects": "音效", |   "settingsSoundEffects": "音效", | ||||||
| @@ -857,5 +861,8 @@ | |||||||
|   "expiresIn": "过期时间(秒)", |   "expiresIn": "过期时间(秒)", | ||||||
|   "isOidc": "OIDC 兼容", |   "isOidc": "OIDC 兼容", | ||||||
|   "statusPresent": "至今", |   "statusPresent": "至今", | ||||||
|   "accountAutomated": "机器人" |   "accountAutomated": "机器人", | ||||||
|  |   "openInBrowser": "在浏览器中打开", | ||||||
|  |   "highlightPost": "精选帖子", | ||||||
|  |   "notableDayNext": "距离 {} 还有" | ||||||
| } | } | ||||||
|   | |||||||
| @@ -303,7 +303,8 @@ | |||||||
|     "notifications": "通知", |     "notifications": "通知", | ||||||
|     "posts": "帖子", |     "posts": "帖子", | ||||||
|     "settingsBackgroundImage": "背景圖片", |     "settingsBackgroundImage": "背景圖片", | ||||||
|     "settingsBackgroundImageClear": "清除背景圖片", |   "settingsBackgroundImageEnable": "顯示背景圖片", | ||||||
|  |   "settingsBackgroundImageClear": "清除背景圖片", | ||||||
|     "settingsBackgroundGenerateColor": "從背景圖像生成主題色", |     "settingsBackgroundGenerateColor": "從背景圖像生成主題色", | ||||||
|     "messageNone": "沒有內容可顯示", |     "messageNone": "沒有內容可顯示", | ||||||
|     "unreadMessages": { |     "unreadMessages": { | ||||||
| @@ -314,6 +315,8 @@ | |||||||
|     "settingsRealmCompactView": "緊湊領域視圖", |     "settingsRealmCompactView": "緊湊領域視圖", | ||||||
|     "settingsMixedFeed": "混合動態", |     "settingsMixedFeed": "混合動態", | ||||||
|     "settingsAutoTranslate": "自動翻譯", |     "settingsAutoTranslate": "自動翻譯", | ||||||
|  |     "settingsDataSavingMode": "低數據模式", | ||||||
|  |     "dataSavingHint": "低數據模式", | ||||||
|     "settingsHideBottomNav": "隱藏底部導航", |     "settingsHideBottomNav": "隱藏底部導航", | ||||||
|     "settingsSoundEffects": "音效", |     "settingsSoundEffects": "音效", | ||||||
|     "settingsAprilFoolFeatures": "愚人節功能", |     "settingsAprilFoolFeatures": "愚人節功能", | ||||||
|   | |||||||
							
								
								
									
										12
									
								
								assets/icons/icon-outline.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								assets/icons/icon-outline.svg
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | |||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" width="192" height="192" fill="none"> | ||||||
|  |     <path stroke="#fff" stroke-linecap="round" stroke-linejoin="round" stroke-width="12" | ||||||
|  |         d="M54 147h86" /> | ||||||
|  |     <path stroke="#fff" stroke-linecap="round" stroke-linejoin="round" stroke-width="10" | ||||||
|  |         d="M57 111s-2-4.5-2-10m22 22s-4 7-11 4m9-22s-2-4.5-2-10" /> | ||||||
|  |     <path stroke="#fff" stroke-linecap="round" stroke-linejoin="round" stroke-width="12" | ||||||
|  |         d="M54 147a32 32 0 0 1-11.999-61.665A39 39 0 0 1 81 46m59 101a30 30 0 0 0 29.933-28" /> | ||||||
|  |     <circle cx="132" cy="75" r="4" stroke="#fff" stroke-linecap="round" stroke-linejoin="round" | ||||||
|  |         stroke-width="8" /> | ||||||
|  |     <path stroke="#fff" stroke-linecap="round" stroke-linejoin="round" stroke-width="10" | ||||||
|  |         d="M112.5 41.217C100.843 47.961 93 60.564 93 75c0 6.375 1.53 12.393 4.242 17.707m69.513-35.419A38.84 38.84 0 0 1 171 75c0 14.433-7.84 27.034-19.493 33.779m-.793-43.317A20.9 20.9 0 0 1 153 75c0 7.77-4.221 14.556-10.495 18.188m-21.003-36.38C115.224 60.44 111 67.226 111 75a20.9 20.9 0 0 0 2.284 9.533" /> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 1.0 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/images/media-offline.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								assets/images/media-offline.jpg
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 461 KiB | 
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 307 KiB | 
| @@ -21,6 +21,6 @@ | |||||||
|   <key>CFBundleVersion</key> |   <key>CFBundleVersion</key> | ||||||
|   <string>1.0</string> |   <string>1.0</string> | ||||||
|   <key>MinimumOSVersion</key> |   <key>MinimumOSVersion</key> | ||||||
|   <string>12.0</string> |   <string>13.0</string> | ||||||
| </dict> | </dict> | ||||||
| </plist> | </plist> | ||||||
|   | |||||||
							
								
								
									
										162
									
								
								ios/Podfile.lock
									
									
									
									
									
								
							
							
						
						
									
										162
									
								
								ios/Podfile.lock
									
									
									
									
									
								
							| @@ -42,83 +42,83 @@ PODS: | |||||||
|     - Flutter |     - Flutter | ||||||
|   - file_saver (0.0.1): |   - file_saver (0.0.1): | ||||||
|     - Flutter |     - Flutter | ||||||
|   - Firebase/CoreOnly (12.0.0): |   - Firebase/CoreOnly (12.2.0): | ||||||
|     - FirebaseCore (~> 12.0.0) |     - FirebaseCore (~> 12.2.0) | ||||||
|   - Firebase/Crashlytics (12.0.0): |   - Firebase/Crashlytics (12.2.0): | ||||||
|     - Firebase/CoreOnly |     - Firebase/CoreOnly | ||||||
|     - FirebaseCrashlytics (~> 12.0.0) |     - FirebaseCrashlytics (~> 12.2.0) | ||||||
|   - Firebase/Messaging (12.0.0): |   - Firebase/Messaging (12.2.0): | ||||||
|     - Firebase/CoreOnly |     - Firebase/CoreOnly | ||||||
|     - FirebaseMessaging (~> 12.0.0) |     - FirebaseMessaging (~> 12.2.0) | ||||||
|   - firebase_analytics (12.0.0): |   - firebase_analytics (12.0.1): | ||||||
|     - firebase_core |     - firebase_core | ||||||
|     - FirebaseAnalytics (= 12.0.0) |     - FirebaseAnalytics (= 12.2.0) | ||||||
|     - Flutter |     - Flutter | ||||||
|   - firebase_core (4.0.0): |   - firebase_core (4.1.0): | ||||||
|     - Firebase/CoreOnly (= 12.0.0) |     - Firebase/CoreOnly (= 12.2.0) | ||||||
|     - Flutter |     - Flutter | ||||||
|   - firebase_crashlytics (5.0.0): |   - firebase_crashlytics (5.0.1): | ||||||
|     - Firebase/Crashlytics (= 12.0.0) |     - Firebase/Crashlytics (= 12.2.0) | ||||||
|     - firebase_core |     - firebase_core | ||||||
|     - Flutter |     - Flutter | ||||||
|   - firebase_messaging (16.0.0): |   - firebase_messaging (16.0.1): | ||||||
|     - Firebase/Messaging (= 12.0.0) |     - Firebase/Messaging (= 12.2.0) | ||||||
|     - firebase_core |     - firebase_core | ||||||
|     - Flutter |     - Flutter | ||||||
|   - FirebaseAnalytics (12.0.0): |   - FirebaseAnalytics (12.2.0): | ||||||
|     - FirebaseAnalytics/Default (= 12.0.0) |     - FirebaseAnalytics/Default (= 12.2.0) | ||||||
|     - FirebaseCore (~> 12.0.0) |     - FirebaseCore (~> 12.2.0) | ||||||
|     - FirebaseInstallations (~> 12.0.0) |     - FirebaseInstallations (~> 12.2.0) | ||||||
|     - GoogleUtilities/AppDelegateSwizzler (~> 8.1) |     - GoogleUtilities/AppDelegateSwizzler (~> 8.1) | ||||||
|     - GoogleUtilities/MethodSwizzler (~> 8.1) |     - GoogleUtilities/MethodSwizzler (~> 8.1) | ||||||
|     - GoogleUtilities/Network (~> 8.1) |     - GoogleUtilities/Network (~> 8.1) | ||||||
|     - "GoogleUtilities/NSData+zlib (~> 8.1)" |     - "GoogleUtilities/NSData+zlib (~> 8.1)" | ||||||
|     - nanopb (~> 3.30910.0) |     - nanopb (~> 3.30910.0) | ||||||
|   - FirebaseAnalytics/Default (12.0.0): |   - FirebaseAnalytics/Default (12.2.0): | ||||||
|     - FirebaseCore (~> 12.0.0) |     - FirebaseCore (~> 12.2.0) | ||||||
|     - FirebaseInstallations (~> 12.0.0) |     - FirebaseInstallations (~> 12.2.0) | ||||||
|     - GoogleAppMeasurement/Default (= 12.0.0) |     - GoogleAppMeasurement/Default (= 12.2.0) | ||||||
|     - GoogleUtilities/AppDelegateSwizzler (~> 8.1) |     - GoogleUtilities/AppDelegateSwizzler (~> 8.1) | ||||||
|     - GoogleUtilities/MethodSwizzler (~> 8.1) |     - GoogleUtilities/MethodSwizzler (~> 8.1) | ||||||
|     - GoogleUtilities/Network (~> 8.1) |     - GoogleUtilities/Network (~> 8.1) | ||||||
|     - "GoogleUtilities/NSData+zlib (~> 8.1)" |     - "GoogleUtilities/NSData+zlib (~> 8.1)" | ||||||
|     - nanopb (~> 3.30910.0) |     - nanopb (~> 3.30910.0) | ||||||
|   - FirebaseCore (12.0.0): |   - FirebaseCore (12.2.0): | ||||||
|     - FirebaseCoreInternal (~> 12.0.0) |     - FirebaseCoreInternal (~> 12.2.0) | ||||||
|     - GoogleUtilities/Environment (~> 8.1) |     - GoogleUtilities/Environment (~> 8.1) | ||||||
|     - GoogleUtilities/Logger (~> 8.1) |     - GoogleUtilities/Logger (~> 8.1) | ||||||
|   - FirebaseCoreExtension (12.0.0): |   - FirebaseCoreExtension (12.2.0): | ||||||
|     - FirebaseCore (~> 12.0.0) |     - FirebaseCore (~> 12.2.0) | ||||||
|   - FirebaseCoreInternal (12.0.0): |   - FirebaseCoreInternal (12.2.0): | ||||||
|     - "GoogleUtilities/NSData+zlib (~> 8.1)" |     - "GoogleUtilities/NSData+zlib (~> 8.1)" | ||||||
|   - FirebaseCrashlytics (12.0.0): |   - FirebaseCrashlytics (12.2.0): | ||||||
|     - FirebaseCore (~> 12.0.0) |     - FirebaseCore (~> 12.2.0) | ||||||
|     - FirebaseInstallations (~> 12.0.0) |     - FirebaseInstallations (~> 12.2.0) | ||||||
|     - FirebaseRemoteConfigInterop (~> 12.0.0) |     - FirebaseRemoteConfigInterop (~> 12.2.0) | ||||||
|     - FirebaseSessions (~> 12.0.0) |     - FirebaseSessions (~> 12.2.0) | ||||||
|     - GoogleDataTransport (~> 10.1) |     - GoogleDataTransport (~> 10.1) | ||||||
|     - GoogleUtilities/Environment (~> 8.1) |     - GoogleUtilities/Environment (~> 8.1) | ||||||
|     - nanopb (~> 3.30910.0) |     - nanopb (~> 3.30910.0) | ||||||
|     - PromisesObjC (~> 2.4) |     - PromisesObjC (~> 2.4) | ||||||
|   - FirebaseInstallations (12.0.0): |   - FirebaseInstallations (12.2.0): | ||||||
|     - FirebaseCore (~> 12.0.0) |     - FirebaseCore (~> 12.2.0) | ||||||
|     - GoogleUtilities/Environment (~> 8.1) |     - GoogleUtilities/Environment (~> 8.1) | ||||||
|     - GoogleUtilities/UserDefaults (~> 8.1) |     - GoogleUtilities/UserDefaults (~> 8.1) | ||||||
|     - PromisesObjC (~> 2.4) |     - PromisesObjC (~> 2.4) | ||||||
|   - FirebaseMessaging (12.0.0): |   - FirebaseMessaging (12.2.0): | ||||||
|     - FirebaseCore (~> 12.0.0) |     - FirebaseCore (~> 12.2.0) | ||||||
|     - FirebaseInstallations (~> 12.0.0) |     - FirebaseInstallations (~> 12.2.0) | ||||||
|     - GoogleDataTransport (~> 10.1) |     - GoogleDataTransport (~> 10.1) | ||||||
|     - GoogleUtilities/AppDelegateSwizzler (~> 8.1) |     - GoogleUtilities/AppDelegateSwizzler (~> 8.1) | ||||||
|     - GoogleUtilities/Environment (~> 8.1) |     - GoogleUtilities/Environment (~> 8.1) | ||||||
|     - GoogleUtilities/Reachability (~> 8.1) |     - GoogleUtilities/Reachability (~> 8.1) | ||||||
|     - GoogleUtilities/UserDefaults (~> 8.1) |     - GoogleUtilities/UserDefaults (~> 8.1) | ||||||
|     - nanopb (~> 3.30910.0) |     - nanopb (~> 3.30910.0) | ||||||
|   - FirebaseRemoteConfigInterop (12.0.0) |   - FirebaseRemoteConfigInterop (12.2.0) | ||||||
|   - FirebaseSessions (12.0.0): |   - FirebaseSessions (12.2.0): | ||||||
|     - FirebaseCore (~> 12.0.0) |     - FirebaseCore (~> 12.2.0) | ||||||
|     - FirebaseCoreExtension (~> 12.0.0) |     - FirebaseCoreExtension (~> 12.2.0) | ||||||
|     - FirebaseInstallations (~> 12.0.0) |     - FirebaseInstallations (~> 12.2.0) | ||||||
|     - GoogleDataTransport (~> 10.1) |     - GoogleDataTransport (~> 10.1) | ||||||
|     - GoogleUtilities/Environment (~> 8.1) |     - GoogleUtilities/Environment (~> 8.1) | ||||||
|     - GoogleUtilities/UserDefaults (~> 8.1) |     - GoogleUtilities/UserDefaults (~> 8.1) | ||||||
| @@ -136,6 +136,8 @@ PODS: | |||||||
|     - OrderedSet (~> 6.0.3) |     - OrderedSet (~> 6.0.3) | ||||||
|   - flutter_keyboard_visibility (0.0.1): |   - flutter_keyboard_visibility (0.0.1): | ||||||
|     - Flutter |     - Flutter | ||||||
|  |   - flutter_local_notifications (0.0.1): | ||||||
|  |     - Flutter | ||||||
|   - flutter_native_splash (2.4.3): |   - flutter_native_splash (2.4.3): | ||||||
|     - Flutter |     - Flutter | ||||||
|   - flutter_platform_alert (0.0.1): |   - flutter_platform_alert (0.0.1): | ||||||
| @@ -147,33 +149,33 @@ PODS: | |||||||
|   - flutter_udid (0.0.1): |   - flutter_udid (0.0.1): | ||||||
|     - Flutter |     - Flutter | ||||||
|     - SAMKeychain |     - SAMKeychain | ||||||
|   - flutter_webrtc (1.0.0): |   - flutter_webrtc (1.1.0): | ||||||
|     - Flutter |     - Flutter | ||||||
|     - WebRTC-SDK (= 137.7151.02) |     - WebRTC-SDK (= 137.7151.03) | ||||||
|   - gal (1.0.0): |   - gal (1.0.0): | ||||||
|     - Flutter |     - Flutter | ||||||
|     - FlutterMacOS |     - FlutterMacOS | ||||||
|   - GoogleAdsOnDeviceConversion (2.1.0): |   - GoogleAdsOnDeviceConversion (2.3.0): | ||||||
|     - GoogleUtilities/Logger (~> 8.1) |     - GoogleUtilities/Logger (~> 8.1) | ||||||
|     - GoogleUtilities/Network (~> 8.1) |     - GoogleUtilities/Network (~> 8.1) | ||||||
|     - nanopb (~> 3.30910.0) |     - nanopb (~> 3.30910.0) | ||||||
|   - GoogleAppMeasurement/Core (12.0.0): |   - GoogleAppMeasurement/Core (12.2.0): | ||||||
|     - GoogleUtilities/AppDelegateSwizzler (~> 8.1) |     - GoogleUtilities/AppDelegateSwizzler (~> 8.1) | ||||||
|     - GoogleUtilities/MethodSwizzler (~> 8.1) |     - GoogleUtilities/MethodSwizzler (~> 8.1) | ||||||
|     - GoogleUtilities/Network (~> 8.1) |     - GoogleUtilities/Network (~> 8.1) | ||||||
|     - "GoogleUtilities/NSData+zlib (~> 8.1)" |     - "GoogleUtilities/NSData+zlib (~> 8.1)" | ||||||
|     - nanopb (~> 3.30910.0) |     - nanopb (~> 3.30910.0) | ||||||
|   - GoogleAppMeasurement/Default (12.0.0): |   - GoogleAppMeasurement/Default (12.2.0): | ||||||
|     - GoogleAdsOnDeviceConversion (= 2.1.0) |     - GoogleAdsOnDeviceConversion (= 2.3.0) | ||||||
|     - GoogleAppMeasurement/Core (= 12.0.0) |     - GoogleAppMeasurement/Core (= 12.2.0) | ||||||
|     - GoogleAppMeasurement/IdentitySupport (= 12.0.0) |     - GoogleAppMeasurement/IdentitySupport (= 12.2.0) | ||||||
|     - GoogleUtilities/AppDelegateSwizzler (~> 8.1) |     - GoogleUtilities/AppDelegateSwizzler (~> 8.1) | ||||||
|     - GoogleUtilities/MethodSwizzler (~> 8.1) |     - GoogleUtilities/MethodSwizzler (~> 8.1) | ||||||
|     - GoogleUtilities/Network (~> 8.1) |     - GoogleUtilities/Network (~> 8.1) | ||||||
|     - "GoogleUtilities/NSData+zlib (~> 8.1)" |     - "GoogleUtilities/NSData+zlib (~> 8.1)" | ||||||
|     - nanopb (~> 3.30910.0) |     - nanopb (~> 3.30910.0) | ||||||
|   - GoogleAppMeasurement/IdentitySupport (12.0.0): |   - GoogleAppMeasurement/IdentitySupport (12.2.0): | ||||||
|     - GoogleAppMeasurement/Core (= 12.0.0) |     - GoogleAppMeasurement/Core (= 12.2.0) | ||||||
|     - GoogleUtilities/AppDelegateSwizzler (~> 8.1) |     - GoogleUtilities/AppDelegateSwizzler (~> 8.1) | ||||||
|     - GoogleUtilities/MethodSwizzler (~> 8.1) |     - GoogleUtilities/MethodSwizzler (~> 8.1) | ||||||
|     - GoogleUtilities/Network (~> 8.1) |     - GoogleUtilities/Network (~> 8.1) | ||||||
| @@ -217,7 +219,7 @@ PODS: | |||||||
|   - livekit_client (2.5.0): |   - livekit_client (2.5.0): | ||||||
|     - Flutter |     - Flutter | ||||||
|     - flutter_webrtc |     - flutter_webrtc | ||||||
|     - WebRTC-SDK (= 137.7151.02) |     - WebRTC-SDK (= 137.7151.03) | ||||||
|   - local_auth_darwin (0.0.1): |   - local_auth_darwin (0.0.1): | ||||||
|     - Flutter |     - Flutter | ||||||
|     - FlutterMacOS |     - FlutterMacOS | ||||||
| @@ -250,9 +252,9 @@ PODS: | |||||||
|   - record_ios (1.1.0): |   - record_ios (1.1.0): | ||||||
|     - Flutter |     - Flutter | ||||||
|   - SAMKeychain (1.5.3) |   - SAMKeychain (1.5.3) | ||||||
|   - SDWebImage (5.21.1): |   - SDWebImage (5.21.2): | ||||||
|     - SDWebImage/Core (= 5.21.1) |     - SDWebImage/Core (= 5.21.2) | ||||||
|   - SDWebImage/Core (5.21.1) |   - SDWebImage/Core (5.21.2) | ||||||
|   - share_plus (0.0.1): |   - share_plus (0.0.1): | ||||||
|     - Flutter |     - Flutter | ||||||
|   - shared_preferences_foundation (0.0.1): |   - shared_preferences_foundation (0.0.1): | ||||||
| @@ -297,7 +299,7 @@ PODS: | |||||||
|     - Flutter |     - Flutter | ||||||
|   - wakelock_plus (0.0.1): |   - wakelock_plus (0.0.1): | ||||||
|     - Flutter |     - Flutter | ||||||
|   - WebRTC-SDK (137.7151.02) |   - WebRTC-SDK (137.7151.03) | ||||||
|  |  | ||||||
| DEPENDENCIES: | DEPENDENCIES: | ||||||
|   - Alamofire |   - Alamofire | ||||||
| @@ -314,6 +316,7 @@ DEPENDENCIES: | |||||||
|   - flutter_app_update (from `.symlinks/plugins/flutter_app_update/ios`) |   - flutter_app_update (from `.symlinks/plugins/flutter_app_update/ios`) | ||||||
|   - flutter_inappwebview_ios (from `.symlinks/plugins/flutter_inappwebview_ios/ios`) |   - flutter_inappwebview_ios (from `.symlinks/plugins/flutter_inappwebview_ios/ios`) | ||||||
|   - flutter_keyboard_visibility (from `.symlinks/plugins/flutter_keyboard_visibility/ios`) |   - flutter_keyboard_visibility (from `.symlinks/plugins/flutter_keyboard_visibility/ios`) | ||||||
|  |   - flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`) | ||||||
|   - flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`) |   - flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`) | ||||||
|   - flutter_platform_alert (from `.symlinks/plugins/flutter_platform_alert/ios`) |   - flutter_platform_alert (from `.symlinks/plugins/flutter_platform_alert/ios`) | ||||||
|   - flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`) |   - flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`) | ||||||
| @@ -402,6 +405,8 @@ EXTERNAL SOURCES: | |||||||
|     :path: ".symlinks/plugins/flutter_inappwebview_ios/ios" |     :path: ".symlinks/plugins/flutter_inappwebview_ios/ios" | ||||||
|   flutter_keyboard_visibility: |   flutter_keyboard_visibility: | ||||||
|     :path: ".symlinks/plugins/flutter_keyboard_visibility/ios" |     :path: ".symlinks/plugins/flutter_keyboard_visibility/ios" | ||||||
|  |   flutter_local_notifications: | ||||||
|  |     :path: ".symlinks/plugins/flutter_local_notifications/ios" | ||||||
|   flutter_native_splash: |   flutter_native_splash: | ||||||
|     :path: ".symlinks/plugins/flutter_native_splash/ios" |     :path: ".symlinks/plugins/flutter_native_splash/ios" | ||||||
|   flutter_platform_alert: |   flutter_platform_alert: | ||||||
| @@ -470,39 +475,40 @@ SPEC CHECKSUMS: | |||||||
|   DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60 |   DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60 | ||||||
|   file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be |   file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be | ||||||
|   file_saver: 6cdbcddd690cb02b0c1a0c225b37cd805c2bf8b6 |   file_saver: 6cdbcddd690cb02b0c1a0c225b37cd805c2bf8b6 | ||||||
|   Firebase: 800d487043c0557d9faed71477a38d9aafb08a41 |   Firebase: 26f6f8d460603af3df970ad505b16b15f5e2e9a1 | ||||||
|   firebase_analytics: cd56fc56f75c1df30a6ff5290cd56e230996a76d |   firebase_analytics: 111ff65791a430356bd6c7e4d7339537fc6a15ae | ||||||
|   firebase_core: 633e1851ffe1b9ab875f6467a4f574c79cef02e4 |   firebase_core: 3ff52146406557dddd01d570e807e203ec7e1302 | ||||||
|   firebase_crashlytics: 2c6c1a17900a38081d938330e9f48e60ec5b255d |   firebase_crashlytics: 3637078b718a52dc9fb4d64e37c969e86b87ff6f | ||||||
|   firebase_messaging: d17feef781edc84ebefe62624fb384358ad96361 |   firebase_messaging: 3dcc998dd98e1e54af75d0cccae8606eba43553c | ||||||
|   FirebaseAnalytics: 6d790cd1b159b4eb61a99948df0934ce505a34f7 |   FirebaseAnalytics: e04e23bc070e3014aa5cf4980f9df7ce5cd79ec8 | ||||||
|   FirebaseCore: 055f4ab117d5964158c833f3d5e7ec6d91648d4a |   FirebaseCore: 311c48a147ad4a0ab7febbaed89e8025c67510cd | ||||||
|   FirebaseCoreExtension: 639afb3de6abd611952be78a794c54a47fa0f361 |   FirebaseCoreExtension: 73af080c22a2f7b44cefa391dc08f7e4ee162cb5 | ||||||
|   FirebaseCoreInternal: dedc28e569a4be85f38f3d6af1070a2e12018d55 |   FirebaseCoreInternal: 56ea29f3dad2894f81b060f706f9d53509b6ed3b | ||||||
|   FirebaseCrashlytics: db75aa0cab8d00f68406fa247c32fe17ade884d7 |   FirebaseCrashlytics: f83cbf176d5c637ade108c0aacf1ccbd5ec499bf | ||||||
|   FirebaseInstallations: d4c7c958f99c8860d7fcece786314ae790e2f988 |   FirebaseInstallations: 3e884b01feabdf67582a80f3250425a00979b4ed | ||||||
|   FirebaseMessaging: af49f8d7c0a3d2a017d9302c80946f45a7777dde |   FirebaseMessaging: 43ec73bbfedd0c385a849bb91593ab4ad4b9e48e | ||||||
|   FirebaseRemoteConfigInterop: bfa0ea72ba3dc5af739777296424e46bd6f42613 |   FirebaseRemoteConfigInterop: 0896fd52ab72586a355c8f389ff85aaa9e5375e1 | ||||||
|   FirebaseSessions: 4e784acda213108aafef536535cdfc03504acc42 |   FirebaseSessions: f4692789e770bec66ce17d772c0e9561c4f11737 | ||||||
|   Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 |   Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 | ||||||
|   flutter_app_update: 816fdb2e30e4832a7c45e3f108d391c42ef040a9 |   flutter_app_update: 816fdb2e30e4832a7c45e3f108d391c42ef040a9 | ||||||
|   flutter_inappwebview_ios: b89ba3482b96fb25e00c967aae065701b66e9b99 |   flutter_inappwebview_ios: b89ba3482b96fb25e00c967aae065701b66e9b99 | ||||||
|   flutter_keyboard_visibility: 4625131e43015dbbe759d9b20daaf77e0e3f6619 |   flutter_keyboard_visibility: 4625131e43015dbbe759d9b20daaf77e0e3f6619 | ||||||
|  |   flutter_local_notifications: a5a732f069baa862e728d839dd2ebb904737effb | ||||||
|   flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf |   flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf | ||||||
|   flutter_platform_alert: bf3b5fcd4ac14bd637e20527e9c471633071afd3 |   flutter_platform_alert: bf3b5fcd4ac14bd637e20527e9c471633071afd3 | ||||||
|   flutter_secure_storage: 1ed9476fba7e7a782b22888f956cce43e2c62f13 |   flutter_secure_storage: 1ed9476fba7e7a782b22888f956cce43e2c62f13 | ||||||
|   flutter_timezone: 7c838e17ffd4645d261e87037e5bebf6d38fe544 |   flutter_timezone: 7c838e17ffd4645d261e87037e5bebf6d38fe544 | ||||||
|   flutter_udid: f7c3884e6ec2951efe4f9de082257fc77c4d15e9 |   flutter_udid: f7c3884e6ec2951efe4f9de082257fc77c4d15e9 | ||||||
|   flutter_webrtc: 6f7da106613d52ade777d5b4875a43f48c28b457 |   flutter_webrtc: b0b2e04411747142962164a1cfa43a1af9a0afac | ||||||
|   gal: baecd024ebfd13c441269ca7404792a7152fde89 |   gal: baecd024ebfd13c441269ca7404792a7152fde89 | ||||||
|   GoogleAdsOnDeviceConversion: 2be6297a4f048459e0ae17fad9bfd2844e10cf64 |   GoogleAdsOnDeviceConversion: 9090c435cde08903e8dd1ba2c77fbec9e46d9afe | ||||||
|   GoogleAppMeasurement: 8f6ab04ad6ae493b53fcf56bd26323fb2f1384f3 |   GoogleAppMeasurement: 09f341dfa8527d1612a09cbfe809a242c0b737af | ||||||
|   GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7 |   GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7 | ||||||
|   GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1 |   GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1 | ||||||
|   image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a |   image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a | ||||||
|   irondash_engine_context: 8e58ca8e0212ee9d1c7dc6a42121849986c88486 |   irondash_engine_context: 8e58ca8e0212ee9d1c7dc6a42121849986c88486 | ||||||
|   Kingfisher: ff0d31a1f07bdff6a1ebb3ba08b8e6e567b6500c |   Kingfisher: ff0d31a1f07bdff6a1ebb3ba08b8e6e567b6500c | ||||||
|   livekit_client: e3b79b99405428aac439b6b76a254cd9a11dbbfb |   livekit_client: f810c81bbbc229a84f60b09e66603ac4e93f7599 | ||||||
|   local_auth_darwin: d2e8c53ef0c4f43c646462e3415432c4dab3ae19 |   local_auth_darwin: d2e8c53ef0c4f43c646462e3415432c4dab3ae19 | ||||||
|   media_kit_libs_ios_video: 5a18affdb97d1f5d466dc79988b13eff6c5e2854 |   media_kit_libs_ios_video: 5a18affdb97d1f5d466dc79988b13eff6c5e2854 | ||||||
|   media_kit_video: 1746e198cb697d1ffb734b1d05ec429d1fcd1474 |   media_kit_video: 1746e198cb697d1ffb734b1d05ec429d1fcd1474 | ||||||
| @@ -518,7 +524,7 @@ SPEC CHECKSUMS: | |||||||
|   receive_sharing_intent: 222384f00ffe7e952bbfabaa9e3967cb87e5fe00 |   receive_sharing_intent: 222384f00ffe7e952bbfabaa9e3967cb87e5fe00 | ||||||
|   record_ios: f75fa1d57f840012775c0e93a38a7f3ceea1a374 |   record_ios: f75fa1d57f840012775c0e93a38a7f3ceea1a374 | ||||||
|   SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c |   SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c | ||||||
|   SDWebImage: f29024626962457f3470184232766516dee8dfea |   SDWebImage: 9f177d83116802728e122410fb25ad88f5c7608a | ||||||
|   share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a |   share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a | ||||||
|   shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 |   shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 | ||||||
|   sign_in_with_apple: c5dcc141574c8c54d5ac99dd2163c0c72ad22418 |   sign_in_with_apple: c5dcc141574c8c54d5ac99dd2163c0c72ad22418 | ||||||
| @@ -530,7 +536,7 @@ SPEC CHECKSUMS: | |||||||
|   url_launcher_ios: 694010445543906933d732453a59da0a173ae33d |   url_launcher_ios: 694010445543906933d732453a59da0a173ae33d | ||||||
|   volume_controller: 3657a1f65bedb98fa41ff7dc5793537919f31b12 |   volume_controller: 3657a1f65bedb98fa41ff7dc5793537919f31b12 | ||||||
|   wakelock_plus: e29112ab3ef0b318e58cfa5c32326458be66b556 |   wakelock_plus: e29112ab3ef0b318e58cfa5c32326458be66b556 | ||||||
|   WebRTC-SDK: d20de357dcbf7c9696b124b39f3ff62125107e4b |   WebRTC-SDK: 69d4e56b0b4b27d788e87bab9b9a1326ed05b1e3 | ||||||
|  |  | ||||||
| PODFILE CHECKSUM: c818292390b02fa379036ea099713a332bd7193f | PODFILE CHECKSUM: c818292390b02fa379036ea099713a332bd7193f | ||||||
|  |  | ||||||
|   | |||||||
| @@ -853,7 +853,7 @@ | |||||||
| 				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; | 				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; | ||||||
| 				GCC_WARN_UNUSED_FUNCTION = YES; | 				GCC_WARN_UNUSED_FUNCTION = YES; | ||||||
| 				GCC_WARN_UNUSED_VARIABLE = YES; | 				GCC_WARN_UNUSED_VARIABLE = YES; | ||||||
| 				IPHONEOS_DEPLOYMENT_TARGET = 12.0; | 				IPHONEOS_DEPLOYMENT_TARGET = 13.0; | ||||||
| 				MTL_ENABLE_DEBUG_INFO = NO; | 				MTL_ENABLE_DEBUG_INFO = NO; | ||||||
| 				SDKROOT = iphoneos; | 				SDKROOT = iphoneos; | ||||||
| 				SUPPORTED_PLATFORMS = iphoneos; | 				SUPPORTED_PLATFORMS = iphoneos; | ||||||
| @@ -897,6 +897,7 @@ | |||||||
| 				CODE_SIGN_STYLE = Automatic; | 				CODE_SIGN_STYLE = Automatic; | ||||||
| 				CURRENT_PROJECT_VERSION = 1; | 				CURRENT_PROJECT_VERSION = 1; | ||||||
| 				GENERATE_INFOPLIST_FILE = YES; | 				GENERATE_INFOPLIST_FILE = YES; | ||||||
|  | 				IPHONEOS_DEPLOYMENT_TARGET = 15.0; | ||||||
| 				MARKETING_VERSION = 1.0; | 				MARKETING_VERSION = 1.0; | ||||||
| 				PRODUCT_BUNDLE_IDENTIFIER = dev.solsynth.solian.RunnerTests; | 				PRODUCT_BUNDLE_IDENTIFIER = dev.solsynth.solian.RunnerTests; | ||||||
| 				PRODUCT_NAME = "$(TARGET_NAME)"; | 				PRODUCT_NAME = "$(TARGET_NAME)"; | ||||||
| @@ -915,6 +916,7 @@ | |||||||
| 				CODE_SIGN_STYLE = Automatic; | 				CODE_SIGN_STYLE = Automatic; | ||||||
| 				CURRENT_PROJECT_VERSION = 1; | 				CURRENT_PROJECT_VERSION = 1; | ||||||
| 				GENERATE_INFOPLIST_FILE = YES; | 				GENERATE_INFOPLIST_FILE = YES; | ||||||
|  | 				IPHONEOS_DEPLOYMENT_TARGET = 15.0; | ||||||
| 				MARKETING_VERSION = 1.0; | 				MARKETING_VERSION = 1.0; | ||||||
| 				PRODUCT_BUNDLE_IDENTIFIER = dev.solsynth.solian.RunnerTests; | 				PRODUCT_BUNDLE_IDENTIFIER = dev.solsynth.solian.RunnerTests; | ||||||
| 				PRODUCT_NAME = "$(TARGET_NAME)"; | 				PRODUCT_NAME = "$(TARGET_NAME)"; | ||||||
| @@ -931,6 +933,7 @@ | |||||||
| 				CODE_SIGN_STYLE = Automatic; | 				CODE_SIGN_STYLE = Automatic; | ||||||
| 				CURRENT_PROJECT_VERSION = 1; | 				CURRENT_PROJECT_VERSION = 1; | ||||||
| 				GENERATE_INFOPLIST_FILE = YES; | 				GENERATE_INFOPLIST_FILE = YES; | ||||||
|  | 				IPHONEOS_DEPLOYMENT_TARGET = 15.0; | ||||||
| 				MARKETING_VERSION = 1.0; | 				MARKETING_VERSION = 1.0; | ||||||
| 				PRODUCT_BUNDLE_IDENTIFIER = dev.solsynth.solian.RunnerTests; | 				PRODUCT_BUNDLE_IDENTIFIER = dev.solsynth.solian.RunnerTests; | ||||||
| 				PRODUCT_NAME = "$(TARGET_NAME)"; | 				PRODUCT_NAME = "$(TARGET_NAME)"; | ||||||
| @@ -1078,7 +1081,7 @@ | |||||||
| 				INFOPLIST_FILE = SolianShareExtension/Info.plist; | 				INFOPLIST_FILE = SolianShareExtension/Info.plist; | ||||||
| 				INFOPLIST_KEY_CFBundleDisplayName = SolianShareExtension; | 				INFOPLIST_KEY_CFBundleDisplayName = SolianShareExtension; | ||||||
| 				INFOPLIST_KEY_NSHumanReadableCopyright = ""; | 				INFOPLIST_KEY_NSHumanReadableCopyright = ""; | ||||||
| 				IPHONEOS_DEPLOYMENT_TARGET = 13.0; | 				IPHONEOS_DEPLOYMENT_TARGET = 15.0; | ||||||
| 				LD_RUNPATH_SEARCH_PATHS = ( | 				LD_RUNPATH_SEARCH_PATHS = ( | ||||||
| 					"$(inherited)", | 					"$(inherited)", | ||||||
| 					"@executable_path/Frameworks", | 					"@executable_path/Frameworks", | ||||||
| @@ -1121,7 +1124,7 @@ | |||||||
| 				INFOPLIST_FILE = SolianShareExtension/Info.plist; | 				INFOPLIST_FILE = SolianShareExtension/Info.plist; | ||||||
| 				INFOPLIST_KEY_CFBundleDisplayName = SolianShareExtension; | 				INFOPLIST_KEY_CFBundleDisplayName = SolianShareExtension; | ||||||
| 				INFOPLIST_KEY_NSHumanReadableCopyright = ""; | 				INFOPLIST_KEY_NSHumanReadableCopyright = ""; | ||||||
| 				IPHONEOS_DEPLOYMENT_TARGET = 13.0; | 				IPHONEOS_DEPLOYMENT_TARGET = 15.0; | ||||||
| 				LD_RUNPATH_SEARCH_PATHS = ( | 				LD_RUNPATH_SEARCH_PATHS = ( | ||||||
| 					"$(inherited)", | 					"$(inherited)", | ||||||
| 					"@executable_path/Frameworks", | 					"@executable_path/Frameworks", | ||||||
| @@ -1161,7 +1164,7 @@ | |||||||
| 				INFOPLIST_FILE = SolianShareExtension/Info.plist; | 				INFOPLIST_FILE = SolianShareExtension/Info.plist; | ||||||
| 				INFOPLIST_KEY_CFBundleDisplayName = SolianShareExtension; | 				INFOPLIST_KEY_CFBundleDisplayName = SolianShareExtension; | ||||||
| 				INFOPLIST_KEY_NSHumanReadableCopyright = ""; | 				INFOPLIST_KEY_NSHumanReadableCopyright = ""; | ||||||
| 				IPHONEOS_DEPLOYMENT_TARGET = 13.0; | 				IPHONEOS_DEPLOYMENT_TARGET = 15.0; | ||||||
| 				LD_RUNPATH_SEARCH_PATHS = ( | 				LD_RUNPATH_SEARCH_PATHS = ( | ||||||
| 					"$(inherited)", | 					"$(inherited)", | ||||||
| 					"@executable_path/Frameworks", | 					"@executable_path/Frameworks", | ||||||
| @@ -1348,7 +1351,7 @@ | |||||||
| 				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; | 				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; | ||||||
| 				GCC_WARN_UNUSED_FUNCTION = YES; | 				GCC_WARN_UNUSED_FUNCTION = YES; | ||||||
| 				GCC_WARN_UNUSED_VARIABLE = YES; | 				GCC_WARN_UNUSED_VARIABLE = YES; | ||||||
| 				IPHONEOS_DEPLOYMENT_TARGET = 12.0; | 				IPHONEOS_DEPLOYMENT_TARGET = 13.0; | ||||||
| 				MTL_ENABLE_DEBUG_INFO = YES; | 				MTL_ENABLE_DEBUG_INFO = YES; | ||||||
| 				ONLY_ACTIVE_ARCH = YES; | 				ONLY_ACTIVE_ARCH = YES; | ||||||
| 				SDKROOT = iphoneos; | 				SDKROOT = iphoneos; | ||||||
| @@ -1399,7 +1402,7 @@ | |||||||
| 				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; | 				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; | ||||||
| 				GCC_WARN_UNUSED_FUNCTION = YES; | 				GCC_WARN_UNUSED_FUNCTION = YES; | ||||||
| 				GCC_WARN_UNUSED_VARIABLE = YES; | 				GCC_WARN_UNUSED_VARIABLE = YES; | ||||||
| 				IPHONEOS_DEPLOYMENT_TARGET = 12.0; | 				IPHONEOS_DEPLOYMENT_TARGET = 13.0; | ||||||
| 				MTL_ENABLE_DEBUG_INFO = NO; | 				MTL_ENABLE_DEBUG_INFO = NO; | ||||||
| 				SDKROOT = iphoneos; | 				SDKROOT = iphoneos; | ||||||
| 				SUPPORTED_PLATFORMS = iphoneos; | 				SUPPORTED_PLATFORMS = iphoneos; | ||||||
|   | |||||||
| @@ -47,6 +47,7 @@ class NotificationService: UNNotificationServiceExtension { | |||||||
|     private func processNotification(request: UNNotificationRequest, content: UNMutableNotificationContent) throws { |     private func processNotification(request: UNNotificationRequest, content: UNMutableNotificationContent) throws { | ||||||
|         switch content.userInfo["type"] as? String { |         switch content.userInfo["type"] as? String { | ||||||
|         case "messages.new": |         case "messages.new": | ||||||
|  |             content.categoryIdentifier = "REPLYABLE_MESSAGE" | ||||||
|             try handleMessagingNotification(request: request, content: content) |             try handleMessagingNotification(request: request, content: content) | ||||||
|         default: |         default: | ||||||
|             try handleDefaultNotification(content: content) |             try handleDefaultNotification(content: content) | ||||||
| @@ -60,8 +61,6 @@ class NotificationService: UNNotificationServiceExtension { | |||||||
|          |          | ||||||
|         let pfpIdentifier = meta["pfp"] as? String |         let pfpIdentifier = meta["pfp"] as? String | ||||||
|          |          | ||||||
|         content.categoryIdentifier = "REPLYABLE_MESSAGE" |  | ||||||
|          |  | ||||||
|         let metaCopy = meta as? [String: Any] ?? [:] |         let metaCopy = meta as? [String: Any] ?? [:] | ||||||
|         let pfpUrl = pfpIdentifier != nil ? getAttachmentUrl(for: pfpIdentifier!) : nil |         let pfpUrl = pfpIdentifier != nil ? getAttachmentUrl(for: pfpIdentifier!) : nil | ||||||
|          |          | ||||||
|   | |||||||
| @@ -30,7 +30,6 @@ import 'package:shared_preferences/shared_preferences.dart'; | |||||||
| import 'package:image_picker_platform_interface/image_picker_platform_interface.dart'; | import 'package:image_picker_platform_interface/image_picker_platform_interface.dart'; | ||||||
| import 'package:flutter_native_splash/flutter_native_splash.dart'; | import 'package:flutter_native_splash/flutter_native_splash.dart'; | ||||||
| import 'package:url_launcher/url_launcher_string.dart'; | import 'package:url_launcher/url_launcher_string.dart'; | ||||||
| import 'package:flutter_langdetect/flutter_langdetect.dart' as langdetect; |  | ||||||
|  |  | ||||||
| @pragma('vm:entry-point') | @pragma('vm:entry-point') | ||||||
| Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async { | Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async { | ||||||
| @@ -52,7 +51,6 @@ void main() async { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   try { |   try { | ||||||
|     await langdetect.initLangDetect(); |  | ||||||
|     await EasyLocalization.ensureInitialized(); |     await EasyLocalization.ensureInitialized(); | ||||||
|  |  | ||||||
|     if (kIsWeb || !Platform.isLinux) { |     if (kIsWeb || !Platform.isLinux) { | ||||||
| @@ -225,6 +223,7 @@ class IslandApp extends HookConsumerWidget { | |||||||
|           if (user.value != null) { |           if (user.value != null) { | ||||||
|             final apiClient = ref.read(apiClientProvider); |             final apiClient = ref.read(apiClientProvider); | ||||||
|             subscribePushNotification(apiClient); |             subscribePushNotification(apiClient); | ||||||
|  |             initializeLocalNotifications(); | ||||||
|             final wsNotifier = ref.read(websocketStateProvider.notifier); |             final wsNotifier = ref.read(websocketStateProvider.notifier); | ||||||
|             wsNotifier.connect(); |             wsNotifier.connect(); | ||||||
|           } |           } | ||||||
| @@ -241,6 +240,7 @@ class IslandApp extends HookConsumerWidget { | |||||||
|       themeMode: ThemeMode.system, |       themeMode: ThemeMode.system, | ||||||
|       routerConfig: router, |       routerConfig: router, | ||||||
|       supportedLocales: context.supportedLocales, |       supportedLocales: context.supportedLocales, | ||||||
|  |       scrollBehavior: AppScrollBehavior(), | ||||||
|       localizationsDelegates: [ |       localizationsDelegates: [ | ||||||
|         ...context.localizationDelegates, |         ...context.localizationDelegates, | ||||||
|         CroppyLocalizations.delegate, |         CroppyLocalizations.delegate, | ||||||
|   | |||||||
| @@ -13,11 +13,13 @@ sealed class SnAccount with _$SnAccount { | |||||||
|     required String name, |     required String name, | ||||||
|     required String nick, |     required String nick, | ||||||
|     required String language, |     required String language, | ||||||
|  |     @Default("") String region, | ||||||
|     required bool isSuperuser, |     required bool isSuperuser, | ||||||
|     required String? automatedId, |     required String? automatedId, | ||||||
|     required SnAccountProfile profile, |     required SnAccountProfile profile, | ||||||
|     required SnWalletSubscriptionRef? perkSubscription, |     required SnWalletSubscriptionRef? perkSubscription, | ||||||
|     @Default([]) List<SnAccountBadge> badges, |     @Default([]) List<SnAccountBadge> badges, | ||||||
|  |     @Default([]) List<SnContactMethod> contacts, | ||||||
|     required DateTime createdAt, |     required DateTime createdAt, | ||||||
|     required DateTime updatedAt, |     required DateTime updatedAt, | ||||||
|     required DateTime? deletedAt, |     required DateTime? deletedAt, | ||||||
| @@ -134,6 +136,7 @@ sealed class SnContactMethod with _$SnContactMethod { | |||||||
|     required int type, |     required int type, | ||||||
|     required DateTime? verifiedAt, |     required DateTime? verifiedAt, | ||||||
|     required bool isPrimary, |     required bool isPrimary, | ||||||
|  |     required bool isPublic, | ||||||
|     required String content, |     required String content, | ||||||
|     required String accountId, |     required String accountId, | ||||||
|     required DateTime createdAt, |     required DateTime createdAt, | ||||||
|   | |||||||
| @@ -15,7 +15,7 @@ T _$identity<T>(T value) => value; | |||||||
| /// @nodoc | /// @nodoc | ||||||
| mixin _$SnAccount { | mixin _$SnAccount { | ||||||
|  |  | ||||||
|  String get id; String get name; String get nick; String get language; bool get isSuperuser; String? get automatedId; SnAccountProfile get profile; SnWalletSubscriptionRef? get perkSubscription; List<SnAccountBadge> get badges; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; |  String get id; String get name; String get nick; String get language; String get region; bool get isSuperuser; String? get automatedId; SnAccountProfile get profile; SnWalletSubscriptionRef? get perkSubscription; List<SnAccountBadge> get badges; List<SnContactMethod> get contacts; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; | ||||||
| /// Create a copy of SnAccount | /// Create a copy of SnAccount | ||||||
| /// with the given fields replaced by the non-null parameter values. | /// with the given fields replaced by the non-null parameter values. | ||||||
| @JsonKey(includeFromJson: false, includeToJson: false) | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
| @@ -28,16 +28,16 @@ $SnAccountCopyWith<SnAccount> get copyWith => _$SnAccountCopyWithImpl<SnAccount> | |||||||
|  |  | ||||||
| @override | @override | ||||||
| bool operator ==(Object other) { | bool operator ==(Object other) { | ||||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is SnAccount&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.nick, nick) || other.nick == nick)&&(identical(other.language, language) || other.language == language)&&(identical(other.isSuperuser, isSuperuser) || other.isSuperuser == isSuperuser)&&(identical(other.automatedId, automatedId) || other.automatedId == automatedId)&&(identical(other.profile, profile) || other.profile == profile)&&(identical(other.perkSubscription, perkSubscription) || other.perkSubscription == perkSubscription)&&const DeepCollectionEquality().equals(other.badges, badges)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)); |   return identical(this, other) || (other.runtimeType == runtimeType&&other is SnAccount&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.nick, nick) || other.nick == nick)&&(identical(other.language, language) || other.language == language)&&(identical(other.region, region) || other.region == region)&&(identical(other.isSuperuser, isSuperuser) || other.isSuperuser == isSuperuser)&&(identical(other.automatedId, automatedId) || other.automatedId == automatedId)&&(identical(other.profile, profile) || other.profile == profile)&&(identical(other.perkSubscription, perkSubscription) || other.perkSubscription == perkSubscription)&&const DeepCollectionEquality().equals(other.badges, badges)&&const DeepCollectionEquality().equals(other.contacts, contacts)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)); | ||||||
| } | } | ||||||
|  |  | ||||||
| @JsonKey(includeFromJson: false, includeToJson: false) | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
| @override | @override | ||||||
| int get hashCode => Object.hash(runtimeType,id,name,nick,language,isSuperuser,automatedId,profile,perkSubscription,const DeepCollectionEquality().hash(badges),createdAt,updatedAt,deletedAt); | int get hashCode => Object.hash(runtimeType,id,name,nick,language,region,isSuperuser,automatedId,profile,perkSubscription,const DeepCollectionEquality().hash(badges),const DeepCollectionEquality().hash(contacts),createdAt,updatedAt,deletedAt); | ||||||
|  |  | ||||||
| @override | @override | ||||||
| String toString() { | String toString() { | ||||||
|   return 'SnAccount(id: $id, name: $name, nick: $nick, language: $language, isSuperuser: $isSuperuser, automatedId: $automatedId, profile: $profile, perkSubscription: $perkSubscription, badges: $badges, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; |   return 'SnAccount(id: $id, name: $name, nick: $nick, language: $language, region: $region, isSuperuser: $isSuperuser, automatedId: $automatedId, profile: $profile, perkSubscription: $perkSubscription, badges: $badges, contacts: $contacts, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -48,7 +48,7 @@ abstract mixin class $SnAccountCopyWith<$Res>  { | |||||||
|   factory $SnAccountCopyWith(SnAccount value, $Res Function(SnAccount) _then) = _$SnAccountCopyWithImpl; |   factory $SnAccountCopyWith(SnAccount value, $Res Function(SnAccount) _then) = _$SnAccountCopyWithImpl; | ||||||
| @useResult | @useResult | ||||||
| $Res call({ | $Res call({ | ||||||
|  String id, String name, String nick, String language, bool isSuperuser, String? automatedId, SnAccountProfile profile, SnWalletSubscriptionRef? perkSubscription, List<SnAccountBadge> badges, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt |  String id, String name, String nick, String language, String region, bool isSuperuser, String? automatedId, SnAccountProfile profile, SnWalletSubscriptionRef? perkSubscription, List<SnAccountBadge> badges, List<SnContactMethod> contacts, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -65,18 +65,20 @@ class _$SnAccountCopyWithImpl<$Res> | |||||||
|  |  | ||||||
| /// Create a copy of SnAccount | /// Create a copy of SnAccount | ||||||
| /// with the given fields replaced by the non-null parameter values. | /// with the given fields replaced by the non-null parameter values. | ||||||
| @pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? name = null,Object? nick = null,Object? language = null,Object? isSuperuser = null,Object? automatedId = freezed,Object? profile = null,Object? perkSubscription = freezed,Object? badges = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) { | @pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? name = null,Object? nick = null,Object? language = null,Object? region = null,Object? isSuperuser = null,Object? automatedId = freezed,Object? profile = null,Object? perkSubscription = freezed,Object? badges = null,Object? contacts = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) { | ||||||
|   return _then(_self.copyWith( |   return _then(_self.copyWith( | ||||||
| id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable | id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable | ||||||
| as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable | as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable | ||||||
| as String,nick: null == nick ? _self.nick : nick // ignore: cast_nullable_to_non_nullable | as String,nick: null == nick ? _self.nick : nick // ignore: cast_nullable_to_non_nullable | ||||||
| as String,language: null == language ? _self.language : language // ignore: cast_nullable_to_non_nullable | as String,language: null == language ? _self.language : language // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,region: null == region ? _self.region : region // ignore: cast_nullable_to_non_nullable | ||||||
| as String,isSuperuser: null == isSuperuser ? _self.isSuperuser : isSuperuser // ignore: cast_nullable_to_non_nullable | as String,isSuperuser: null == isSuperuser ? _self.isSuperuser : isSuperuser // ignore: cast_nullable_to_non_nullable | ||||||
| as bool,automatedId: freezed == automatedId ? _self.automatedId : automatedId // ignore: cast_nullable_to_non_nullable | as bool,automatedId: freezed == automatedId ? _self.automatedId : automatedId // ignore: cast_nullable_to_non_nullable | ||||||
| as String?,profile: null == profile ? _self.profile : profile // ignore: cast_nullable_to_non_nullable | as String?,profile: null == profile ? _self.profile : profile // ignore: cast_nullable_to_non_nullable | ||||||
| as SnAccountProfile,perkSubscription: freezed == perkSubscription ? _self.perkSubscription : perkSubscription // ignore: cast_nullable_to_non_nullable | as SnAccountProfile,perkSubscription: freezed == perkSubscription ? _self.perkSubscription : perkSubscription // ignore: cast_nullable_to_non_nullable | ||||||
| as SnWalletSubscriptionRef?,badges: null == badges ? _self.badges : badges // ignore: cast_nullable_to_non_nullable | as SnWalletSubscriptionRef?,badges: null == badges ? _self.badges : badges // ignore: cast_nullable_to_non_nullable | ||||||
| as List<SnAccountBadge>,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable | as List<SnAccountBadge>,contacts: null == contacts ? _self.contacts : contacts // ignore: cast_nullable_to_non_nullable | ||||||
|  | as List<SnContactMethod>,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable | ||||||
| as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable | as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable | ||||||
| as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable | as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable | ||||||
| as DateTime?, | as DateTime?, | ||||||
| @@ -182,10 +184,10 @@ return $default(_that);case _: | |||||||
| /// } | /// } | ||||||
| /// ``` | /// ``` | ||||||
|  |  | ||||||
| @optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id,  String name,  String nick,  String language,  bool isSuperuser,  String? automatedId,  SnAccountProfile profile,  SnWalletSubscriptionRef? perkSubscription,  List<SnAccountBadge> badges,  DateTime createdAt,  DateTime updatedAt,  DateTime? deletedAt)?  $default,{required TResult orElse(),}) {final _that = this; | @optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id,  String name,  String nick,  String language,  String region,  bool isSuperuser,  String? automatedId,  SnAccountProfile profile,  SnWalletSubscriptionRef? perkSubscription,  List<SnAccountBadge> badges,  List<SnContactMethod> contacts,  DateTime createdAt,  DateTime updatedAt,  DateTime? deletedAt)?  $default,{required TResult orElse(),}) {final _that = this; | ||||||
| switch (_that) { | switch (_that) { | ||||||
| case _SnAccount() when $default != null: | case _SnAccount() when $default != null: | ||||||
| return $default(_that.id,_that.name,_that.nick,_that.language,_that.isSuperuser,_that.automatedId,_that.profile,_that.perkSubscription,_that.badges,_that.createdAt,_that.updatedAt,_that.deletedAt);case _: | return $default(_that.id,_that.name,_that.nick,_that.language,_that.region,_that.isSuperuser,_that.automatedId,_that.profile,_that.perkSubscription,_that.badges,_that.contacts,_that.createdAt,_that.updatedAt,_that.deletedAt);case _: | ||||||
|   return orElse(); |   return orElse(); | ||||||
|  |  | ||||||
| } | } | ||||||
| @@ -203,10 +205,10 @@ return $default(_that.id,_that.name,_that.nick,_that.language,_that.isSuperuser, | |||||||
| /// } | /// } | ||||||
| /// ``` | /// ``` | ||||||
|  |  | ||||||
| @optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id,  String name,  String nick,  String language,  bool isSuperuser,  String? automatedId,  SnAccountProfile profile,  SnWalletSubscriptionRef? perkSubscription,  List<SnAccountBadge> badges,  DateTime createdAt,  DateTime updatedAt,  DateTime? deletedAt)  $default,) {final _that = this; | @optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id,  String name,  String nick,  String language,  String region,  bool isSuperuser,  String? automatedId,  SnAccountProfile profile,  SnWalletSubscriptionRef? perkSubscription,  List<SnAccountBadge> badges,  List<SnContactMethod> contacts,  DateTime createdAt,  DateTime updatedAt,  DateTime? deletedAt)  $default,) {final _that = this; | ||||||
| switch (_that) { | switch (_that) { | ||||||
| case _SnAccount(): | case _SnAccount(): | ||||||
| return $default(_that.id,_that.name,_that.nick,_that.language,_that.isSuperuser,_that.automatedId,_that.profile,_that.perkSubscription,_that.badges,_that.createdAt,_that.updatedAt,_that.deletedAt);} | return $default(_that.id,_that.name,_that.nick,_that.language,_that.region,_that.isSuperuser,_that.automatedId,_that.profile,_that.perkSubscription,_that.badges,_that.contacts,_that.createdAt,_that.updatedAt,_that.deletedAt);} | ||||||
| } | } | ||||||
| /// A variant of `when` that fallback to returning `null` | /// A variant of `when` that fallback to returning `null` | ||||||
| /// | /// | ||||||
| @@ -220,10 +222,10 @@ return $default(_that.id,_that.name,_that.nick,_that.language,_that.isSuperuser, | |||||||
| /// } | /// } | ||||||
| /// ``` | /// ``` | ||||||
|  |  | ||||||
| @optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id,  String name,  String nick,  String language,  bool isSuperuser,  String? automatedId,  SnAccountProfile profile,  SnWalletSubscriptionRef? perkSubscription,  List<SnAccountBadge> badges,  DateTime createdAt,  DateTime updatedAt,  DateTime? deletedAt)?  $default,) {final _that = this; | @optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id,  String name,  String nick,  String language,  String region,  bool isSuperuser,  String? automatedId,  SnAccountProfile profile,  SnWalletSubscriptionRef? perkSubscription,  List<SnAccountBadge> badges,  List<SnContactMethod> contacts,  DateTime createdAt,  DateTime updatedAt,  DateTime? deletedAt)?  $default,) {final _that = this; | ||||||
| switch (_that) { | switch (_that) { | ||||||
| case _SnAccount() when $default != null: | case _SnAccount() when $default != null: | ||||||
| return $default(_that.id,_that.name,_that.nick,_that.language,_that.isSuperuser,_that.automatedId,_that.profile,_that.perkSubscription,_that.badges,_that.createdAt,_that.updatedAt,_that.deletedAt);case _: | return $default(_that.id,_that.name,_that.nick,_that.language,_that.region,_that.isSuperuser,_that.automatedId,_that.profile,_that.perkSubscription,_that.badges,_that.contacts,_that.createdAt,_that.updatedAt,_that.deletedAt);case _: | ||||||
|   return null; |   return null; | ||||||
|  |  | ||||||
| } | } | ||||||
| @@ -235,13 +237,14 @@ return $default(_that.id,_that.name,_that.nick,_that.language,_that.isSuperuser, | |||||||
| @JsonSerializable() | @JsonSerializable() | ||||||
|  |  | ||||||
| class _SnAccount implements SnAccount { | class _SnAccount implements SnAccount { | ||||||
|   const _SnAccount({required this.id, required this.name, required this.nick, required this.language, required this.isSuperuser, required this.automatedId, required this.profile, required this.perkSubscription, final  List<SnAccountBadge> badges = const [], required this.createdAt, required this.updatedAt, required this.deletedAt}): _badges = badges; |   const _SnAccount({required this.id, required this.name, required this.nick, required this.language, this.region = "", required this.isSuperuser, required this.automatedId, required this.profile, required this.perkSubscription, final  List<SnAccountBadge> badges = const [], final  List<SnContactMethod> contacts = const [], required this.createdAt, required this.updatedAt, required this.deletedAt}): _badges = badges,_contacts = contacts; | ||||||
|   factory _SnAccount.fromJson(Map<String, dynamic> json) => _$SnAccountFromJson(json); |   factory _SnAccount.fromJson(Map<String, dynamic> json) => _$SnAccountFromJson(json); | ||||||
|  |  | ||||||
| @override final  String id; | @override final  String id; | ||||||
| @override final  String name; | @override final  String name; | ||||||
| @override final  String nick; | @override final  String nick; | ||||||
| @override final  String language; | @override final  String language; | ||||||
|  | @override@JsonKey() final  String region; | ||||||
| @override final  bool isSuperuser; | @override final  bool isSuperuser; | ||||||
| @override final  String? automatedId; | @override final  String? automatedId; | ||||||
| @override final  SnAccountProfile profile; | @override final  SnAccountProfile profile; | ||||||
| @@ -253,6 +256,13 @@ class _SnAccount implements SnAccount { | |||||||
|   return EqualUnmodifiableListView(_badges); |   return EqualUnmodifiableListView(_badges); | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  final  List<SnContactMethod> _contacts; | ||||||
|  | @override@JsonKey() List<SnContactMethod> get contacts { | ||||||
|  |   if (_contacts is EqualUnmodifiableListView) return _contacts; | ||||||
|  |   // ignore: implicit_dynamic_type | ||||||
|  |   return EqualUnmodifiableListView(_contacts); | ||||||
|  | } | ||||||
|  |  | ||||||
| @override final  DateTime createdAt; | @override final  DateTime createdAt; | ||||||
| @override final  DateTime updatedAt; | @override final  DateTime updatedAt; | ||||||
| @override final  DateTime? deletedAt; | @override final  DateTime? deletedAt; | ||||||
| @@ -270,16 +280,16 @@ Map<String, dynamic> toJson() { | |||||||
|  |  | ||||||
| @override | @override | ||||||
| bool operator ==(Object other) { | bool operator ==(Object other) { | ||||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnAccount&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.nick, nick) || other.nick == nick)&&(identical(other.language, language) || other.language == language)&&(identical(other.isSuperuser, isSuperuser) || other.isSuperuser == isSuperuser)&&(identical(other.automatedId, automatedId) || other.automatedId == automatedId)&&(identical(other.profile, profile) || other.profile == profile)&&(identical(other.perkSubscription, perkSubscription) || other.perkSubscription == perkSubscription)&&const DeepCollectionEquality().equals(other._badges, _badges)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)); |   return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnAccount&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.nick, nick) || other.nick == nick)&&(identical(other.language, language) || other.language == language)&&(identical(other.region, region) || other.region == region)&&(identical(other.isSuperuser, isSuperuser) || other.isSuperuser == isSuperuser)&&(identical(other.automatedId, automatedId) || other.automatedId == automatedId)&&(identical(other.profile, profile) || other.profile == profile)&&(identical(other.perkSubscription, perkSubscription) || other.perkSubscription == perkSubscription)&&const DeepCollectionEquality().equals(other._badges, _badges)&&const DeepCollectionEquality().equals(other._contacts, _contacts)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)); | ||||||
| } | } | ||||||
|  |  | ||||||
| @JsonKey(includeFromJson: false, includeToJson: false) | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
| @override | @override | ||||||
| int get hashCode => Object.hash(runtimeType,id,name,nick,language,isSuperuser,automatedId,profile,perkSubscription,const DeepCollectionEquality().hash(_badges),createdAt,updatedAt,deletedAt); | int get hashCode => Object.hash(runtimeType,id,name,nick,language,region,isSuperuser,automatedId,profile,perkSubscription,const DeepCollectionEquality().hash(_badges),const DeepCollectionEquality().hash(_contacts),createdAt,updatedAt,deletedAt); | ||||||
|  |  | ||||||
| @override | @override | ||||||
| String toString() { | String toString() { | ||||||
|   return 'SnAccount(id: $id, name: $name, nick: $nick, language: $language, isSuperuser: $isSuperuser, automatedId: $automatedId, profile: $profile, perkSubscription: $perkSubscription, badges: $badges, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; |   return 'SnAccount(id: $id, name: $name, nick: $nick, language: $language, region: $region, isSuperuser: $isSuperuser, automatedId: $automatedId, profile: $profile, perkSubscription: $perkSubscription, badges: $badges, contacts: $contacts, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -290,7 +300,7 @@ abstract mixin class _$SnAccountCopyWith<$Res> implements $SnAccountCopyWith<$Re | |||||||
|   factory _$SnAccountCopyWith(_SnAccount value, $Res Function(_SnAccount) _then) = __$SnAccountCopyWithImpl; |   factory _$SnAccountCopyWith(_SnAccount value, $Res Function(_SnAccount) _then) = __$SnAccountCopyWithImpl; | ||||||
| @override @useResult | @override @useResult | ||||||
| $Res call({ | $Res call({ | ||||||
|  String id, String name, String nick, String language, bool isSuperuser, String? automatedId, SnAccountProfile profile, SnWalletSubscriptionRef? perkSubscription, List<SnAccountBadge> badges, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt |  String id, String name, String nick, String language, String region, bool isSuperuser, String? automatedId, SnAccountProfile profile, SnWalletSubscriptionRef? perkSubscription, List<SnAccountBadge> badges, List<SnContactMethod> contacts, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -307,18 +317,20 @@ class __$SnAccountCopyWithImpl<$Res> | |||||||
|  |  | ||||||
| /// Create a copy of SnAccount | /// Create a copy of SnAccount | ||||||
| /// with the given fields replaced by the non-null parameter values. | /// with the given fields replaced by the non-null parameter values. | ||||||
| @override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? name = null,Object? nick = null,Object? language = null,Object? isSuperuser = null,Object? automatedId = freezed,Object? profile = null,Object? perkSubscription = freezed,Object? badges = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) { | @override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? name = null,Object? nick = null,Object? language = null,Object? region = null,Object? isSuperuser = null,Object? automatedId = freezed,Object? profile = null,Object? perkSubscription = freezed,Object? badges = null,Object? contacts = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) { | ||||||
|   return _then(_SnAccount( |   return _then(_SnAccount( | ||||||
| id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable | id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable | ||||||
| as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable | as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable | ||||||
| as String,nick: null == nick ? _self.nick : nick // ignore: cast_nullable_to_non_nullable | as String,nick: null == nick ? _self.nick : nick // ignore: cast_nullable_to_non_nullable | ||||||
| as String,language: null == language ? _self.language : language // ignore: cast_nullable_to_non_nullable | as String,language: null == language ? _self.language : language // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,region: null == region ? _self.region : region // ignore: cast_nullable_to_non_nullable | ||||||
| as String,isSuperuser: null == isSuperuser ? _self.isSuperuser : isSuperuser // ignore: cast_nullable_to_non_nullable | as String,isSuperuser: null == isSuperuser ? _self.isSuperuser : isSuperuser // ignore: cast_nullable_to_non_nullable | ||||||
| as bool,automatedId: freezed == automatedId ? _self.automatedId : automatedId // ignore: cast_nullable_to_non_nullable | as bool,automatedId: freezed == automatedId ? _self.automatedId : automatedId // ignore: cast_nullable_to_non_nullable | ||||||
| as String?,profile: null == profile ? _self.profile : profile // ignore: cast_nullable_to_non_nullable | as String?,profile: null == profile ? _self.profile : profile // ignore: cast_nullable_to_non_nullable | ||||||
| as SnAccountProfile,perkSubscription: freezed == perkSubscription ? _self.perkSubscription : perkSubscription // ignore: cast_nullable_to_non_nullable | as SnAccountProfile,perkSubscription: freezed == perkSubscription ? _self.perkSubscription : perkSubscription // ignore: cast_nullable_to_non_nullable | ||||||
| as SnWalletSubscriptionRef?,badges: null == badges ? _self._badges : badges // ignore: cast_nullable_to_non_nullable | as SnWalletSubscriptionRef?,badges: null == badges ? _self._badges : badges // ignore: cast_nullable_to_non_nullable | ||||||
| as List<SnAccountBadge>,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable | as List<SnAccountBadge>,contacts: null == contacts ? _self._contacts : contacts // ignore: cast_nullable_to_non_nullable | ||||||
|  | as List<SnContactMethod>,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable | ||||||
| as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable | as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable | ||||||
| as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable | as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable | ||||||
| as DateTime?, | as DateTime?, | ||||||
| @@ -1624,7 +1636,7 @@ as DateTime?, | |||||||
| /// @nodoc | /// @nodoc | ||||||
| mixin _$SnContactMethod { | mixin _$SnContactMethod { | ||||||
|  |  | ||||||
|  String get id; int get type; DateTime? get verifiedAt; bool get isPrimary; String get content; String get accountId; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; |  String get id; int get type; DateTime? get verifiedAt; bool get isPrimary; bool get isPublic; String get content; String get accountId; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; | ||||||
| /// Create a copy of SnContactMethod | /// Create a copy of SnContactMethod | ||||||
| /// with the given fields replaced by the non-null parameter values. | /// with the given fields replaced by the non-null parameter values. | ||||||
| @JsonKey(includeFromJson: false, includeToJson: false) | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
| @@ -1637,16 +1649,16 @@ $SnContactMethodCopyWith<SnContactMethod> get copyWith => _$SnContactMethodCopyW | |||||||
|  |  | ||||||
| @override | @override | ||||||
| bool operator ==(Object other) { | bool operator ==(Object other) { | ||||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is SnContactMethod&&(identical(other.id, id) || other.id == id)&&(identical(other.type, type) || other.type == type)&&(identical(other.verifiedAt, verifiedAt) || other.verifiedAt == verifiedAt)&&(identical(other.isPrimary, isPrimary) || other.isPrimary == isPrimary)&&(identical(other.content, content) || other.content == content)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)); |   return identical(this, other) || (other.runtimeType == runtimeType&&other is SnContactMethod&&(identical(other.id, id) || other.id == id)&&(identical(other.type, type) || other.type == type)&&(identical(other.verifiedAt, verifiedAt) || other.verifiedAt == verifiedAt)&&(identical(other.isPrimary, isPrimary) || other.isPrimary == isPrimary)&&(identical(other.isPublic, isPublic) || other.isPublic == isPublic)&&(identical(other.content, content) || other.content == content)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)); | ||||||
| } | } | ||||||
|  |  | ||||||
| @JsonKey(includeFromJson: false, includeToJson: false) | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
| @override | @override | ||||||
| int get hashCode => Object.hash(runtimeType,id,type,verifiedAt,isPrimary,content,accountId,createdAt,updatedAt,deletedAt); | int get hashCode => Object.hash(runtimeType,id,type,verifiedAt,isPrimary,isPublic,content,accountId,createdAt,updatedAt,deletedAt); | ||||||
|  |  | ||||||
| @override | @override | ||||||
| String toString() { | String toString() { | ||||||
|   return 'SnContactMethod(id: $id, type: $type, verifiedAt: $verifiedAt, isPrimary: $isPrimary, content: $content, accountId: $accountId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; |   return 'SnContactMethod(id: $id, type: $type, verifiedAt: $verifiedAt, isPrimary: $isPrimary, isPublic: $isPublic, content: $content, accountId: $accountId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -1657,7 +1669,7 @@ abstract mixin class $SnContactMethodCopyWith<$Res>  { | |||||||
|   factory $SnContactMethodCopyWith(SnContactMethod value, $Res Function(SnContactMethod) _then) = _$SnContactMethodCopyWithImpl; |   factory $SnContactMethodCopyWith(SnContactMethod value, $Res Function(SnContactMethod) _then) = _$SnContactMethodCopyWithImpl; | ||||||
| @useResult | @useResult | ||||||
| $Res call({ | $Res call({ | ||||||
|  String id, int type, DateTime? verifiedAt, bool isPrimary, String content, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt |  String id, int type, DateTime? verifiedAt, bool isPrimary, bool isPublic, String content, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -1674,12 +1686,13 @@ class _$SnContactMethodCopyWithImpl<$Res> | |||||||
|  |  | ||||||
| /// Create a copy of SnContactMethod | /// Create a copy of SnContactMethod | ||||||
| /// with the given fields replaced by the non-null parameter values. | /// with the given fields replaced by the non-null parameter values. | ||||||
| @pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? type = null,Object? verifiedAt = freezed,Object? isPrimary = null,Object? content = null,Object? accountId = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) { | @pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? type = null,Object? verifiedAt = freezed,Object? isPrimary = null,Object? isPublic = null,Object? content = null,Object? accountId = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) { | ||||||
|   return _then(_self.copyWith( |   return _then(_self.copyWith( | ||||||
| id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable | id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable | ||||||
| as String,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable | as String,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable | ||||||
| as int,verifiedAt: freezed == verifiedAt ? _self.verifiedAt : verifiedAt // ignore: cast_nullable_to_non_nullable | as int,verifiedAt: freezed == verifiedAt ? _self.verifiedAt : verifiedAt // ignore: cast_nullable_to_non_nullable | ||||||
| as DateTime?,isPrimary: null == isPrimary ? _self.isPrimary : isPrimary // ignore: cast_nullable_to_non_nullable | as DateTime?,isPrimary: null == isPrimary ? _self.isPrimary : isPrimary // ignore: cast_nullable_to_non_nullable | ||||||
|  | as bool,isPublic: null == isPublic ? _self.isPublic : isPublic // ignore: cast_nullable_to_non_nullable | ||||||
| as bool,content: null == content ? _self.content : content // ignore: cast_nullable_to_non_nullable | as bool,content: null == content ? _self.content : content // ignore: cast_nullable_to_non_nullable | ||||||
| as String,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable | as String,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable | ||||||
| as String,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable | as String,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable | ||||||
| @@ -1767,10 +1780,10 @@ return $default(_that);case _: | |||||||
| /// } | /// } | ||||||
| /// ``` | /// ``` | ||||||
|  |  | ||||||
| @optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id,  int type,  DateTime? verifiedAt,  bool isPrimary,  String content,  String accountId,  DateTime createdAt,  DateTime updatedAt,  DateTime? deletedAt)?  $default,{required TResult orElse(),}) {final _that = this; | @optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id,  int type,  DateTime? verifiedAt,  bool isPrimary,  bool isPublic,  String content,  String accountId,  DateTime createdAt,  DateTime updatedAt,  DateTime? deletedAt)?  $default,{required TResult orElse(),}) {final _that = this; | ||||||
| switch (_that) { | switch (_that) { | ||||||
| case _SnContactMethod() when $default != null: | case _SnContactMethod() when $default != null: | ||||||
| return $default(_that.id,_that.type,_that.verifiedAt,_that.isPrimary,_that.content,_that.accountId,_that.createdAt,_that.updatedAt,_that.deletedAt);case _: | return $default(_that.id,_that.type,_that.verifiedAt,_that.isPrimary,_that.isPublic,_that.content,_that.accountId,_that.createdAt,_that.updatedAt,_that.deletedAt);case _: | ||||||
|   return orElse(); |   return orElse(); | ||||||
|  |  | ||||||
| } | } | ||||||
| @@ -1788,10 +1801,10 @@ return $default(_that.id,_that.type,_that.verifiedAt,_that.isPrimary,_that.conte | |||||||
| /// } | /// } | ||||||
| /// ``` | /// ``` | ||||||
|  |  | ||||||
| @optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id,  int type,  DateTime? verifiedAt,  bool isPrimary,  String content,  String accountId,  DateTime createdAt,  DateTime updatedAt,  DateTime? deletedAt)  $default,) {final _that = this; | @optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id,  int type,  DateTime? verifiedAt,  bool isPrimary,  bool isPublic,  String content,  String accountId,  DateTime createdAt,  DateTime updatedAt,  DateTime? deletedAt)  $default,) {final _that = this; | ||||||
| switch (_that) { | switch (_that) { | ||||||
| case _SnContactMethod(): | case _SnContactMethod(): | ||||||
| return $default(_that.id,_that.type,_that.verifiedAt,_that.isPrimary,_that.content,_that.accountId,_that.createdAt,_that.updatedAt,_that.deletedAt);} | return $default(_that.id,_that.type,_that.verifiedAt,_that.isPrimary,_that.isPublic,_that.content,_that.accountId,_that.createdAt,_that.updatedAt,_that.deletedAt);} | ||||||
| } | } | ||||||
| /// A variant of `when` that fallback to returning `null` | /// A variant of `when` that fallback to returning `null` | ||||||
| /// | /// | ||||||
| @@ -1805,10 +1818,10 @@ return $default(_that.id,_that.type,_that.verifiedAt,_that.isPrimary,_that.conte | |||||||
| /// } | /// } | ||||||
| /// ``` | /// ``` | ||||||
|  |  | ||||||
| @optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id,  int type,  DateTime? verifiedAt,  bool isPrimary,  String content,  String accountId,  DateTime createdAt,  DateTime updatedAt,  DateTime? deletedAt)?  $default,) {final _that = this; | @optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id,  int type,  DateTime? verifiedAt,  bool isPrimary,  bool isPublic,  String content,  String accountId,  DateTime createdAt,  DateTime updatedAt,  DateTime? deletedAt)?  $default,) {final _that = this; | ||||||
| switch (_that) { | switch (_that) { | ||||||
| case _SnContactMethod() when $default != null: | case _SnContactMethod() when $default != null: | ||||||
| return $default(_that.id,_that.type,_that.verifiedAt,_that.isPrimary,_that.content,_that.accountId,_that.createdAt,_that.updatedAt,_that.deletedAt);case _: | return $default(_that.id,_that.type,_that.verifiedAt,_that.isPrimary,_that.isPublic,_that.content,_that.accountId,_that.createdAt,_that.updatedAt,_that.deletedAt);case _: | ||||||
|   return null; |   return null; | ||||||
|  |  | ||||||
| } | } | ||||||
| @@ -1820,13 +1833,14 @@ return $default(_that.id,_that.type,_that.verifiedAt,_that.isPrimary,_that.conte | |||||||
| @JsonSerializable() | @JsonSerializable() | ||||||
|  |  | ||||||
| class _SnContactMethod implements SnContactMethod { | class _SnContactMethod implements SnContactMethod { | ||||||
|   const _SnContactMethod({required this.id, required this.type, required this.verifiedAt, required this.isPrimary, required this.content, required this.accountId, required this.createdAt, required this.updatedAt, required this.deletedAt}); |   const _SnContactMethod({required this.id, required this.type, required this.verifiedAt, required this.isPrimary, required this.isPublic, required this.content, required this.accountId, required this.createdAt, required this.updatedAt, required this.deletedAt}); | ||||||
|   factory _SnContactMethod.fromJson(Map<String, dynamic> json) => _$SnContactMethodFromJson(json); |   factory _SnContactMethod.fromJson(Map<String, dynamic> json) => _$SnContactMethodFromJson(json); | ||||||
|  |  | ||||||
| @override final  String id; | @override final  String id; | ||||||
| @override final  int type; | @override final  int type; | ||||||
| @override final  DateTime? verifiedAt; | @override final  DateTime? verifiedAt; | ||||||
| @override final  bool isPrimary; | @override final  bool isPrimary; | ||||||
|  | @override final  bool isPublic; | ||||||
| @override final  String content; | @override final  String content; | ||||||
| @override final  String accountId; | @override final  String accountId; | ||||||
| @override final  DateTime createdAt; | @override final  DateTime createdAt; | ||||||
| @@ -1846,16 +1860,16 @@ Map<String, dynamic> toJson() { | |||||||
|  |  | ||||||
| @override | @override | ||||||
| bool operator ==(Object other) { | bool operator ==(Object other) { | ||||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnContactMethod&&(identical(other.id, id) || other.id == id)&&(identical(other.type, type) || other.type == type)&&(identical(other.verifiedAt, verifiedAt) || other.verifiedAt == verifiedAt)&&(identical(other.isPrimary, isPrimary) || other.isPrimary == isPrimary)&&(identical(other.content, content) || other.content == content)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)); |   return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnContactMethod&&(identical(other.id, id) || other.id == id)&&(identical(other.type, type) || other.type == type)&&(identical(other.verifiedAt, verifiedAt) || other.verifiedAt == verifiedAt)&&(identical(other.isPrimary, isPrimary) || other.isPrimary == isPrimary)&&(identical(other.isPublic, isPublic) || other.isPublic == isPublic)&&(identical(other.content, content) || other.content == content)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)); | ||||||
| } | } | ||||||
|  |  | ||||||
| @JsonKey(includeFromJson: false, includeToJson: false) | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
| @override | @override | ||||||
| int get hashCode => Object.hash(runtimeType,id,type,verifiedAt,isPrimary,content,accountId,createdAt,updatedAt,deletedAt); | int get hashCode => Object.hash(runtimeType,id,type,verifiedAt,isPrimary,isPublic,content,accountId,createdAt,updatedAt,deletedAt); | ||||||
|  |  | ||||||
| @override | @override | ||||||
| String toString() { | String toString() { | ||||||
|   return 'SnContactMethod(id: $id, type: $type, verifiedAt: $verifiedAt, isPrimary: $isPrimary, content: $content, accountId: $accountId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; |   return 'SnContactMethod(id: $id, type: $type, verifiedAt: $verifiedAt, isPrimary: $isPrimary, isPublic: $isPublic, content: $content, accountId: $accountId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -1866,7 +1880,7 @@ abstract mixin class _$SnContactMethodCopyWith<$Res> implements $SnContactMethod | |||||||
|   factory _$SnContactMethodCopyWith(_SnContactMethod value, $Res Function(_SnContactMethod) _then) = __$SnContactMethodCopyWithImpl; |   factory _$SnContactMethodCopyWith(_SnContactMethod value, $Res Function(_SnContactMethod) _then) = __$SnContactMethodCopyWithImpl; | ||||||
| @override @useResult | @override @useResult | ||||||
| $Res call({ | $Res call({ | ||||||
|  String id, int type, DateTime? verifiedAt, bool isPrimary, String content, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt |  String id, int type, DateTime? verifiedAt, bool isPrimary, bool isPublic, String content, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -1883,12 +1897,13 @@ class __$SnContactMethodCopyWithImpl<$Res> | |||||||
|  |  | ||||||
| /// Create a copy of SnContactMethod | /// Create a copy of SnContactMethod | ||||||
| /// with the given fields replaced by the non-null parameter values. | /// with the given fields replaced by the non-null parameter values. | ||||||
| @override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? type = null,Object? verifiedAt = freezed,Object? isPrimary = null,Object? content = null,Object? accountId = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) { | @override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? type = null,Object? verifiedAt = freezed,Object? isPrimary = null,Object? isPublic = null,Object? content = null,Object? accountId = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) { | ||||||
|   return _then(_SnContactMethod( |   return _then(_SnContactMethod( | ||||||
| id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable | id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable | ||||||
| as String,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable | as String,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable | ||||||
| as int,verifiedAt: freezed == verifiedAt ? _self.verifiedAt : verifiedAt // ignore: cast_nullable_to_non_nullable | as int,verifiedAt: freezed == verifiedAt ? _self.verifiedAt : verifiedAt // ignore: cast_nullable_to_non_nullable | ||||||
| as DateTime?,isPrimary: null == isPrimary ? _self.isPrimary : isPrimary // ignore: cast_nullable_to_non_nullable | as DateTime?,isPrimary: null == isPrimary ? _self.isPrimary : isPrimary // ignore: cast_nullable_to_non_nullable | ||||||
|  | as bool,isPublic: null == isPublic ? _self.isPublic : isPublic // ignore: cast_nullable_to_non_nullable | ||||||
| as bool,content: null == content ? _self.content : content // ignore: cast_nullable_to_non_nullable | as bool,content: null == content ? _self.content : content // ignore: cast_nullable_to_non_nullable | ||||||
| as String,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable | as String,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable | ||||||
| as String,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable | as String,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable | ||||||
|   | |||||||
| @@ -11,6 +11,7 @@ _SnAccount _$SnAccountFromJson(Map<String, dynamic> json) => _SnAccount( | |||||||
|   name: json['name'] as String, |   name: json['name'] as String, | ||||||
|   nick: json['nick'] as String, |   nick: json['nick'] as String, | ||||||
|   language: json['language'] as String, |   language: json['language'] as String, | ||||||
|  |   region: json['region'] as String? ?? "", | ||||||
|   isSuperuser: json['is_superuser'] as bool, |   isSuperuser: json['is_superuser'] as bool, | ||||||
|   automatedId: json['automated_id'] as String?, |   automatedId: json['automated_id'] as String?, | ||||||
|   profile: SnAccountProfile.fromJson(json['profile'] as Map<String, dynamic>), |   profile: SnAccountProfile.fromJson(json['profile'] as Map<String, dynamic>), | ||||||
| @@ -25,6 +26,11 @@ _SnAccount _$SnAccountFromJson(Map<String, dynamic> json) => _SnAccount( | |||||||
|           ?.map((e) => SnAccountBadge.fromJson(e as Map<String, dynamic>)) |           ?.map((e) => SnAccountBadge.fromJson(e as Map<String, dynamic>)) | ||||||
|           .toList() ?? |           .toList() ?? | ||||||
|       const [], |       const [], | ||||||
|  |   contacts: | ||||||
|  |       (json['contacts'] as List<dynamic>?) | ||||||
|  |           ?.map((e) => SnContactMethod.fromJson(e as Map<String, dynamic>)) | ||||||
|  |           .toList() ?? | ||||||
|  |       const [], | ||||||
|   createdAt: DateTime.parse(json['created_at'] as String), |   createdAt: DateTime.parse(json['created_at'] as String), | ||||||
|   updatedAt: DateTime.parse(json['updated_at'] as String), |   updatedAt: DateTime.parse(json['updated_at'] as String), | ||||||
|   deletedAt: |   deletedAt: | ||||||
| @@ -39,11 +45,13 @@ Map<String, dynamic> _$SnAccountToJson(_SnAccount instance) => | |||||||
|       'name': instance.name, |       'name': instance.name, | ||||||
|       'nick': instance.nick, |       'nick': instance.nick, | ||||||
|       'language': instance.language, |       'language': instance.language, | ||||||
|  |       'region': instance.region, | ||||||
|       'is_superuser': instance.isSuperuser, |       'is_superuser': instance.isSuperuser, | ||||||
|       'automated_id': instance.automatedId, |       'automated_id': instance.automatedId, | ||||||
|       'profile': instance.profile.toJson(), |       'profile': instance.profile.toJson(), | ||||||
|       'perk_subscription': instance.perkSubscription?.toJson(), |       'perk_subscription': instance.perkSubscription?.toJson(), | ||||||
|       'badges': instance.badges.map((e) => e.toJson()).toList(), |       'badges': instance.badges.map((e) => e.toJson()).toList(), | ||||||
|  |       'contacts': instance.contacts.map((e) => e.toJson()).toList(), | ||||||
|       'created_at': instance.createdAt.toIso8601String(), |       'created_at': instance.createdAt.toIso8601String(), | ||||||
|       'updated_at': instance.updatedAt.toIso8601String(), |       'updated_at': instance.updatedAt.toIso8601String(), | ||||||
|       'deleted_at': instance.deletedAt?.toIso8601String(), |       'deleted_at': instance.deletedAt?.toIso8601String(), | ||||||
| @@ -227,6 +235,7 @@ _SnContactMethod _$SnContactMethodFromJson(Map<String, dynamic> json) => | |||||||
|               ? null |               ? null | ||||||
|               : DateTime.parse(json['verified_at'] as String), |               : DateTime.parse(json['verified_at'] as String), | ||||||
|       isPrimary: json['is_primary'] as bool, |       isPrimary: json['is_primary'] as bool, | ||||||
|  |       isPublic: json['is_public'] as bool, | ||||||
|       content: json['content'] as String, |       content: json['content'] as String, | ||||||
|       accountId: json['account_id'] as String, |       accountId: json['account_id'] as String, | ||||||
|       createdAt: DateTime.parse(json['created_at'] as String), |       createdAt: DateTime.parse(json['created_at'] as String), | ||||||
| @@ -243,6 +252,7 @@ Map<String, dynamic> _$SnContactMethodToJson(_SnContactMethod instance) => | |||||||
|       'type': instance.type, |       'type': instance.type, | ||||||
|       'verified_at': instance.verifiedAt?.toIso8601String(), |       'verified_at': instance.verifiedAt?.toIso8601String(), | ||||||
|       'is_primary': instance.isPrimary, |       'is_primary': instance.isPrimary, | ||||||
|  |       'is_public': instance.isPublic, | ||||||
|       'content': instance.content, |       'content': instance.content, | ||||||
|       'account_id': instance.accountId, |       'account_id': instance.accountId, | ||||||
|       'created_at': instance.createdAt.toIso8601String(), |       'created_at': instance.createdAt.toIso8601String(), | ||||||
|   | |||||||
| @@ -4,6 +4,20 @@ import 'package:island/models/account.dart'; | |||||||
| part 'activity.freezed.dart'; | part 'activity.freezed.dart'; | ||||||
| part 'activity.g.dart'; | part 'activity.g.dart'; | ||||||
|  |  | ||||||
|  | @freezed | ||||||
|  | sealed class SnNotableDay with _$SnNotableDay { | ||||||
|  |   const factory SnNotableDay({ | ||||||
|  |     required DateTime date, | ||||||
|  |     required String localName, | ||||||
|  |     required String globalName, | ||||||
|  |     required String countryCode, | ||||||
|  |     required List<int> holidays, | ||||||
|  |   }) = _SnNotableDay; | ||||||
|  |  | ||||||
|  |   factory SnNotableDay.fromJson(Map<String, dynamic> json) => | ||||||
|  |       _$SnNotableDayFromJson(json); | ||||||
|  | } | ||||||
|  |  | ||||||
| @freezed | @freezed | ||||||
| sealed class SnActivity with _$SnActivity { | sealed class SnActivity with _$SnActivity { | ||||||
|   const factory SnActivity({ |   const factory SnActivity({ | ||||||
|   | |||||||
| @@ -12,6 +12,281 @@ part of 'activity.dart'; | |||||||
| // dart format off | // dart format off | ||||||
| T _$identity<T>(T value) => value; | T _$identity<T>(T value) => value; | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | mixin _$SnNotableDay { | ||||||
|  |  | ||||||
|  |  DateTime get date; String get localName; String get globalName; String get countryCode; List<int> get holidays; | ||||||
|  | /// Create a copy of SnNotableDay | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
|  | $SnNotableDayCopyWith<SnNotableDay> get copyWith => _$SnNotableDayCopyWithImpl<SnNotableDay>(this as SnNotableDay, _$identity); | ||||||
|  |  | ||||||
|  |   /// Serializes this SnNotableDay to a JSON map. | ||||||
|  |   Map<String, dynamic> toJson(); | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | bool operator ==(Object other) { | ||||||
|  |   return identical(this, other) || (other.runtimeType == runtimeType&&other is SnNotableDay&&(identical(other.date, date) || other.date == date)&&(identical(other.localName, localName) || other.localName == localName)&&(identical(other.globalName, globalName) || other.globalName == globalName)&&(identical(other.countryCode, countryCode) || other.countryCode == countryCode)&&const DeepCollectionEquality().equals(other.holidays, holidays)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  | @override | ||||||
|  | int get hashCode => Object.hash(runtimeType,date,localName,globalName,countryCode,const DeepCollectionEquality().hash(holidays)); | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | String toString() { | ||||||
|  |   return 'SnNotableDay(date: $date, localName: $localName, globalName: $globalName, countryCode: $countryCode, holidays: $holidays)'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | abstract mixin class $SnNotableDayCopyWith<$Res>  { | ||||||
|  |   factory $SnNotableDayCopyWith(SnNotableDay value, $Res Function(SnNotableDay) _then) = _$SnNotableDayCopyWithImpl; | ||||||
|  | @useResult | ||||||
|  | $Res call({ | ||||||
|  |  DateTime date, String localName, String globalName, String countryCode, List<int> holidays | ||||||
|  | }); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  | /// @nodoc | ||||||
|  | class _$SnNotableDayCopyWithImpl<$Res> | ||||||
|  |     implements $SnNotableDayCopyWith<$Res> { | ||||||
|  |   _$SnNotableDayCopyWithImpl(this._self, this._then); | ||||||
|  |  | ||||||
|  |   final SnNotableDay _self; | ||||||
|  |   final $Res Function(SnNotableDay) _then; | ||||||
|  |  | ||||||
|  | /// Create a copy of SnNotableDay | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @pragma('vm:prefer-inline') @override $Res call({Object? date = null,Object? localName = null,Object? globalName = null,Object? countryCode = null,Object? holidays = null,}) { | ||||||
|  |   return _then(_self.copyWith( | ||||||
|  | date: null == date ? _self.date : date // ignore: cast_nullable_to_non_nullable | ||||||
|  | as DateTime,localName: null == localName ? _self.localName : localName // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,globalName: null == globalName ? _self.globalName : globalName // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,countryCode: null == countryCode ? _self.countryCode : countryCode // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,holidays: null == holidays ? _self.holidays : holidays // ignore: cast_nullable_to_non_nullable | ||||||
|  | as List<int>, | ||||||
|  |   )); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | /// Adds pattern-matching-related methods to [SnNotableDay]. | ||||||
|  | extension SnNotableDayPatterns on SnNotableDay { | ||||||
|  | /// A variant of `map` that fallback to returning `orElse`. | ||||||
|  | /// | ||||||
|  | /// It is equivalent to doing: | ||||||
|  | /// ```dart | ||||||
|  | /// switch (sealedClass) { | ||||||
|  | ///   case final Subclass value: | ||||||
|  | ///     return ...; | ||||||
|  | ///   case _: | ||||||
|  | ///     return orElse(); | ||||||
|  | /// } | ||||||
|  | /// ``` | ||||||
|  |  | ||||||
|  | @optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _SnNotableDay value)?  $default,{required TResult orElse(),}){ | ||||||
|  | final _that = this; | ||||||
|  | switch (_that) { | ||||||
|  | case _SnNotableDay() when $default != null: | ||||||
|  | return $default(_that);case _: | ||||||
|  |   return orElse(); | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  | /// A `switch`-like method, using callbacks. | ||||||
|  | /// | ||||||
|  | /// Callbacks receives the raw object, upcasted. | ||||||
|  | /// It is equivalent to doing: | ||||||
|  | /// ```dart | ||||||
|  | /// switch (sealedClass) { | ||||||
|  | ///   case final Subclass value: | ||||||
|  | ///     return ...; | ||||||
|  | ///   case final Subclass2 value: | ||||||
|  | ///     return ...; | ||||||
|  | /// } | ||||||
|  | /// ``` | ||||||
|  |  | ||||||
|  | @optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _SnNotableDay value)  $default,){ | ||||||
|  | final _that = this; | ||||||
|  | switch (_that) { | ||||||
|  | case _SnNotableDay(): | ||||||
|  | return $default(_that);} | ||||||
|  | } | ||||||
|  | /// A variant of `map` that fallback to returning `null`. | ||||||
|  | /// | ||||||
|  | /// It is equivalent to doing: | ||||||
|  | /// ```dart | ||||||
|  | /// switch (sealedClass) { | ||||||
|  | ///   case final Subclass value: | ||||||
|  | ///     return ...; | ||||||
|  | ///   case _: | ||||||
|  | ///     return null; | ||||||
|  | /// } | ||||||
|  | /// ``` | ||||||
|  |  | ||||||
|  | @optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _SnNotableDay value)?  $default,){ | ||||||
|  | final _that = this; | ||||||
|  | switch (_that) { | ||||||
|  | case _SnNotableDay() when $default != null: | ||||||
|  | return $default(_that);case _: | ||||||
|  |   return null; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  | /// A variant of `when` that fallback to an `orElse` callback. | ||||||
|  | /// | ||||||
|  | /// It is equivalent to doing: | ||||||
|  | /// ```dart | ||||||
|  | /// switch (sealedClass) { | ||||||
|  | ///   case Subclass(:final field): | ||||||
|  | ///     return ...; | ||||||
|  | ///   case _: | ||||||
|  | ///     return orElse(); | ||||||
|  | /// } | ||||||
|  | /// ``` | ||||||
|  |  | ||||||
|  | @optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( DateTime date,  String localName,  String globalName,  String countryCode,  List<int> holidays)?  $default,{required TResult orElse(),}) {final _that = this; | ||||||
|  | switch (_that) { | ||||||
|  | case _SnNotableDay() when $default != null: | ||||||
|  | return $default(_that.date,_that.localName,_that.globalName,_that.countryCode,_that.holidays);case _: | ||||||
|  |   return orElse(); | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  | /// A `switch`-like method, using callbacks. | ||||||
|  | /// | ||||||
|  | /// As opposed to `map`, this offers destructuring. | ||||||
|  | /// It is equivalent to doing: | ||||||
|  | /// ```dart | ||||||
|  | /// switch (sealedClass) { | ||||||
|  | ///   case Subclass(:final field): | ||||||
|  | ///     return ...; | ||||||
|  | ///   case Subclass2(:final field2): | ||||||
|  | ///     return ...; | ||||||
|  | /// } | ||||||
|  | /// ``` | ||||||
|  |  | ||||||
|  | @optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( DateTime date,  String localName,  String globalName,  String countryCode,  List<int> holidays)  $default,) {final _that = this; | ||||||
|  | switch (_that) { | ||||||
|  | case _SnNotableDay(): | ||||||
|  | return $default(_that.date,_that.localName,_that.globalName,_that.countryCode,_that.holidays);} | ||||||
|  | } | ||||||
|  | /// A variant of `when` that fallback to returning `null` | ||||||
|  | /// | ||||||
|  | /// It is equivalent to doing: | ||||||
|  | /// ```dart | ||||||
|  | /// switch (sealedClass) { | ||||||
|  | ///   case Subclass(:final field): | ||||||
|  | ///     return ...; | ||||||
|  | ///   case _: | ||||||
|  | ///     return null; | ||||||
|  | /// } | ||||||
|  | /// ``` | ||||||
|  |  | ||||||
|  | @optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( DateTime date,  String localName,  String globalName,  String countryCode,  List<int> holidays)?  $default,) {final _that = this; | ||||||
|  | switch (_that) { | ||||||
|  | case _SnNotableDay() when $default != null: | ||||||
|  | return $default(_that.date,_that.localName,_that.globalName,_that.countryCode,_that.holidays);case _: | ||||||
|  |   return null; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | @JsonSerializable() | ||||||
|  |  | ||||||
|  | class _SnNotableDay implements SnNotableDay { | ||||||
|  |   const _SnNotableDay({required this.date, required this.localName, required this.globalName, required this.countryCode, required final  List<int> holidays}): _holidays = holidays; | ||||||
|  |   factory _SnNotableDay.fromJson(Map<String, dynamic> json) => _$SnNotableDayFromJson(json); | ||||||
|  |  | ||||||
|  | @override final  DateTime date; | ||||||
|  | @override final  String localName; | ||||||
|  | @override final  String globalName; | ||||||
|  | @override final  String countryCode; | ||||||
|  |  final  List<int> _holidays; | ||||||
|  | @override List<int> get holidays { | ||||||
|  |   if (_holidays is EqualUnmodifiableListView) return _holidays; | ||||||
|  |   // ignore: implicit_dynamic_type | ||||||
|  |   return EqualUnmodifiableListView(_holidays); | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | /// Create a copy of SnNotableDay | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
|  | _$SnNotableDayCopyWith<_SnNotableDay> get copyWith => __$SnNotableDayCopyWithImpl<_SnNotableDay>(this, _$identity); | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | Map<String, dynamic> toJson() { | ||||||
|  |   return _$SnNotableDayToJson(this, ); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | bool operator ==(Object other) { | ||||||
|  |   return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnNotableDay&&(identical(other.date, date) || other.date == date)&&(identical(other.localName, localName) || other.localName == localName)&&(identical(other.globalName, globalName) || other.globalName == globalName)&&(identical(other.countryCode, countryCode) || other.countryCode == countryCode)&&const DeepCollectionEquality().equals(other._holidays, _holidays)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  | @override | ||||||
|  | int get hashCode => Object.hash(runtimeType,date,localName,globalName,countryCode,const DeepCollectionEquality().hash(_holidays)); | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | String toString() { | ||||||
|  |   return 'SnNotableDay(date: $date, localName: $localName, globalName: $globalName, countryCode: $countryCode, holidays: $holidays)'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | abstract mixin class _$SnNotableDayCopyWith<$Res> implements $SnNotableDayCopyWith<$Res> { | ||||||
|  |   factory _$SnNotableDayCopyWith(_SnNotableDay value, $Res Function(_SnNotableDay) _then) = __$SnNotableDayCopyWithImpl; | ||||||
|  | @override @useResult | ||||||
|  | $Res call({ | ||||||
|  |  DateTime date, String localName, String globalName, String countryCode, List<int> holidays | ||||||
|  | }); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  | /// @nodoc | ||||||
|  | class __$SnNotableDayCopyWithImpl<$Res> | ||||||
|  |     implements _$SnNotableDayCopyWith<$Res> { | ||||||
|  |   __$SnNotableDayCopyWithImpl(this._self, this._then); | ||||||
|  |  | ||||||
|  |   final _SnNotableDay _self; | ||||||
|  |   final $Res Function(_SnNotableDay) _then; | ||||||
|  |  | ||||||
|  | /// Create a copy of SnNotableDay | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override @pragma('vm:prefer-inline') $Res call({Object? date = null,Object? localName = null,Object? globalName = null,Object? countryCode = null,Object? holidays = null,}) { | ||||||
|  |   return _then(_SnNotableDay( | ||||||
|  | date: null == date ? _self.date : date // ignore: cast_nullable_to_non_nullable | ||||||
|  | as DateTime,localName: null == localName ? _self.localName : localName // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,globalName: null == globalName ? _self.globalName : globalName // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,countryCode: null == countryCode ? _self.countryCode : countryCode // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,holidays: null == holidays ? _self._holidays : holidays // ignore: cast_nullable_to_non_nullable | ||||||
|  | as List<int>, | ||||||
|  |   )); | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
| /// @nodoc | /// @nodoc | ||||||
| mixin _$SnActivity { | mixin _$SnActivity { | ||||||
|  |  | ||||||
|   | |||||||
| @@ -6,6 +6,27 @@ part of 'activity.dart'; | |||||||
| // JsonSerializableGenerator | // JsonSerializableGenerator | ||||||
| // ************************************************************************** | // ************************************************************************** | ||||||
|  |  | ||||||
|  | _SnNotableDay _$SnNotableDayFromJson(Map<String, dynamic> json) => | ||||||
|  |     _SnNotableDay( | ||||||
|  |       date: DateTime.parse(json['date'] as String), | ||||||
|  |       localName: json['local_name'] as String, | ||||||
|  |       globalName: json['global_name'] as String, | ||||||
|  |       countryCode: json['country_code'] as String, | ||||||
|  |       holidays: | ||||||
|  |           (json['holidays'] as List<dynamic>) | ||||||
|  |               .map((e) => (e as num).toInt()) | ||||||
|  |               .toList(), | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  | Map<String, dynamic> _$SnNotableDayToJson(_SnNotableDay instance) => | ||||||
|  |     <String, dynamic>{ | ||||||
|  |       'date': instance.date.toIso8601String(), | ||||||
|  |       'local_name': instance.localName, | ||||||
|  |       'global_name': instance.globalName, | ||||||
|  |       'country_code': instance.countryCode, | ||||||
|  |       'holidays': instance.holidays, | ||||||
|  |     }; | ||||||
|  |  | ||||||
| _SnActivity _$SnActivityFromJson(Map<String, dynamic> json) => _SnActivity( | _SnActivity _$SnActivityFromJson(Map<String, dynamic> json) => _SnActivity( | ||||||
|   id: json['id'] as String, |   id: json['id'] as String, | ||||||
|   type: json['type'] as String, |   type: json['type'] as String, | ||||||
|   | |||||||
| @@ -11,6 +11,20 @@ sealed class AppToken with _$AppToken { | |||||||
|       _$AppTokenFromJson(json); |       _$AppTokenFromJson(json); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @freezed | ||||||
|  | sealed class GeoIpLocation with _$GeoIpLocation { | ||||||
|  |   const factory GeoIpLocation({ | ||||||
|  |     required double latitude, | ||||||
|  |     required double longitude, | ||||||
|  |     required String countryCode, | ||||||
|  |     required String country, | ||||||
|  |     required String city, | ||||||
|  |   }) = _GeoIpLocation; | ||||||
|  |  | ||||||
|  |   factory GeoIpLocation.fromJson(Map<String, dynamic> json) => | ||||||
|  |       _$GeoIpLocationFromJson(json); | ||||||
|  | } | ||||||
|  |  | ||||||
| @freezed | @freezed | ||||||
| sealed class SnAuthChallenge with _$SnAuthChallenge { | sealed class SnAuthChallenge with _$SnAuthChallenge { | ||||||
|   const factory SnAuthChallenge({ |   const factory SnAuthChallenge({ | ||||||
| @@ -26,7 +40,7 @@ sealed class SnAuthChallenge with _$SnAuthChallenge { | |||||||
|     required String ipAddress, |     required String ipAddress, | ||||||
|     required String userAgent, |     required String userAgent, | ||||||
|     required String? nonce, |     required String? nonce, | ||||||
|     required String? location, |     required GeoIpLocation? location, | ||||||
|     required String accountId, |     required String accountId, | ||||||
|     required DateTime createdAt, |     required DateTime createdAt, | ||||||
|     required DateTime updatedAt, |     required DateTime updatedAt, | ||||||
|   | |||||||
| @@ -269,10 +269,279 @@ as String, | |||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | mixin _$GeoIpLocation { | ||||||
|  |  | ||||||
|  |  double get latitude; double get longitude; String get countryCode; String get country; String get city; | ||||||
|  | /// Create a copy of GeoIpLocation | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
|  | $GeoIpLocationCopyWith<GeoIpLocation> get copyWith => _$GeoIpLocationCopyWithImpl<GeoIpLocation>(this as GeoIpLocation, _$identity); | ||||||
|  |  | ||||||
|  |   /// Serializes this GeoIpLocation to a JSON map. | ||||||
|  |   Map<String, dynamic> toJson(); | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | bool operator ==(Object other) { | ||||||
|  |   return identical(this, other) || (other.runtimeType == runtimeType&&other is GeoIpLocation&&(identical(other.latitude, latitude) || other.latitude == latitude)&&(identical(other.longitude, longitude) || other.longitude == longitude)&&(identical(other.countryCode, countryCode) || other.countryCode == countryCode)&&(identical(other.country, country) || other.country == country)&&(identical(other.city, city) || other.city == city)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  | @override | ||||||
|  | int get hashCode => Object.hash(runtimeType,latitude,longitude,countryCode,country,city); | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | String toString() { | ||||||
|  |   return 'GeoIpLocation(latitude: $latitude, longitude: $longitude, countryCode: $countryCode, country: $country, city: $city)'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | abstract mixin class $GeoIpLocationCopyWith<$Res>  { | ||||||
|  |   factory $GeoIpLocationCopyWith(GeoIpLocation value, $Res Function(GeoIpLocation) _then) = _$GeoIpLocationCopyWithImpl; | ||||||
|  | @useResult | ||||||
|  | $Res call({ | ||||||
|  |  double latitude, double longitude, String countryCode, String country, String city | ||||||
|  | }); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  | /// @nodoc | ||||||
|  | class _$GeoIpLocationCopyWithImpl<$Res> | ||||||
|  |     implements $GeoIpLocationCopyWith<$Res> { | ||||||
|  |   _$GeoIpLocationCopyWithImpl(this._self, this._then); | ||||||
|  |  | ||||||
|  |   final GeoIpLocation _self; | ||||||
|  |   final $Res Function(GeoIpLocation) _then; | ||||||
|  |  | ||||||
|  | /// Create a copy of GeoIpLocation | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @pragma('vm:prefer-inline') @override $Res call({Object? latitude = null,Object? longitude = null,Object? countryCode = null,Object? country = null,Object? city = null,}) { | ||||||
|  |   return _then(_self.copyWith( | ||||||
|  | latitude: null == latitude ? _self.latitude : latitude // ignore: cast_nullable_to_non_nullable | ||||||
|  | as double,longitude: null == longitude ? _self.longitude : longitude // ignore: cast_nullable_to_non_nullable | ||||||
|  | as double,countryCode: null == countryCode ? _self.countryCode : countryCode // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,country: null == country ? _self.country : country // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,city: null == city ? _self.city : city // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String, | ||||||
|  |   )); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | /// Adds pattern-matching-related methods to [GeoIpLocation]. | ||||||
|  | extension GeoIpLocationPatterns on GeoIpLocation { | ||||||
|  | /// A variant of `map` that fallback to returning `orElse`. | ||||||
|  | /// | ||||||
|  | /// It is equivalent to doing: | ||||||
|  | /// ```dart | ||||||
|  | /// switch (sealedClass) { | ||||||
|  | ///   case final Subclass value: | ||||||
|  | ///     return ...; | ||||||
|  | ///   case _: | ||||||
|  | ///     return orElse(); | ||||||
|  | /// } | ||||||
|  | /// ``` | ||||||
|  |  | ||||||
|  | @optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _GeoIpLocation value)?  $default,{required TResult orElse(),}){ | ||||||
|  | final _that = this; | ||||||
|  | switch (_that) { | ||||||
|  | case _GeoIpLocation() when $default != null: | ||||||
|  | return $default(_that);case _: | ||||||
|  |   return orElse(); | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  | /// A `switch`-like method, using callbacks. | ||||||
|  | /// | ||||||
|  | /// Callbacks receives the raw object, upcasted. | ||||||
|  | /// It is equivalent to doing: | ||||||
|  | /// ```dart | ||||||
|  | /// switch (sealedClass) { | ||||||
|  | ///   case final Subclass value: | ||||||
|  | ///     return ...; | ||||||
|  | ///   case final Subclass2 value: | ||||||
|  | ///     return ...; | ||||||
|  | /// } | ||||||
|  | /// ``` | ||||||
|  |  | ||||||
|  | @optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _GeoIpLocation value)  $default,){ | ||||||
|  | final _that = this; | ||||||
|  | switch (_that) { | ||||||
|  | case _GeoIpLocation(): | ||||||
|  | return $default(_that);} | ||||||
|  | } | ||||||
|  | /// A variant of `map` that fallback to returning `null`. | ||||||
|  | /// | ||||||
|  | /// It is equivalent to doing: | ||||||
|  | /// ```dart | ||||||
|  | /// switch (sealedClass) { | ||||||
|  | ///   case final Subclass value: | ||||||
|  | ///     return ...; | ||||||
|  | ///   case _: | ||||||
|  | ///     return null; | ||||||
|  | /// } | ||||||
|  | /// ``` | ||||||
|  |  | ||||||
|  | @optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _GeoIpLocation value)?  $default,){ | ||||||
|  | final _that = this; | ||||||
|  | switch (_that) { | ||||||
|  | case _GeoIpLocation() when $default != null: | ||||||
|  | return $default(_that);case _: | ||||||
|  |   return null; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  | /// A variant of `when` that fallback to an `orElse` callback. | ||||||
|  | /// | ||||||
|  | /// It is equivalent to doing: | ||||||
|  | /// ```dart | ||||||
|  | /// switch (sealedClass) { | ||||||
|  | ///   case Subclass(:final field): | ||||||
|  | ///     return ...; | ||||||
|  | ///   case _: | ||||||
|  | ///     return orElse(); | ||||||
|  | /// } | ||||||
|  | /// ``` | ||||||
|  |  | ||||||
|  | @optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( double latitude,  double longitude,  String countryCode,  String country,  String city)?  $default,{required TResult orElse(),}) {final _that = this; | ||||||
|  | switch (_that) { | ||||||
|  | case _GeoIpLocation() when $default != null: | ||||||
|  | return $default(_that.latitude,_that.longitude,_that.countryCode,_that.country,_that.city);case _: | ||||||
|  |   return orElse(); | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  | /// A `switch`-like method, using callbacks. | ||||||
|  | /// | ||||||
|  | /// As opposed to `map`, this offers destructuring. | ||||||
|  | /// It is equivalent to doing: | ||||||
|  | /// ```dart | ||||||
|  | /// switch (sealedClass) { | ||||||
|  | ///   case Subclass(:final field): | ||||||
|  | ///     return ...; | ||||||
|  | ///   case Subclass2(:final field2): | ||||||
|  | ///     return ...; | ||||||
|  | /// } | ||||||
|  | /// ``` | ||||||
|  |  | ||||||
|  | @optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( double latitude,  double longitude,  String countryCode,  String country,  String city)  $default,) {final _that = this; | ||||||
|  | switch (_that) { | ||||||
|  | case _GeoIpLocation(): | ||||||
|  | return $default(_that.latitude,_that.longitude,_that.countryCode,_that.country,_that.city);} | ||||||
|  | } | ||||||
|  | /// A variant of `when` that fallback to returning `null` | ||||||
|  | /// | ||||||
|  | /// It is equivalent to doing: | ||||||
|  | /// ```dart | ||||||
|  | /// switch (sealedClass) { | ||||||
|  | ///   case Subclass(:final field): | ||||||
|  | ///     return ...; | ||||||
|  | ///   case _: | ||||||
|  | ///     return null; | ||||||
|  | /// } | ||||||
|  | /// ``` | ||||||
|  |  | ||||||
|  | @optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( double latitude,  double longitude,  String countryCode,  String country,  String city)?  $default,) {final _that = this; | ||||||
|  | switch (_that) { | ||||||
|  | case _GeoIpLocation() when $default != null: | ||||||
|  | return $default(_that.latitude,_that.longitude,_that.countryCode,_that.country,_that.city);case _: | ||||||
|  |   return null; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | @JsonSerializable() | ||||||
|  |  | ||||||
|  | class _GeoIpLocation implements GeoIpLocation { | ||||||
|  |   const _GeoIpLocation({required this.latitude, required this.longitude, required this.countryCode, required this.country, required this.city}); | ||||||
|  |   factory _GeoIpLocation.fromJson(Map<String, dynamic> json) => _$GeoIpLocationFromJson(json); | ||||||
|  |  | ||||||
|  | @override final  double latitude; | ||||||
|  | @override final  double longitude; | ||||||
|  | @override final  String countryCode; | ||||||
|  | @override final  String country; | ||||||
|  | @override final  String city; | ||||||
|  |  | ||||||
|  | /// Create a copy of GeoIpLocation | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
|  | _$GeoIpLocationCopyWith<_GeoIpLocation> get copyWith => __$GeoIpLocationCopyWithImpl<_GeoIpLocation>(this, _$identity); | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | Map<String, dynamic> toJson() { | ||||||
|  |   return _$GeoIpLocationToJson(this, ); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | bool operator ==(Object other) { | ||||||
|  |   return identical(this, other) || (other.runtimeType == runtimeType&&other is _GeoIpLocation&&(identical(other.latitude, latitude) || other.latitude == latitude)&&(identical(other.longitude, longitude) || other.longitude == longitude)&&(identical(other.countryCode, countryCode) || other.countryCode == countryCode)&&(identical(other.country, country) || other.country == country)&&(identical(other.city, city) || other.city == city)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  | @override | ||||||
|  | int get hashCode => Object.hash(runtimeType,latitude,longitude,countryCode,country,city); | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | String toString() { | ||||||
|  |   return 'GeoIpLocation(latitude: $latitude, longitude: $longitude, countryCode: $countryCode, country: $country, city: $city)'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | abstract mixin class _$GeoIpLocationCopyWith<$Res> implements $GeoIpLocationCopyWith<$Res> { | ||||||
|  |   factory _$GeoIpLocationCopyWith(_GeoIpLocation value, $Res Function(_GeoIpLocation) _then) = __$GeoIpLocationCopyWithImpl; | ||||||
|  | @override @useResult | ||||||
|  | $Res call({ | ||||||
|  |  double latitude, double longitude, String countryCode, String country, String city | ||||||
|  | }); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  | /// @nodoc | ||||||
|  | class __$GeoIpLocationCopyWithImpl<$Res> | ||||||
|  |     implements _$GeoIpLocationCopyWith<$Res> { | ||||||
|  |   __$GeoIpLocationCopyWithImpl(this._self, this._then); | ||||||
|  |  | ||||||
|  |   final _GeoIpLocation _self; | ||||||
|  |   final $Res Function(_GeoIpLocation) _then; | ||||||
|  |  | ||||||
|  | /// Create a copy of GeoIpLocation | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override @pragma('vm:prefer-inline') $Res call({Object? latitude = null,Object? longitude = null,Object? countryCode = null,Object? country = null,Object? city = null,}) { | ||||||
|  |   return _then(_GeoIpLocation( | ||||||
|  | latitude: null == latitude ? _self.latitude : latitude // ignore: cast_nullable_to_non_nullable | ||||||
|  | as double,longitude: null == longitude ? _self.longitude : longitude // ignore: cast_nullable_to_non_nullable | ||||||
|  | as double,countryCode: null == countryCode ? _self.countryCode : countryCode // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,country: null == country ? _self.country : country // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,city: null == city ? _self.city : city // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String, | ||||||
|  |   )); | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
| /// @nodoc | /// @nodoc | ||||||
| mixin _$SnAuthChallenge { | mixin _$SnAuthChallenge { | ||||||
|  |  | ||||||
|  String get id; DateTime get expiredAt; int get stepRemain; int get stepTotal; int get failedAttempts; int get type; List<String> get blacklistFactors; List<dynamic> get audiences; List<dynamic> get scopes; String get ipAddress; String get userAgent; String? get nonce; String? get location; String get accountId; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; |  String get id; DateTime get expiredAt; int get stepRemain; int get stepTotal; int get failedAttempts; int get type; List<String> get blacklistFactors; List<dynamic> get audiences; List<dynamic> get scopes; String get ipAddress; String get userAgent; String? get nonce; GeoIpLocation? get location; String get accountId; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; | ||||||
| /// Create a copy of SnAuthChallenge | /// Create a copy of SnAuthChallenge | ||||||
| /// with the given fields replaced by the non-null parameter values. | /// with the given fields replaced by the non-null parameter values. | ||||||
| @JsonKey(includeFromJson: false, includeToJson: false) | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
| @@ -305,11 +574,11 @@ abstract mixin class $SnAuthChallengeCopyWith<$Res>  { | |||||||
|   factory $SnAuthChallengeCopyWith(SnAuthChallenge value, $Res Function(SnAuthChallenge) _then) = _$SnAuthChallengeCopyWithImpl; |   factory $SnAuthChallengeCopyWith(SnAuthChallenge value, $Res Function(SnAuthChallenge) _then) = _$SnAuthChallengeCopyWithImpl; | ||||||
| @useResult | @useResult | ||||||
| $Res call({ | $Res call({ | ||||||
|  String id, DateTime expiredAt, int stepRemain, int stepTotal, int failedAttempts, int type, List<String> blacklistFactors, List<dynamic> audiences, List<dynamic> scopes, String ipAddress, String userAgent, String? nonce, String? location, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt |  String id, DateTime expiredAt, int stepRemain, int stepTotal, int failedAttempts, int type, List<String> blacklistFactors, List<dynamic> audiences, List<dynamic> scopes, String ipAddress, String userAgent, String? nonce, GeoIpLocation? location, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  |  | ||||||
|  | $GeoIpLocationCopyWith<$Res>? get location; | ||||||
|  |  | ||||||
| } | } | ||||||
| /// @nodoc | /// @nodoc | ||||||
| @@ -337,14 +606,26 @@ as List<dynamic>,ipAddress: null == ipAddress ? _self.ipAddress : ipAddress // i | |||||||
| as String,userAgent: null == userAgent ? _self.userAgent : userAgent // ignore: cast_nullable_to_non_nullable | as String,userAgent: null == userAgent ? _self.userAgent : userAgent // ignore: cast_nullable_to_non_nullable | ||||||
| as String,nonce: freezed == nonce ? _self.nonce : nonce // ignore: cast_nullable_to_non_nullable | as String,nonce: freezed == nonce ? _self.nonce : nonce // ignore: cast_nullable_to_non_nullable | ||||||
| as String?,location: freezed == location ? _self.location : location // ignore: cast_nullable_to_non_nullable | as String?,location: freezed == location ? _self.location : location // ignore: cast_nullable_to_non_nullable | ||||||
| as String?,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable | as GeoIpLocation?,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable | ||||||
| as String,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable | as String,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable | ||||||
| as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable | as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable | ||||||
| as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable | as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable | ||||||
| as DateTime?, | as DateTime?, | ||||||
|   )); |   )); | ||||||
| } | } | ||||||
|  | /// Create a copy of SnAuthChallenge | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
|  | $GeoIpLocationCopyWith<$Res>? get location { | ||||||
|  |     if (_self.location == null) { | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return $GeoIpLocationCopyWith<$Res>(_self.location!, (value) { | ||||||
|  |     return _then(_self.copyWith(location: value)); | ||||||
|  |   }); | ||||||
|  | } | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -423,7 +704,7 @@ return $default(_that);case _: | |||||||
| /// } | /// } | ||||||
| /// ``` | /// ``` | ||||||
|  |  | ||||||
| @optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id,  DateTime expiredAt,  int stepRemain,  int stepTotal,  int failedAttempts,  int type,  List<String> blacklistFactors,  List<dynamic> audiences,  List<dynamic> scopes,  String ipAddress,  String userAgent,  String? nonce,  String? location,  String accountId,  DateTime createdAt,  DateTime updatedAt,  DateTime? deletedAt)?  $default,{required TResult orElse(),}) {final _that = this; | @optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id,  DateTime expiredAt,  int stepRemain,  int stepTotal,  int failedAttempts,  int type,  List<String> blacklistFactors,  List<dynamic> audiences,  List<dynamic> scopes,  String ipAddress,  String userAgent,  String? nonce,  GeoIpLocation? location,  String accountId,  DateTime createdAt,  DateTime updatedAt,  DateTime? deletedAt)?  $default,{required TResult orElse(),}) {final _that = this; | ||||||
| switch (_that) { | switch (_that) { | ||||||
| case _SnAuthChallenge() when $default != null: | case _SnAuthChallenge() when $default != null: | ||||||
| return $default(_that.id,_that.expiredAt,_that.stepRemain,_that.stepTotal,_that.failedAttempts,_that.type,_that.blacklistFactors,_that.audiences,_that.scopes,_that.ipAddress,_that.userAgent,_that.nonce,_that.location,_that.accountId,_that.createdAt,_that.updatedAt,_that.deletedAt);case _: | return $default(_that.id,_that.expiredAt,_that.stepRemain,_that.stepTotal,_that.failedAttempts,_that.type,_that.blacklistFactors,_that.audiences,_that.scopes,_that.ipAddress,_that.userAgent,_that.nonce,_that.location,_that.accountId,_that.createdAt,_that.updatedAt,_that.deletedAt);case _: | ||||||
| @@ -444,7 +725,7 @@ return $default(_that.id,_that.expiredAt,_that.stepRemain,_that.stepTotal,_that. | |||||||
| /// } | /// } | ||||||
| /// ``` | /// ``` | ||||||
|  |  | ||||||
| @optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id,  DateTime expiredAt,  int stepRemain,  int stepTotal,  int failedAttempts,  int type,  List<String> blacklistFactors,  List<dynamic> audiences,  List<dynamic> scopes,  String ipAddress,  String userAgent,  String? nonce,  String? location,  String accountId,  DateTime createdAt,  DateTime updatedAt,  DateTime? deletedAt)  $default,) {final _that = this; | @optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id,  DateTime expiredAt,  int stepRemain,  int stepTotal,  int failedAttempts,  int type,  List<String> blacklistFactors,  List<dynamic> audiences,  List<dynamic> scopes,  String ipAddress,  String userAgent,  String? nonce,  GeoIpLocation? location,  String accountId,  DateTime createdAt,  DateTime updatedAt,  DateTime? deletedAt)  $default,) {final _that = this; | ||||||
| switch (_that) { | switch (_that) { | ||||||
| case _SnAuthChallenge(): | case _SnAuthChallenge(): | ||||||
| return $default(_that.id,_that.expiredAt,_that.stepRemain,_that.stepTotal,_that.failedAttempts,_that.type,_that.blacklistFactors,_that.audiences,_that.scopes,_that.ipAddress,_that.userAgent,_that.nonce,_that.location,_that.accountId,_that.createdAt,_that.updatedAt,_that.deletedAt);} | return $default(_that.id,_that.expiredAt,_that.stepRemain,_that.stepTotal,_that.failedAttempts,_that.type,_that.blacklistFactors,_that.audiences,_that.scopes,_that.ipAddress,_that.userAgent,_that.nonce,_that.location,_that.accountId,_that.createdAt,_that.updatedAt,_that.deletedAt);} | ||||||
| @@ -461,7 +742,7 @@ return $default(_that.id,_that.expiredAt,_that.stepRemain,_that.stepTotal,_that. | |||||||
| /// } | /// } | ||||||
| /// ``` | /// ``` | ||||||
|  |  | ||||||
| @optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id,  DateTime expiredAt,  int stepRemain,  int stepTotal,  int failedAttempts,  int type,  List<String> blacklistFactors,  List<dynamic> audiences,  List<dynamic> scopes,  String ipAddress,  String userAgent,  String? nonce,  String? location,  String accountId,  DateTime createdAt,  DateTime updatedAt,  DateTime? deletedAt)?  $default,) {final _that = this; | @optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id,  DateTime expiredAt,  int stepRemain,  int stepTotal,  int failedAttempts,  int type,  List<String> blacklistFactors,  List<dynamic> audiences,  List<dynamic> scopes,  String ipAddress,  String userAgent,  String? nonce,  GeoIpLocation? location,  String accountId,  DateTime createdAt,  DateTime updatedAt,  DateTime? deletedAt)?  $default,) {final _that = this; | ||||||
| switch (_that) { | switch (_that) { | ||||||
| case _SnAuthChallenge() when $default != null: | case _SnAuthChallenge() when $default != null: | ||||||
| return $default(_that.id,_that.expiredAt,_that.stepRemain,_that.stepTotal,_that.failedAttempts,_that.type,_that.blacklistFactors,_that.audiences,_that.scopes,_that.ipAddress,_that.userAgent,_that.nonce,_that.location,_that.accountId,_that.createdAt,_that.updatedAt,_that.deletedAt);case _: | return $default(_that.id,_that.expiredAt,_that.stepRemain,_that.stepTotal,_that.failedAttempts,_that.type,_that.blacklistFactors,_that.audiences,_that.scopes,_that.ipAddress,_that.userAgent,_that.nonce,_that.location,_that.accountId,_that.createdAt,_that.updatedAt,_that.deletedAt);case _: | ||||||
| @@ -509,7 +790,7 @@ class _SnAuthChallenge implements SnAuthChallenge { | |||||||
| @override final  String ipAddress; | @override final  String ipAddress; | ||||||
| @override final  String userAgent; | @override final  String userAgent; | ||||||
| @override final  String? nonce; | @override final  String? nonce; | ||||||
| @override final  String? location; | @override final  GeoIpLocation? location; | ||||||
| @override final  String accountId; | @override final  String accountId; | ||||||
| @override final  DateTime createdAt; | @override final  DateTime createdAt; | ||||||
| @override final  DateTime updatedAt; | @override final  DateTime updatedAt; | ||||||
| @@ -548,11 +829,11 @@ abstract mixin class _$SnAuthChallengeCopyWith<$Res> implements $SnAuthChallenge | |||||||
|   factory _$SnAuthChallengeCopyWith(_SnAuthChallenge value, $Res Function(_SnAuthChallenge) _then) = __$SnAuthChallengeCopyWithImpl; |   factory _$SnAuthChallengeCopyWith(_SnAuthChallenge value, $Res Function(_SnAuthChallenge) _then) = __$SnAuthChallengeCopyWithImpl; | ||||||
| @override @useResult | @override @useResult | ||||||
| $Res call({ | $Res call({ | ||||||
|  String id, DateTime expiredAt, int stepRemain, int stepTotal, int failedAttempts, int type, List<String> blacklistFactors, List<dynamic> audiences, List<dynamic> scopes, String ipAddress, String userAgent, String? nonce, String? location, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt |  String id, DateTime expiredAt, int stepRemain, int stepTotal, int failedAttempts, int type, List<String> blacklistFactors, List<dynamic> audiences, List<dynamic> scopes, String ipAddress, String userAgent, String? nonce, GeoIpLocation? location, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @override $GeoIpLocationCopyWith<$Res>? get location; | ||||||
|  |  | ||||||
| } | } | ||||||
| /// @nodoc | /// @nodoc | ||||||
| @@ -580,7 +861,7 @@ as List<dynamic>,ipAddress: null == ipAddress ? _self.ipAddress : ipAddress // i | |||||||
| as String,userAgent: null == userAgent ? _self.userAgent : userAgent // ignore: cast_nullable_to_non_nullable | as String,userAgent: null == userAgent ? _self.userAgent : userAgent // ignore: cast_nullable_to_non_nullable | ||||||
| as String,nonce: freezed == nonce ? _self.nonce : nonce // ignore: cast_nullable_to_non_nullable | as String,nonce: freezed == nonce ? _self.nonce : nonce // ignore: cast_nullable_to_non_nullable | ||||||
| as String?,location: freezed == location ? _self.location : location // ignore: cast_nullable_to_non_nullable | as String?,location: freezed == location ? _self.location : location // ignore: cast_nullable_to_non_nullable | ||||||
| as String?,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable | as GeoIpLocation?,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable | ||||||
| as String,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable | as String,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable | ||||||
| as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable | as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable | ||||||
| as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable | as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable | ||||||
| @@ -588,7 +869,19 @@ as DateTime?, | |||||||
|   )); |   )); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /// Create a copy of SnAuthChallenge | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
|  | $GeoIpLocationCopyWith<$Res>? get location { | ||||||
|  |     if (_self.location == null) { | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return $GeoIpLocationCopyWith<$Res>(_self.location!, (value) { | ||||||
|  |     return _then(_self.copyWith(location: value)); | ||||||
|  |   }); | ||||||
|  | } | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -13,6 +13,24 @@ Map<String, dynamic> _$AppTokenToJson(_AppToken instance) => <String, dynamic>{ | |||||||
|   'token': instance.token, |   'token': instance.token, | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | _GeoIpLocation _$GeoIpLocationFromJson(Map<String, dynamic> json) => | ||||||
|  |     _GeoIpLocation( | ||||||
|  |       latitude: (json['latitude'] as num).toDouble(), | ||||||
|  |       longitude: (json['longitude'] as num).toDouble(), | ||||||
|  |       countryCode: json['country_code'] as String, | ||||||
|  |       country: json['country'] as String, | ||||||
|  |       city: json['city'] as String, | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  | Map<String, dynamic> _$GeoIpLocationToJson(_GeoIpLocation instance) => | ||||||
|  |     <String, dynamic>{ | ||||||
|  |       'latitude': instance.latitude, | ||||||
|  |       'longitude': instance.longitude, | ||||||
|  |       'country_code': instance.countryCode, | ||||||
|  |       'country': instance.country, | ||||||
|  |       'city': instance.city, | ||||||
|  |     }; | ||||||
|  |  | ||||||
| _SnAuthChallenge _$SnAuthChallengeFromJson(Map<String, dynamic> json) => | _SnAuthChallenge _$SnAuthChallengeFromJson(Map<String, dynamic> json) => | ||||||
|     _SnAuthChallenge( |     _SnAuthChallenge( | ||||||
|       id: json['id'] as String, |       id: json['id'] as String, | ||||||
| @@ -30,7 +48,12 @@ _SnAuthChallenge _$SnAuthChallengeFromJson(Map<String, dynamic> json) => | |||||||
|       ipAddress: json['ip_address'] as String, |       ipAddress: json['ip_address'] as String, | ||||||
|       userAgent: json['user_agent'] as String, |       userAgent: json['user_agent'] as String, | ||||||
|       nonce: json['nonce'] as String?, |       nonce: json['nonce'] as String?, | ||||||
|       location: json['location'] as String?, |       location: | ||||||
|  |           json['location'] == null | ||||||
|  |               ? null | ||||||
|  |               : GeoIpLocation.fromJson( | ||||||
|  |                 json['location'] as Map<String, dynamic>, | ||||||
|  |               ), | ||||||
|       accountId: json['account_id'] as String, |       accountId: json['account_id'] as String, | ||||||
|       createdAt: DateTime.parse(json['created_at'] as String), |       createdAt: DateTime.parse(json['created_at'] as String), | ||||||
|       updatedAt: DateTime.parse(json['updated_at'] as String), |       updatedAt: DateTime.parse(json['updated_at'] as String), | ||||||
| @@ -54,7 +77,7 @@ Map<String, dynamic> _$SnAuthChallengeToJson(_SnAuthChallenge instance) => | |||||||
|       'ip_address': instance.ipAddress, |       'ip_address': instance.ipAddress, | ||||||
|       'user_agent': instance.userAgent, |       'user_agent': instance.userAgent, | ||||||
|       'nonce': instance.nonce, |       'nonce': instance.nonce, | ||||||
|       'location': instance.location, |       'location': instance.location?.toJson(), | ||||||
|       'account_id': instance.accountId, |       'account_id': instance.accountId, | ||||||
|       'created_at': instance.createdAt.toIso8601String(), |       'created_at': instance.createdAt.toIso8601String(), | ||||||
|       'updated_at': instance.updatedAt.toIso8601String(), |       'updated_at': instance.updatedAt.toIso8601String(), | ||||||
|   | |||||||
| @@ -22,6 +22,7 @@ sealed class SnPost with _$SnPost { | |||||||
|     String? slug, |     String? slug, | ||||||
|     @Default(0) int type, |     @Default(0) int type, | ||||||
|     Map<String, dynamic>? meta, |     Map<String, dynamic>? meta, | ||||||
|  |     SnPostEmbedView? embedView, | ||||||
|     @Default(0) int viewsUnique, |     @Default(0) int viewsUnique, | ||||||
|     @Default(0) int viewsTotal, |     @Default(0) int viewsTotal, | ||||||
|     @Default(0) int upvotes, |     @Default(0) int upvotes, | ||||||
| @@ -105,3 +106,20 @@ const Map<String, ReactInfo> kReactionTemplates = { | |||||||
|   'pray': ReactInfo(icon: '🙏', attitude: 0), |   'pray': ReactInfo(icon: '🙏', attitude: 0), | ||||||
|   'heart': ReactInfo(icon: '❤️', attitude: 0), |   'heart': ReactInfo(icon: '❤️', attitude: 0), | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | enum PostEmbedViewRenderer { | ||||||
|  |   @JsonValue(0) | ||||||
|  |   webView, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @freezed | ||||||
|  | sealed class SnPostEmbedView with _$SnPostEmbedView { | ||||||
|  |   const factory SnPostEmbedView({ | ||||||
|  |     required String uri, | ||||||
|  |     double? aspectRatio, | ||||||
|  |     @Default(PostEmbedViewRenderer.webView) PostEmbedViewRenderer renderer, | ||||||
|  |   }) = _SnPostEmbedView; | ||||||
|  |  | ||||||
|  |   factory SnPostEmbedView.fromJson(Map<String, dynamic> json) => | ||||||
|  |       _$SnPostEmbedViewFromJson(json); | ||||||
|  | } | ||||||
|   | |||||||
| @@ -15,7 +15,7 @@ T _$identity<T>(T value) => value; | |||||||
| /// @nodoc | /// @nodoc | ||||||
| mixin _$SnPost { | mixin _$SnPost { | ||||||
|  |  | ||||||
|  String get id; String? get title; String? get description; String? get language; DateTime? get editedAt; DateTime? get publishedAt; int get visibility; String? get content; String? get slug; int get type; Map<String, dynamic>? get meta; int get viewsUnique; int get viewsTotal; int get upvotes; int get downvotes; int get repliesCount; int? get pinMode; String? get threadedPostId; SnPost? get threadedPost; String? get repliedPostId; SnPost? get repliedPost; String? get forwardedPostId; SnPost? get forwardedPost; String? get realmId; SnRealm? get realm; List<SnCloudFile> get attachments; SnPublisher get publisher; Map<String, int> get reactionsCount; Map<String, bool> get reactionsMade; List<dynamic> get reactions; List<SnPostTag> get tags; List<SnPostCategory> get categories; List<dynamic> get collections; DateTime? get createdAt; DateTime? get updatedAt; DateTime? get deletedAt; bool get isTruncated; |  String get id; String? get title; String? get description; String? get language; DateTime? get editedAt; DateTime? get publishedAt; int get visibility; String? get content; String? get slug; int get type; Map<String, dynamic>? get meta; SnPostEmbedView? get embedView; int get viewsUnique; int get viewsTotal; int get upvotes; int get downvotes; int get repliesCount; int? get pinMode; String? get threadedPostId; SnPost? get threadedPost; String? get repliedPostId; SnPost? get repliedPost; String? get forwardedPostId; SnPost? get forwardedPost; String? get realmId; SnRealm? get realm; List<SnCloudFile> get attachments; SnPublisher get publisher; Map<String, int> get reactionsCount; Map<String, bool> get reactionsMade; List<dynamic> get reactions; List<SnPostTag> get tags; List<SnPostCategory> get categories; List<dynamic> get collections; DateTime? get createdAt; DateTime? get updatedAt; DateTime? get deletedAt; bool get isTruncated; | ||||||
| /// Create a copy of SnPost | /// Create a copy of SnPost | ||||||
| /// with the given fields replaced by the non-null parameter values. | /// with the given fields replaced by the non-null parameter values. | ||||||
| @JsonKey(includeFromJson: false, includeToJson: false) | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
| @@ -28,16 +28,16 @@ $SnPostCopyWith<SnPost> get copyWith => _$SnPostCopyWithImpl<SnPost>(this as SnP | |||||||
|  |  | ||||||
| @override | @override | ||||||
| bool operator ==(Object other) { | bool operator ==(Object other) { | ||||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is SnPost&&(identical(other.id, id) || other.id == id)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)&&(identical(other.language, language) || other.language == language)&&(identical(other.editedAt, editedAt) || other.editedAt == editedAt)&&(identical(other.publishedAt, publishedAt) || other.publishedAt == publishedAt)&&(identical(other.visibility, visibility) || other.visibility == visibility)&&(identical(other.content, content) || other.content == content)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.type, type) || other.type == type)&&const DeepCollectionEquality().equals(other.meta, meta)&&(identical(other.viewsUnique, viewsUnique) || other.viewsUnique == viewsUnique)&&(identical(other.viewsTotal, viewsTotal) || other.viewsTotal == viewsTotal)&&(identical(other.upvotes, upvotes) || other.upvotes == upvotes)&&(identical(other.downvotes, downvotes) || other.downvotes == downvotes)&&(identical(other.repliesCount, repliesCount) || other.repliesCount == repliesCount)&&(identical(other.pinMode, pinMode) || other.pinMode == pinMode)&&(identical(other.threadedPostId, threadedPostId) || other.threadedPostId == threadedPostId)&&(identical(other.threadedPost, threadedPost) || other.threadedPost == threadedPost)&&(identical(other.repliedPostId, repliedPostId) || other.repliedPostId == repliedPostId)&&(identical(other.repliedPost, repliedPost) || other.repliedPost == repliedPost)&&(identical(other.forwardedPostId, forwardedPostId) || other.forwardedPostId == forwardedPostId)&&(identical(other.forwardedPost, forwardedPost) || other.forwardedPost == forwardedPost)&&(identical(other.realmId, realmId) || other.realmId == realmId)&&(identical(other.realm, realm) || other.realm == realm)&&const DeepCollectionEquality().equals(other.attachments, attachments)&&(identical(other.publisher, publisher) || other.publisher == publisher)&&const DeepCollectionEquality().equals(other.reactionsCount, reactionsCount)&&const DeepCollectionEquality().equals(other.reactionsMade, reactionsMade)&&const DeepCollectionEquality().equals(other.reactions, reactions)&&const DeepCollectionEquality().equals(other.tags, tags)&&const DeepCollectionEquality().equals(other.categories, categories)&&const DeepCollectionEquality().equals(other.collections, collections)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.isTruncated, isTruncated) || other.isTruncated == isTruncated)); |   return identical(this, other) || (other.runtimeType == runtimeType&&other is SnPost&&(identical(other.id, id) || other.id == id)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)&&(identical(other.language, language) || other.language == language)&&(identical(other.editedAt, editedAt) || other.editedAt == editedAt)&&(identical(other.publishedAt, publishedAt) || other.publishedAt == publishedAt)&&(identical(other.visibility, visibility) || other.visibility == visibility)&&(identical(other.content, content) || other.content == content)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.type, type) || other.type == type)&&const DeepCollectionEquality().equals(other.meta, meta)&&(identical(other.embedView, embedView) || other.embedView == embedView)&&(identical(other.viewsUnique, viewsUnique) || other.viewsUnique == viewsUnique)&&(identical(other.viewsTotal, viewsTotal) || other.viewsTotal == viewsTotal)&&(identical(other.upvotes, upvotes) || other.upvotes == upvotes)&&(identical(other.downvotes, downvotes) || other.downvotes == downvotes)&&(identical(other.repliesCount, repliesCount) || other.repliesCount == repliesCount)&&(identical(other.pinMode, pinMode) || other.pinMode == pinMode)&&(identical(other.threadedPostId, threadedPostId) || other.threadedPostId == threadedPostId)&&(identical(other.threadedPost, threadedPost) || other.threadedPost == threadedPost)&&(identical(other.repliedPostId, repliedPostId) || other.repliedPostId == repliedPostId)&&(identical(other.repliedPost, repliedPost) || other.repliedPost == repliedPost)&&(identical(other.forwardedPostId, forwardedPostId) || other.forwardedPostId == forwardedPostId)&&(identical(other.forwardedPost, forwardedPost) || other.forwardedPost == forwardedPost)&&(identical(other.realmId, realmId) || other.realmId == realmId)&&(identical(other.realm, realm) || other.realm == realm)&&const DeepCollectionEquality().equals(other.attachments, attachments)&&(identical(other.publisher, publisher) || other.publisher == publisher)&&const DeepCollectionEquality().equals(other.reactionsCount, reactionsCount)&&const DeepCollectionEquality().equals(other.reactionsMade, reactionsMade)&&const DeepCollectionEquality().equals(other.reactions, reactions)&&const DeepCollectionEquality().equals(other.tags, tags)&&const DeepCollectionEquality().equals(other.categories, categories)&&const DeepCollectionEquality().equals(other.collections, collections)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.isTruncated, isTruncated) || other.isTruncated == isTruncated)); | ||||||
| } | } | ||||||
|  |  | ||||||
| @JsonKey(includeFromJson: false, includeToJson: false) | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
| @override | @override | ||||||
| int get hashCode => Object.hashAll([runtimeType,id,title,description,language,editedAt,publishedAt,visibility,content,slug,type,const DeepCollectionEquality().hash(meta),viewsUnique,viewsTotal,upvotes,downvotes,repliesCount,pinMode,threadedPostId,threadedPost,repliedPostId,repliedPost,forwardedPostId,forwardedPost,realmId,realm,const DeepCollectionEquality().hash(attachments),publisher,const DeepCollectionEquality().hash(reactionsCount),const DeepCollectionEquality().hash(reactionsMade),const DeepCollectionEquality().hash(reactions),const DeepCollectionEquality().hash(tags),const DeepCollectionEquality().hash(categories),const DeepCollectionEquality().hash(collections),createdAt,updatedAt,deletedAt,isTruncated]); | int get hashCode => Object.hashAll([runtimeType,id,title,description,language,editedAt,publishedAt,visibility,content,slug,type,const DeepCollectionEquality().hash(meta),embedView,viewsUnique,viewsTotal,upvotes,downvotes,repliesCount,pinMode,threadedPostId,threadedPost,repliedPostId,repliedPost,forwardedPostId,forwardedPost,realmId,realm,const DeepCollectionEquality().hash(attachments),publisher,const DeepCollectionEquality().hash(reactionsCount),const DeepCollectionEquality().hash(reactionsMade),const DeepCollectionEquality().hash(reactions),const DeepCollectionEquality().hash(tags),const DeepCollectionEquality().hash(categories),const DeepCollectionEquality().hash(collections),createdAt,updatedAt,deletedAt,isTruncated]); | ||||||
|  |  | ||||||
| @override | @override | ||||||
| String toString() { | String toString() { | ||||||
|   return 'SnPost(id: $id, title: $title, description: $description, language: $language, editedAt: $editedAt, publishedAt: $publishedAt, visibility: $visibility, content: $content, slug: $slug, type: $type, meta: $meta, viewsUnique: $viewsUnique, viewsTotal: $viewsTotal, upvotes: $upvotes, downvotes: $downvotes, repliesCount: $repliesCount, pinMode: $pinMode, threadedPostId: $threadedPostId, threadedPost: $threadedPost, repliedPostId: $repliedPostId, repliedPost: $repliedPost, forwardedPostId: $forwardedPostId, forwardedPost: $forwardedPost, realmId: $realmId, realm: $realm, attachments: $attachments, publisher: $publisher, reactionsCount: $reactionsCount, reactionsMade: $reactionsMade, reactions: $reactions, tags: $tags, categories: $categories, collections: $collections, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, isTruncated: $isTruncated)'; |   return 'SnPost(id: $id, title: $title, description: $description, language: $language, editedAt: $editedAt, publishedAt: $publishedAt, visibility: $visibility, content: $content, slug: $slug, type: $type, meta: $meta, embedView: $embedView, viewsUnique: $viewsUnique, viewsTotal: $viewsTotal, upvotes: $upvotes, downvotes: $downvotes, repliesCount: $repliesCount, pinMode: $pinMode, threadedPostId: $threadedPostId, threadedPost: $threadedPost, repliedPostId: $repliedPostId, repliedPost: $repliedPost, forwardedPostId: $forwardedPostId, forwardedPost: $forwardedPost, realmId: $realmId, realm: $realm, attachments: $attachments, publisher: $publisher, reactionsCount: $reactionsCount, reactionsMade: $reactionsMade, reactions: $reactions, tags: $tags, categories: $categories, collections: $collections, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, isTruncated: $isTruncated)'; | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -48,11 +48,11 @@ abstract mixin class $SnPostCopyWith<$Res>  { | |||||||
|   factory $SnPostCopyWith(SnPost value, $Res Function(SnPost) _then) = _$SnPostCopyWithImpl; |   factory $SnPostCopyWith(SnPost value, $Res Function(SnPost) _then) = _$SnPostCopyWithImpl; | ||||||
| @useResult | @useResult | ||||||
| $Res call({ | $Res call({ | ||||||
|  String id, String? title, String? description, String? language, DateTime? editedAt, DateTime? publishedAt, int visibility, String? content, String? slug, int type, Map<String, dynamic>? meta, int viewsUnique, int viewsTotal, int upvotes, int downvotes, int repliesCount, int? pinMode, String? threadedPostId, SnPost? threadedPost, String? repliedPostId, SnPost? repliedPost, String? forwardedPostId, SnPost? forwardedPost, String? realmId, SnRealm? realm, List<SnCloudFile> attachments, SnPublisher publisher, Map<String, int> reactionsCount, Map<String, bool> reactionsMade, List<dynamic> reactions, List<SnPostTag> tags, List<SnPostCategory> categories, List<dynamic> collections, DateTime? createdAt, DateTime? updatedAt, DateTime? deletedAt, bool isTruncated |  String id, String? title, String? description, String? language, DateTime? editedAt, DateTime? publishedAt, int visibility, String? content, String? slug, int type, Map<String, dynamic>? meta, SnPostEmbedView? embedView, int viewsUnique, int viewsTotal, int upvotes, int downvotes, int repliesCount, int? pinMode, String? threadedPostId, SnPost? threadedPost, String? repliedPostId, SnPost? repliedPost, String? forwardedPostId, SnPost? forwardedPost, String? realmId, SnRealm? realm, List<SnCloudFile> attachments, SnPublisher publisher, Map<String, int> reactionsCount, Map<String, bool> reactionsMade, List<dynamic> reactions, List<SnPostTag> tags, List<SnPostCategory> categories, List<dynamic> collections, DateTime? createdAt, DateTime? updatedAt, DateTime? deletedAt, bool isTruncated | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  |  | ||||||
| $SnPostCopyWith<$Res>? get threadedPost;$SnPostCopyWith<$Res>? get repliedPost;$SnPostCopyWith<$Res>? get forwardedPost;$SnRealmCopyWith<$Res>? get realm;$SnPublisherCopyWith<$Res> get publisher; | $SnPostEmbedViewCopyWith<$Res>? get embedView;$SnPostCopyWith<$Res>? get threadedPost;$SnPostCopyWith<$Res>? get repliedPost;$SnPostCopyWith<$Res>? get forwardedPost;$SnRealmCopyWith<$Res>? get realm;$SnPublisherCopyWith<$Res> get publisher; | ||||||
|  |  | ||||||
| } | } | ||||||
| /// @nodoc | /// @nodoc | ||||||
| @@ -65,7 +65,7 @@ class _$SnPostCopyWithImpl<$Res> | |||||||
|  |  | ||||||
| /// Create a copy of SnPost | /// Create a copy of SnPost | ||||||
| /// with the given fields replaced by the non-null parameter values. | /// with the given fields replaced by the non-null parameter values. | ||||||
| @pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? title = freezed,Object? description = freezed,Object? language = freezed,Object? editedAt = freezed,Object? publishedAt = freezed,Object? visibility = null,Object? content = freezed,Object? slug = freezed,Object? type = null,Object? meta = freezed,Object? viewsUnique = null,Object? viewsTotal = null,Object? upvotes = null,Object? downvotes = null,Object? repliesCount = null,Object? pinMode = freezed,Object? threadedPostId = freezed,Object? threadedPost = freezed,Object? repliedPostId = freezed,Object? repliedPost = freezed,Object? forwardedPostId = freezed,Object? forwardedPost = freezed,Object? realmId = freezed,Object? realm = freezed,Object? attachments = null,Object? publisher = null,Object? reactionsCount = null,Object? reactionsMade = null,Object? reactions = null,Object? tags = null,Object? categories = null,Object? collections = null,Object? createdAt = freezed,Object? updatedAt = freezed,Object? deletedAt = freezed,Object? isTruncated = null,}) { | @pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? title = freezed,Object? description = freezed,Object? language = freezed,Object? editedAt = freezed,Object? publishedAt = freezed,Object? visibility = null,Object? content = freezed,Object? slug = freezed,Object? type = null,Object? meta = freezed,Object? embedView = freezed,Object? viewsUnique = null,Object? viewsTotal = null,Object? upvotes = null,Object? downvotes = null,Object? repliesCount = null,Object? pinMode = freezed,Object? threadedPostId = freezed,Object? threadedPost = freezed,Object? repliedPostId = freezed,Object? repliedPost = freezed,Object? forwardedPostId = freezed,Object? forwardedPost = freezed,Object? realmId = freezed,Object? realm = freezed,Object? attachments = null,Object? publisher = null,Object? reactionsCount = null,Object? reactionsMade = null,Object? reactions = null,Object? tags = null,Object? categories = null,Object? collections = null,Object? createdAt = freezed,Object? updatedAt = freezed,Object? deletedAt = freezed,Object? isTruncated = null,}) { | ||||||
|   return _then(_self.copyWith( |   return _then(_self.copyWith( | ||||||
| id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable | id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable | ||||||
| as String,title: freezed == title ? _self.title : title // ignore: cast_nullable_to_non_nullable | as String,title: freezed == title ? _self.title : title // ignore: cast_nullable_to_non_nullable | ||||||
| @@ -78,7 +78,8 @@ as int,content: freezed == content ? _self.content : content // ignore: cast_nul | |||||||
| as String?,slug: freezed == slug ? _self.slug : slug // ignore: cast_nullable_to_non_nullable | as String?,slug: freezed == slug ? _self.slug : slug // ignore: cast_nullable_to_non_nullable | ||||||
| as String?,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable | as String?,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable | ||||||
| as int,meta: freezed == meta ? _self.meta : meta // ignore: cast_nullable_to_non_nullable | as int,meta: freezed == meta ? _self.meta : meta // ignore: cast_nullable_to_non_nullable | ||||||
| as Map<String, dynamic>?,viewsUnique: null == viewsUnique ? _self.viewsUnique : viewsUnique // ignore: cast_nullable_to_non_nullable | as Map<String, dynamic>?,embedView: freezed == embedView ? _self.embedView : embedView // ignore: cast_nullable_to_non_nullable | ||||||
|  | as SnPostEmbedView?,viewsUnique: null == viewsUnique ? _self.viewsUnique : viewsUnique // ignore: cast_nullable_to_non_nullable | ||||||
| as int,viewsTotal: null == viewsTotal ? _self.viewsTotal : viewsTotal // ignore: cast_nullable_to_non_nullable | as int,viewsTotal: null == viewsTotal ? _self.viewsTotal : viewsTotal // ignore: cast_nullable_to_non_nullable | ||||||
| as int,upvotes: null == upvotes ? _self.upvotes : upvotes // ignore: cast_nullable_to_non_nullable | as int,upvotes: null == upvotes ? _self.upvotes : upvotes // ignore: cast_nullable_to_non_nullable | ||||||
| as int,downvotes: null == downvotes ? _self.downvotes : downvotes // ignore: cast_nullable_to_non_nullable | as int,downvotes: null == downvotes ? _self.downvotes : downvotes // ignore: cast_nullable_to_non_nullable | ||||||
| @@ -111,6 +112,18 @@ as bool, | |||||||
| /// with the given fields replaced by the non-null parameter values. | /// with the given fields replaced by the non-null parameter values. | ||||||
| @override | @override | ||||||
| @pragma('vm:prefer-inline') | @pragma('vm:prefer-inline') | ||||||
|  | $SnPostEmbedViewCopyWith<$Res>? get embedView { | ||||||
|  |     if (_self.embedView == null) { | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return $SnPostEmbedViewCopyWith<$Res>(_self.embedView!, (value) { | ||||||
|  |     return _then(_self.copyWith(embedView: value)); | ||||||
|  |   }); | ||||||
|  | }/// Create a copy of SnPost | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
| $SnPostCopyWith<$Res>? get threadedPost { | $SnPostCopyWith<$Res>? get threadedPost { | ||||||
|     if (_self.threadedPost == null) { |     if (_self.threadedPost == null) { | ||||||
|     return null; |     return null; | ||||||
| @@ -243,10 +256,10 @@ return $default(_that);case _: | |||||||
| /// } | /// } | ||||||
| /// ``` | /// ``` | ||||||
|  |  | ||||||
| @optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id,  String? title,  String? description,  String? language,  DateTime? editedAt,  DateTime? publishedAt,  int visibility,  String? content,  String? slug,  int type,  Map<String, dynamic>? meta,  int viewsUnique,  int viewsTotal,  int upvotes,  int downvotes,  int repliesCount,  int? pinMode,  String? threadedPostId,  SnPost? threadedPost,  String? repliedPostId,  SnPost? repliedPost,  String? forwardedPostId,  SnPost? forwardedPost,  String? realmId,  SnRealm? realm,  List<SnCloudFile> attachments,  SnPublisher publisher,  Map<String, int> reactionsCount,  Map<String, bool> reactionsMade,  List<dynamic> reactions,  List<SnPostTag> tags,  List<SnPostCategory> categories,  List<dynamic> collections,  DateTime? createdAt,  DateTime? updatedAt,  DateTime? deletedAt,  bool isTruncated)?  $default,{required TResult orElse(),}) {final _that = this; | @optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id,  String? title,  String? description,  String? language,  DateTime? editedAt,  DateTime? publishedAt,  int visibility,  String? content,  String? slug,  int type,  Map<String, dynamic>? meta,  SnPostEmbedView? embedView,  int viewsUnique,  int viewsTotal,  int upvotes,  int downvotes,  int repliesCount,  int? pinMode,  String? threadedPostId,  SnPost? threadedPost,  String? repliedPostId,  SnPost? repliedPost,  String? forwardedPostId,  SnPost? forwardedPost,  String? realmId,  SnRealm? realm,  List<SnCloudFile> attachments,  SnPublisher publisher,  Map<String, int> reactionsCount,  Map<String, bool> reactionsMade,  List<dynamic> reactions,  List<SnPostTag> tags,  List<SnPostCategory> categories,  List<dynamic> collections,  DateTime? createdAt,  DateTime? updatedAt,  DateTime? deletedAt,  bool isTruncated)?  $default,{required TResult orElse(),}) {final _that = this; | ||||||
| switch (_that) { | switch (_that) { | ||||||
| case _SnPost() when $default != null: | case _SnPost() when $default != null: | ||||||
| return $default(_that.id,_that.title,_that.description,_that.language,_that.editedAt,_that.publishedAt,_that.visibility,_that.content,_that.slug,_that.type,_that.meta,_that.viewsUnique,_that.viewsTotal,_that.upvotes,_that.downvotes,_that.repliesCount,_that.pinMode,_that.threadedPostId,_that.threadedPost,_that.repliedPostId,_that.repliedPost,_that.forwardedPostId,_that.forwardedPost,_that.realmId,_that.realm,_that.attachments,_that.publisher,_that.reactionsCount,_that.reactionsMade,_that.reactions,_that.tags,_that.categories,_that.collections,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.isTruncated);case _: | return $default(_that.id,_that.title,_that.description,_that.language,_that.editedAt,_that.publishedAt,_that.visibility,_that.content,_that.slug,_that.type,_that.meta,_that.embedView,_that.viewsUnique,_that.viewsTotal,_that.upvotes,_that.downvotes,_that.repliesCount,_that.pinMode,_that.threadedPostId,_that.threadedPost,_that.repliedPostId,_that.repliedPost,_that.forwardedPostId,_that.forwardedPost,_that.realmId,_that.realm,_that.attachments,_that.publisher,_that.reactionsCount,_that.reactionsMade,_that.reactions,_that.tags,_that.categories,_that.collections,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.isTruncated);case _: | ||||||
|   return orElse(); |   return orElse(); | ||||||
|  |  | ||||||
| } | } | ||||||
| @@ -264,10 +277,10 @@ return $default(_that.id,_that.title,_that.description,_that.language,_that.edit | |||||||
| /// } | /// } | ||||||
| /// ``` | /// ``` | ||||||
|  |  | ||||||
| @optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id,  String? title,  String? description,  String? language,  DateTime? editedAt,  DateTime? publishedAt,  int visibility,  String? content,  String? slug,  int type,  Map<String, dynamic>? meta,  int viewsUnique,  int viewsTotal,  int upvotes,  int downvotes,  int repliesCount,  int? pinMode,  String? threadedPostId,  SnPost? threadedPost,  String? repliedPostId,  SnPost? repliedPost,  String? forwardedPostId,  SnPost? forwardedPost,  String? realmId,  SnRealm? realm,  List<SnCloudFile> attachments,  SnPublisher publisher,  Map<String, int> reactionsCount,  Map<String, bool> reactionsMade,  List<dynamic> reactions,  List<SnPostTag> tags,  List<SnPostCategory> categories,  List<dynamic> collections,  DateTime? createdAt,  DateTime? updatedAt,  DateTime? deletedAt,  bool isTruncated)  $default,) {final _that = this; | @optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id,  String? title,  String? description,  String? language,  DateTime? editedAt,  DateTime? publishedAt,  int visibility,  String? content,  String? slug,  int type,  Map<String, dynamic>? meta,  SnPostEmbedView? embedView,  int viewsUnique,  int viewsTotal,  int upvotes,  int downvotes,  int repliesCount,  int? pinMode,  String? threadedPostId,  SnPost? threadedPost,  String? repliedPostId,  SnPost? repliedPost,  String? forwardedPostId,  SnPost? forwardedPost,  String? realmId,  SnRealm? realm,  List<SnCloudFile> attachments,  SnPublisher publisher,  Map<String, int> reactionsCount,  Map<String, bool> reactionsMade,  List<dynamic> reactions,  List<SnPostTag> tags,  List<SnPostCategory> categories,  List<dynamic> collections,  DateTime? createdAt,  DateTime? updatedAt,  DateTime? deletedAt,  bool isTruncated)  $default,) {final _that = this; | ||||||
| switch (_that) { | switch (_that) { | ||||||
| case _SnPost(): | case _SnPost(): | ||||||
| return $default(_that.id,_that.title,_that.description,_that.language,_that.editedAt,_that.publishedAt,_that.visibility,_that.content,_that.slug,_that.type,_that.meta,_that.viewsUnique,_that.viewsTotal,_that.upvotes,_that.downvotes,_that.repliesCount,_that.pinMode,_that.threadedPostId,_that.threadedPost,_that.repliedPostId,_that.repliedPost,_that.forwardedPostId,_that.forwardedPost,_that.realmId,_that.realm,_that.attachments,_that.publisher,_that.reactionsCount,_that.reactionsMade,_that.reactions,_that.tags,_that.categories,_that.collections,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.isTruncated);} | return $default(_that.id,_that.title,_that.description,_that.language,_that.editedAt,_that.publishedAt,_that.visibility,_that.content,_that.slug,_that.type,_that.meta,_that.embedView,_that.viewsUnique,_that.viewsTotal,_that.upvotes,_that.downvotes,_that.repliesCount,_that.pinMode,_that.threadedPostId,_that.threadedPost,_that.repliedPostId,_that.repliedPost,_that.forwardedPostId,_that.forwardedPost,_that.realmId,_that.realm,_that.attachments,_that.publisher,_that.reactionsCount,_that.reactionsMade,_that.reactions,_that.tags,_that.categories,_that.collections,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.isTruncated);} | ||||||
| } | } | ||||||
| /// A variant of `when` that fallback to returning `null` | /// A variant of `when` that fallback to returning `null` | ||||||
| /// | /// | ||||||
| @@ -281,10 +294,10 @@ return $default(_that.id,_that.title,_that.description,_that.language,_that.edit | |||||||
| /// } | /// } | ||||||
| /// ``` | /// ``` | ||||||
|  |  | ||||||
| @optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id,  String? title,  String? description,  String? language,  DateTime? editedAt,  DateTime? publishedAt,  int visibility,  String? content,  String? slug,  int type,  Map<String, dynamic>? meta,  int viewsUnique,  int viewsTotal,  int upvotes,  int downvotes,  int repliesCount,  int? pinMode,  String? threadedPostId,  SnPost? threadedPost,  String? repliedPostId,  SnPost? repliedPost,  String? forwardedPostId,  SnPost? forwardedPost,  String? realmId,  SnRealm? realm,  List<SnCloudFile> attachments,  SnPublisher publisher,  Map<String, int> reactionsCount,  Map<String, bool> reactionsMade,  List<dynamic> reactions,  List<SnPostTag> tags,  List<SnPostCategory> categories,  List<dynamic> collections,  DateTime? createdAt,  DateTime? updatedAt,  DateTime? deletedAt,  bool isTruncated)?  $default,) {final _that = this; | @optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id,  String? title,  String? description,  String? language,  DateTime? editedAt,  DateTime? publishedAt,  int visibility,  String? content,  String? slug,  int type,  Map<String, dynamic>? meta,  SnPostEmbedView? embedView,  int viewsUnique,  int viewsTotal,  int upvotes,  int downvotes,  int repliesCount,  int? pinMode,  String? threadedPostId,  SnPost? threadedPost,  String? repliedPostId,  SnPost? repliedPost,  String? forwardedPostId,  SnPost? forwardedPost,  String? realmId,  SnRealm? realm,  List<SnCloudFile> attachments,  SnPublisher publisher,  Map<String, int> reactionsCount,  Map<String, bool> reactionsMade,  List<dynamic> reactions,  List<SnPostTag> tags,  List<SnPostCategory> categories,  List<dynamic> collections,  DateTime? createdAt,  DateTime? updatedAt,  DateTime? deletedAt,  bool isTruncated)?  $default,) {final _that = this; | ||||||
| switch (_that) { | switch (_that) { | ||||||
| case _SnPost() when $default != null: | case _SnPost() when $default != null: | ||||||
| return $default(_that.id,_that.title,_that.description,_that.language,_that.editedAt,_that.publishedAt,_that.visibility,_that.content,_that.slug,_that.type,_that.meta,_that.viewsUnique,_that.viewsTotal,_that.upvotes,_that.downvotes,_that.repliesCount,_that.pinMode,_that.threadedPostId,_that.threadedPost,_that.repliedPostId,_that.repliedPost,_that.forwardedPostId,_that.forwardedPost,_that.realmId,_that.realm,_that.attachments,_that.publisher,_that.reactionsCount,_that.reactionsMade,_that.reactions,_that.tags,_that.categories,_that.collections,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.isTruncated);case _: | return $default(_that.id,_that.title,_that.description,_that.language,_that.editedAt,_that.publishedAt,_that.visibility,_that.content,_that.slug,_that.type,_that.meta,_that.embedView,_that.viewsUnique,_that.viewsTotal,_that.upvotes,_that.downvotes,_that.repliesCount,_that.pinMode,_that.threadedPostId,_that.threadedPost,_that.repliedPostId,_that.repliedPost,_that.forwardedPostId,_that.forwardedPost,_that.realmId,_that.realm,_that.attachments,_that.publisher,_that.reactionsCount,_that.reactionsMade,_that.reactions,_that.tags,_that.categories,_that.collections,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.isTruncated);case _: | ||||||
|   return null; |   return null; | ||||||
|  |  | ||||||
| } | } | ||||||
| @@ -296,7 +309,7 @@ return $default(_that.id,_that.title,_that.description,_that.language,_that.edit | |||||||
| @JsonSerializable() | @JsonSerializable() | ||||||
|  |  | ||||||
| class _SnPost implements SnPost { | class _SnPost implements SnPost { | ||||||
|   const _SnPost({required this.id, this.title, this.description, this.language, this.editedAt, this.publishedAt = null, this.visibility = 0, this.content, this.slug, this.type = 0, final  Map<String, dynamic>? meta, this.viewsUnique = 0, this.viewsTotal = 0, this.upvotes = 0, this.downvotes = 0, this.repliesCount = 0, this.pinMode, this.threadedPostId, this.threadedPost, this.repliedPostId, this.repliedPost, this.forwardedPostId, this.forwardedPost, this.realmId, this.realm, final  List<SnCloudFile> attachments = const [], required this.publisher, final  Map<String, int> reactionsCount = const {}, final  Map<String, bool> reactionsMade = const {}, final  List<dynamic> reactions = const [], final  List<SnPostTag> tags = const [], final  List<SnPostCategory> categories = const [], final  List<dynamic> collections = const [], this.createdAt = null, this.updatedAt = null, this.deletedAt, this.isTruncated = false}): _meta = meta,_attachments = attachments,_reactionsCount = reactionsCount,_reactionsMade = reactionsMade,_reactions = reactions,_tags = tags,_categories = categories,_collections = collections; |   const _SnPost({required this.id, this.title, this.description, this.language, this.editedAt, this.publishedAt = null, this.visibility = 0, this.content, this.slug, this.type = 0, final  Map<String, dynamic>? meta, this.embedView, this.viewsUnique = 0, this.viewsTotal = 0, this.upvotes = 0, this.downvotes = 0, this.repliesCount = 0, this.pinMode, this.threadedPostId, this.threadedPost, this.repliedPostId, this.repliedPost, this.forwardedPostId, this.forwardedPost, this.realmId, this.realm, final  List<SnCloudFile> attachments = const [], required this.publisher, final  Map<String, int> reactionsCount = const {}, final  Map<String, bool> reactionsMade = const {}, final  List<dynamic> reactions = const [], final  List<SnPostTag> tags = const [], final  List<SnPostCategory> categories = const [], final  List<dynamic> collections = const [], this.createdAt = null, this.updatedAt = null, this.deletedAt, this.isTruncated = false}): _meta = meta,_attachments = attachments,_reactionsCount = reactionsCount,_reactionsMade = reactionsMade,_reactions = reactions,_tags = tags,_categories = categories,_collections = collections; | ||||||
|   factory _SnPost.fromJson(Map<String, dynamic> json) => _$SnPostFromJson(json); |   factory _SnPost.fromJson(Map<String, dynamic> json) => _$SnPostFromJson(json); | ||||||
|  |  | ||||||
| @override final  String id; | @override final  String id; | ||||||
| @@ -318,6 +331,7 @@ class _SnPost implements SnPost { | |||||||
|   return EqualUnmodifiableMapView(value); |   return EqualUnmodifiableMapView(value); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @override final  SnPostEmbedView? embedView; | ||||||
| @override@JsonKey() final  int viewsUnique; | @override@JsonKey() final  int viewsUnique; | ||||||
| @override@JsonKey() final  int viewsTotal; | @override@JsonKey() final  int viewsTotal; | ||||||
| @override@JsonKey() final  int upvotes; | @override@JsonKey() final  int upvotes; | ||||||
| @@ -400,16 +414,16 @@ Map<String, dynamic> toJson() { | |||||||
|  |  | ||||||
| @override | @override | ||||||
| bool operator ==(Object other) { | bool operator ==(Object other) { | ||||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnPost&&(identical(other.id, id) || other.id == id)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)&&(identical(other.language, language) || other.language == language)&&(identical(other.editedAt, editedAt) || other.editedAt == editedAt)&&(identical(other.publishedAt, publishedAt) || other.publishedAt == publishedAt)&&(identical(other.visibility, visibility) || other.visibility == visibility)&&(identical(other.content, content) || other.content == content)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.type, type) || other.type == type)&&const DeepCollectionEquality().equals(other._meta, _meta)&&(identical(other.viewsUnique, viewsUnique) || other.viewsUnique == viewsUnique)&&(identical(other.viewsTotal, viewsTotal) || other.viewsTotal == viewsTotal)&&(identical(other.upvotes, upvotes) || other.upvotes == upvotes)&&(identical(other.downvotes, downvotes) || other.downvotes == downvotes)&&(identical(other.repliesCount, repliesCount) || other.repliesCount == repliesCount)&&(identical(other.pinMode, pinMode) || other.pinMode == pinMode)&&(identical(other.threadedPostId, threadedPostId) || other.threadedPostId == threadedPostId)&&(identical(other.threadedPost, threadedPost) || other.threadedPost == threadedPost)&&(identical(other.repliedPostId, repliedPostId) || other.repliedPostId == repliedPostId)&&(identical(other.repliedPost, repliedPost) || other.repliedPost == repliedPost)&&(identical(other.forwardedPostId, forwardedPostId) || other.forwardedPostId == forwardedPostId)&&(identical(other.forwardedPost, forwardedPost) || other.forwardedPost == forwardedPost)&&(identical(other.realmId, realmId) || other.realmId == realmId)&&(identical(other.realm, realm) || other.realm == realm)&&const DeepCollectionEquality().equals(other._attachments, _attachments)&&(identical(other.publisher, publisher) || other.publisher == publisher)&&const DeepCollectionEquality().equals(other._reactionsCount, _reactionsCount)&&const DeepCollectionEquality().equals(other._reactionsMade, _reactionsMade)&&const DeepCollectionEquality().equals(other._reactions, _reactions)&&const DeepCollectionEquality().equals(other._tags, _tags)&&const DeepCollectionEquality().equals(other._categories, _categories)&&const DeepCollectionEquality().equals(other._collections, _collections)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.isTruncated, isTruncated) || other.isTruncated == isTruncated)); |   return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnPost&&(identical(other.id, id) || other.id == id)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)&&(identical(other.language, language) || other.language == language)&&(identical(other.editedAt, editedAt) || other.editedAt == editedAt)&&(identical(other.publishedAt, publishedAt) || other.publishedAt == publishedAt)&&(identical(other.visibility, visibility) || other.visibility == visibility)&&(identical(other.content, content) || other.content == content)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.type, type) || other.type == type)&&const DeepCollectionEquality().equals(other._meta, _meta)&&(identical(other.embedView, embedView) || other.embedView == embedView)&&(identical(other.viewsUnique, viewsUnique) || other.viewsUnique == viewsUnique)&&(identical(other.viewsTotal, viewsTotal) || other.viewsTotal == viewsTotal)&&(identical(other.upvotes, upvotes) || other.upvotes == upvotes)&&(identical(other.downvotes, downvotes) || other.downvotes == downvotes)&&(identical(other.repliesCount, repliesCount) || other.repliesCount == repliesCount)&&(identical(other.pinMode, pinMode) || other.pinMode == pinMode)&&(identical(other.threadedPostId, threadedPostId) || other.threadedPostId == threadedPostId)&&(identical(other.threadedPost, threadedPost) || other.threadedPost == threadedPost)&&(identical(other.repliedPostId, repliedPostId) || other.repliedPostId == repliedPostId)&&(identical(other.repliedPost, repliedPost) || other.repliedPost == repliedPost)&&(identical(other.forwardedPostId, forwardedPostId) || other.forwardedPostId == forwardedPostId)&&(identical(other.forwardedPost, forwardedPost) || other.forwardedPost == forwardedPost)&&(identical(other.realmId, realmId) || other.realmId == realmId)&&(identical(other.realm, realm) || other.realm == realm)&&const DeepCollectionEquality().equals(other._attachments, _attachments)&&(identical(other.publisher, publisher) || other.publisher == publisher)&&const DeepCollectionEquality().equals(other._reactionsCount, _reactionsCount)&&const DeepCollectionEquality().equals(other._reactionsMade, _reactionsMade)&&const DeepCollectionEquality().equals(other._reactions, _reactions)&&const DeepCollectionEquality().equals(other._tags, _tags)&&const DeepCollectionEquality().equals(other._categories, _categories)&&const DeepCollectionEquality().equals(other._collections, _collections)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.isTruncated, isTruncated) || other.isTruncated == isTruncated)); | ||||||
| } | } | ||||||
|  |  | ||||||
| @JsonKey(includeFromJson: false, includeToJson: false) | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
| @override | @override | ||||||
| int get hashCode => Object.hashAll([runtimeType,id,title,description,language,editedAt,publishedAt,visibility,content,slug,type,const DeepCollectionEquality().hash(_meta),viewsUnique,viewsTotal,upvotes,downvotes,repliesCount,pinMode,threadedPostId,threadedPost,repliedPostId,repliedPost,forwardedPostId,forwardedPost,realmId,realm,const DeepCollectionEquality().hash(_attachments),publisher,const DeepCollectionEquality().hash(_reactionsCount),const DeepCollectionEquality().hash(_reactionsMade),const DeepCollectionEquality().hash(_reactions),const DeepCollectionEquality().hash(_tags),const DeepCollectionEquality().hash(_categories),const DeepCollectionEquality().hash(_collections),createdAt,updatedAt,deletedAt,isTruncated]); | int get hashCode => Object.hashAll([runtimeType,id,title,description,language,editedAt,publishedAt,visibility,content,slug,type,const DeepCollectionEquality().hash(_meta),embedView,viewsUnique,viewsTotal,upvotes,downvotes,repliesCount,pinMode,threadedPostId,threadedPost,repliedPostId,repliedPost,forwardedPostId,forwardedPost,realmId,realm,const DeepCollectionEquality().hash(_attachments),publisher,const DeepCollectionEquality().hash(_reactionsCount),const DeepCollectionEquality().hash(_reactionsMade),const DeepCollectionEquality().hash(_reactions),const DeepCollectionEquality().hash(_tags),const DeepCollectionEquality().hash(_categories),const DeepCollectionEquality().hash(_collections),createdAt,updatedAt,deletedAt,isTruncated]); | ||||||
|  |  | ||||||
| @override | @override | ||||||
| String toString() { | String toString() { | ||||||
|   return 'SnPost(id: $id, title: $title, description: $description, language: $language, editedAt: $editedAt, publishedAt: $publishedAt, visibility: $visibility, content: $content, slug: $slug, type: $type, meta: $meta, viewsUnique: $viewsUnique, viewsTotal: $viewsTotal, upvotes: $upvotes, downvotes: $downvotes, repliesCount: $repliesCount, pinMode: $pinMode, threadedPostId: $threadedPostId, threadedPost: $threadedPost, repliedPostId: $repliedPostId, repliedPost: $repliedPost, forwardedPostId: $forwardedPostId, forwardedPost: $forwardedPost, realmId: $realmId, realm: $realm, attachments: $attachments, publisher: $publisher, reactionsCount: $reactionsCount, reactionsMade: $reactionsMade, reactions: $reactions, tags: $tags, categories: $categories, collections: $collections, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, isTruncated: $isTruncated)'; |   return 'SnPost(id: $id, title: $title, description: $description, language: $language, editedAt: $editedAt, publishedAt: $publishedAt, visibility: $visibility, content: $content, slug: $slug, type: $type, meta: $meta, embedView: $embedView, viewsUnique: $viewsUnique, viewsTotal: $viewsTotal, upvotes: $upvotes, downvotes: $downvotes, repliesCount: $repliesCount, pinMode: $pinMode, threadedPostId: $threadedPostId, threadedPost: $threadedPost, repliedPostId: $repliedPostId, repliedPost: $repliedPost, forwardedPostId: $forwardedPostId, forwardedPost: $forwardedPost, realmId: $realmId, realm: $realm, attachments: $attachments, publisher: $publisher, reactionsCount: $reactionsCount, reactionsMade: $reactionsMade, reactions: $reactions, tags: $tags, categories: $categories, collections: $collections, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, isTruncated: $isTruncated)'; | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -420,11 +434,11 @@ abstract mixin class _$SnPostCopyWith<$Res> implements $SnPostCopyWith<$Res> { | |||||||
|   factory _$SnPostCopyWith(_SnPost value, $Res Function(_SnPost) _then) = __$SnPostCopyWithImpl; |   factory _$SnPostCopyWith(_SnPost value, $Res Function(_SnPost) _then) = __$SnPostCopyWithImpl; | ||||||
| @override @useResult | @override @useResult | ||||||
| $Res call({ | $Res call({ | ||||||
|  String id, String? title, String? description, String? language, DateTime? editedAt, DateTime? publishedAt, int visibility, String? content, String? slug, int type, Map<String, dynamic>? meta, int viewsUnique, int viewsTotal, int upvotes, int downvotes, int repliesCount, int? pinMode, String? threadedPostId, SnPost? threadedPost, String? repliedPostId, SnPost? repliedPost, String? forwardedPostId, SnPost? forwardedPost, String? realmId, SnRealm? realm, List<SnCloudFile> attachments, SnPublisher publisher, Map<String, int> reactionsCount, Map<String, bool> reactionsMade, List<dynamic> reactions, List<SnPostTag> tags, List<SnPostCategory> categories, List<dynamic> collections, DateTime? createdAt, DateTime? updatedAt, DateTime? deletedAt, bool isTruncated |  String id, String? title, String? description, String? language, DateTime? editedAt, DateTime? publishedAt, int visibility, String? content, String? slug, int type, Map<String, dynamic>? meta, SnPostEmbedView? embedView, int viewsUnique, int viewsTotal, int upvotes, int downvotes, int repliesCount, int? pinMode, String? threadedPostId, SnPost? threadedPost, String? repliedPostId, SnPost? repliedPost, String? forwardedPostId, SnPost? forwardedPost, String? realmId, SnRealm? realm, List<SnCloudFile> attachments, SnPublisher publisher, Map<String, int> reactionsCount, Map<String, bool> reactionsMade, List<dynamic> reactions, List<SnPostTag> tags, List<SnPostCategory> categories, List<dynamic> collections, DateTime? createdAt, DateTime? updatedAt, DateTime? deletedAt, bool isTruncated | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  |  | ||||||
| @override $SnPostCopyWith<$Res>? get threadedPost;@override $SnPostCopyWith<$Res>? get repliedPost;@override $SnPostCopyWith<$Res>? get forwardedPost;@override $SnRealmCopyWith<$Res>? get realm;@override $SnPublisherCopyWith<$Res> get publisher; | @override $SnPostEmbedViewCopyWith<$Res>? get embedView;@override $SnPostCopyWith<$Res>? get threadedPost;@override $SnPostCopyWith<$Res>? get repliedPost;@override $SnPostCopyWith<$Res>? get forwardedPost;@override $SnRealmCopyWith<$Res>? get realm;@override $SnPublisherCopyWith<$Res> get publisher; | ||||||
|  |  | ||||||
| } | } | ||||||
| /// @nodoc | /// @nodoc | ||||||
| @@ -437,7 +451,7 @@ class __$SnPostCopyWithImpl<$Res> | |||||||
|  |  | ||||||
| /// Create a copy of SnPost | /// Create a copy of SnPost | ||||||
| /// with the given fields replaced by the non-null parameter values. | /// with the given fields replaced by the non-null parameter values. | ||||||
| @override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? title = freezed,Object? description = freezed,Object? language = freezed,Object? editedAt = freezed,Object? publishedAt = freezed,Object? visibility = null,Object? content = freezed,Object? slug = freezed,Object? type = null,Object? meta = freezed,Object? viewsUnique = null,Object? viewsTotal = null,Object? upvotes = null,Object? downvotes = null,Object? repliesCount = null,Object? pinMode = freezed,Object? threadedPostId = freezed,Object? threadedPost = freezed,Object? repliedPostId = freezed,Object? repliedPost = freezed,Object? forwardedPostId = freezed,Object? forwardedPost = freezed,Object? realmId = freezed,Object? realm = freezed,Object? attachments = null,Object? publisher = null,Object? reactionsCount = null,Object? reactionsMade = null,Object? reactions = null,Object? tags = null,Object? categories = null,Object? collections = null,Object? createdAt = freezed,Object? updatedAt = freezed,Object? deletedAt = freezed,Object? isTruncated = null,}) { | @override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? title = freezed,Object? description = freezed,Object? language = freezed,Object? editedAt = freezed,Object? publishedAt = freezed,Object? visibility = null,Object? content = freezed,Object? slug = freezed,Object? type = null,Object? meta = freezed,Object? embedView = freezed,Object? viewsUnique = null,Object? viewsTotal = null,Object? upvotes = null,Object? downvotes = null,Object? repliesCount = null,Object? pinMode = freezed,Object? threadedPostId = freezed,Object? threadedPost = freezed,Object? repliedPostId = freezed,Object? repliedPost = freezed,Object? forwardedPostId = freezed,Object? forwardedPost = freezed,Object? realmId = freezed,Object? realm = freezed,Object? attachments = null,Object? publisher = null,Object? reactionsCount = null,Object? reactionsMade = null,Object? reactions = null,Object? tags = null,Object? categories = null,Object? collections = null,Object? createdAt = freezed,Object? updatedAt = freezed,Object? deletedAt = freezed,Object? isTruncated = null,}) { | ||||||
|   return _then(_SnPost( |   return _then(_SnPost( | ||||||
| id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable | id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable | ||||||
| as String,title: freezed == title ? _self.title : title // ignore: cast_nullable_to_non_nullable | as String,title: freezed == title ? _self.title : title // ignore: cast_nullable_to_non_nullable | ||||||
| @@ -450,7 +464,8 @@ as int,content: freezed == content ? _self.content : content // ignore: cast_nul | |||||||
| as String?,slug: freezed == slug ? _self.slug : slug // ignore: cast_nullable_to_non_nullable | as String?,slug: freezed == slug ? _self.slug : slug // ignore: cast_nullable_to_non_nullable | ||||||
| as String?,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable | as String?,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable | ||||||
| as int,meta: freezed == meta ? _self._meta : meta // ignore: cast_nullable_to_non_nullable | as int,meta: freezed == meta ? _self._meta : meta // ignore: cast_nullable_to_non_nullable | ||||||
| as Map<String, dynamic>?,viewsUnique: null == viewsUnique ? _self.viewsUnique : viewsUnique // ignore: cast_nullable_to_non_nullable | as Map<String, dynamic>?,embedView: freezed == embedView ? _self.embedView : embedView // ignore: cast_nullable_to_non_nullable | ||||||
|  | as SnPostEmbedView?,viewsUnique: null == viewsUnique ? _self.viewsUnique : viewsUnique // ignore: cast_nullable_to_non_nullable | ||||||
| as int,viewsTotal: null == viewsTotal ? _self.viewsTotal : viewsTotal // ignore: cast_nullable_to_non_nullable | as int,viewsTotal: null == viewsTotal ? _self.viewsTotal : viewsTotal // ignore: cast_nullable_to_non_nullable | ||||||
| as int,upvotes: null == upvotes ? _self.upvotes : upvotes // ignore: cast_nullable_to_non_nullable | as int,upvotes: null == upvotes ? _self.upvotes : upvotes // ignore: cast_nullable_to_non_nullable | ||||||
| as int,downvotes: null == downvotes ? _self.downvotes : downvotes // ignore: cast_nullable_to_non_nullable | as int,downvotes: null == downvotes ? _self.downvotes : downvotes // ignore: cast_nullable_to_non_nullable | ||||||
| @@ -484,6 +499,18 @@ as bool, | |||||||
| /// with the given fields replaced by the non-null parameter values. | /// with the given fields replaced by the non-null parameter values. | ||||||
| @override | @override | ||||||
| @pragma('vm:prefer-inline') | @pragma('vm:prefer-inline') | ||||||
|  | $SnPostEmbedViewCopyWith<$Res>? get embedView { | ||||||
|  |     if (_self.embedView == null) { | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return $SnPostEmbedViewCopyWith<$Res>(_self.embedView!, (value) { | ||||||
|  |     return _then(_self.copyWith(embedView: value)); | ||||||
|  |   }); | ||||||
|  | }/// Create a copy of SnPost | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
| $SnPostCopyWith<$Res>? get threadedPost { | $SnPostCopyWith<$Res>? get threadedPost { | ||||||
|     if (_self.threadedPost == null) { |     if (_self.threadedPost == null) { | ||||||
|     return null; |     return null; | ||||||
| @@ -1324,6 +1351,269 @@ as int, | |||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | mixin _$SnPostEmbedView { | ||||||
|  |  | ||||||
|  |  String get uri; double? get aspectRatio; PostEmbedViewRenderer get renderer; | ||||||
|  | /// Create a copy of SnPostEmbedView | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
|  | $SnPostEmbedViewCopyWith<SnPostEmbedView> get copyWith => _$SnPostEmbedViewCopyWithImpl<SnPostEmbedView>(this as SnPostEmbedView, _$identity); | ||||||
|  |  | ||||||
|  |   /// Serializes this SnPostEmbedView to a JSON map. | ||||||
|  |   Map<String, dynamic> toJson(); | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | bool operator ==(Object other) { | ||||||
|  |   return identical(this, other) || (other.runtimeType == runtimeType&&other is SnPostEmbedView&&(identical(other.uri, uri) || other.uri == uri)&&(identical(other.aspectRatio, aspectRatio) || other.aspectRatio == aspectRatio)&&(identical(other.renderer, renderer) || other.renderer == renderer)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  | @override | ||||||
|  | int get hashCode => Object.hash(runtimeType,uri,aspectRatio,renderer); | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | String toString() { | ||||||
|  |   return 'SnPostEmbedView(uri: $uri, aspectRatio: $aspectRatio, renderer: $renderer)'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | abstract mixin class $SnPostEmbedViewCopyWith<$Res>  { | ||||||
|  |   factory $SnPostEmbedViewCopyWith(SnPostEmbedView value, $Res Function(SnPostEmbedView) _then) = _$SnPostEmbedViewCopyWithImpl; | ||||||
|  | @useResult | ||||||
|  | $Res call({ | ||||||
|  |  String uri, double? aspectRatio, PostEmbedViewRenderer renderer | ||||||
|  | }); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  | /// @nodoc | ||||||
|  | class _$SnPostEmbedViewCopyWithImpl<$Res> | ||||||
|  |     implements $SnPostEmbedViewCopyWith<$Res> { | ||||||
|  |   _$SnPostEmbedViewCopyWithImpl(this._self, this._then); | ||||||
|  |  | ||||||
|  |   final SnPostEmbedView _self; | ||||||
|  |   final $Res Function(SnPostEmbedView) _then; | ||||||
|  |  | ||||||
|  | /// Create a copy of SnPostEmbedView | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @pragma('vm:prefer-inline') @override $Res call({Object? uri = null,Object? aspectRatio = freezed,Object? renderer = null,}) { | ||||||
|  |   return _then(_self.copyWith( | ||||||
|  | uri: null == uri ? _self.uri : uri // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,aspectRatio: freezed == aspectRatio ? _self.aspectRatio : aspectRatio // ignore: cast_nullable_to_non_nullable | ||||||
|  | as double?,renderer: null == renderer ? _self.renderer : renderer // ignore: cast_nullable_to_non_nullable | ||||||
|  | as PostEmbedViewRenderer, | ||||||
|  |   )); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | /// Adds pattern-matching-related methods to [SnPostEmbedView]. | ||||||
|  | extension SnPostEmbedViewPatterns on SnPostEmbedView { | ||||||
|  | /// A variant of `map` that fallback to returning `orElse`. | ||||||
|  | /// | ||||||
|  | /// It is equivalent to doing: | ||||||
|  | /// ```dart | ||||||
|  | /// switch (sealedClass) { | ||||||
|  | ///   case final Subclass value: | ||||||
|  | ///     return ...; | ||||||
|  | ///   case _: | ||||||
|  | ///     return orElse(); | ||||||
|  | /// } | ||||||
|  | /// ``` | ||||||
|  |  | ||||||
|  | @optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _SnPostEmbedView value)?  $default,{required TResult orElse(),}){ | ||||||
|  | final _that = this; | ||||||
|  | switch (_that) { | ||||||
|  | case _SnPostEmbedView() when $default != null: | ||||||
|  | return $default(_that);case _: | ||||||
|  |   return orElse(); | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  | /// A `switch`-like method, using callbacks. | ||||||
|  | /// | ||||||
|  | /// Callbacks receives the raw object, upcasted. | ||||||
|  | /// It is equivalent to doing: | ||||||
|  | /// ```dart | ||||||
|  | /// switch (sealedClass) { | ||||||
|  | ///   case final Subclass value: | ||||||
|  | ///     return ...; | ||||||
|  | ///   case final Subclass2 value: | ||||||
|  | ///     return ...; | ||||||
|  | /// } | ||||||
|  | /// ``` | ||||||
|  |  | ||||||
|  | @optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _SnPostEmbedView value)  $default,){ | ||||||
|  | final _that = this; | ||||||
|  | switch (_that) { | ||||||
|  | case _SnPostEmbedView(): | ||||||
|  | return $default(_that);} | ||||||
|  | } | ||||||
|  | /// A variant of `map` that fallback to returning `null`. | ||||||
|  | /// | ||||||
|  | /// It is equivalent to doing: | ||||||
|  | /// ```dart | ||||||
|  | /// switch (sealedClass) { | ||||||
|  | ///   case final Subclass value: | ||||||
|  | ///     return ...; | ||||||
|  | ///   case _: | ||||||
|  | ///     return null; | ||||||
|  | /// } | ||||||
|  | /// ``` | ||||||
|  |  | ||||||
|  | @optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _SnPostEmbedView value)?  $default,){ | ||||||
|  | final _that = this; | ||||||
|  | switch (_that) { | ||||||
|  | case _SnPostEmbedView() when $default != null: | ||||||
|  | return $default(_that);case _: | ||||||
|  |   return null; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  | /// A variant of `when` that fallback to an `orElse` callback. | ||||||
|  | /// | ||||||
|  | /// It is equivalent to doing: | ||||||
|  | /// ```dart | ||||||
|  | /// switch (sealedClass) { | ||||||
|  | ///   case Subclass(:final field): | ||||||
|  | ///     return ...; | ||||||
|  | ///   case _: | ||||||
|  | ///     return orElse(); | ||||||
|  | /// } | ||||||
|  | /// ``` | ||||||
|  |  | ||||||
|  | @optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String uri,  double? aspectRatio,  PostEmbedViewRenderer renderer)?  $default,{required TResult orElse(),}) {final _that = this; | ||||||
|  | switch (_that) { | ||||||
|  | case _SnPostEmbedView() when $default != null: | ||||||
|  | return $default(_that.uri,_that.aspectRatio,_that.renderer);case _: | ||||||
|  |   return orElse(); | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  | /// A `switch`-like method, using callbacks. | ||||||
|  | /// | ||||||
|  | /// As opposed to `map`, this offers destructuring. | ||||||
|  | /// It is equivalent to doing: | ||||||
|  | /// ```dart | ||||||
|  | /// switch (sealedClass) { | ||||||
|  | ///   case Subclass(:final field): | ||||||
|  | ///     return ...; | ||||||
|  | ///   case Subclass2(:final field2): | ||||||
|  | ///     return ...; | ||||||
|  | /// } | ||||||
|  | /// ``` | ||||||
|  |  | ||||||
|  | @optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String uri,  double? aspectRatio,  PostEmbedViewRenderer renderer)  $default,) {final _that = this; | ||||||
|  | switch (_that) { | ||||||
|  | case _SnPostEmbedView(): | ||||||
|  | return $default(_that.uri,_that.aspectRatio,_that.renderer);} | ||||||
|  | } | ||||||
|  | /// A variant of `when` that fallback to returning `null` | ||||||
|  | /// | ||||||
|  | /// It is equivalent to doing: | ||||||
|  | /// ```dart | ||||||
|  | /// switch (sealedClass) { | ||||||
|  | ///   case Subclass(:final field): | ||||||
|  | ///     return ...; | ||||||
|  | ///   case _: | ||||||
|  | ///     return null; | ||||||
|  | /// } | ||||||
|  | /// ``` | ||||||
|  |  | ||||||
|  | @optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String uri,  double? aspectRatio,  PostEmbedViewRenderer renderer)?  $default,) {final _that = this; | ||||||
|  | switch (_that) { | ||||||
|  | case _SnPostEmbedView() when $default != null: | ||||||
|  | return $default(_that.uri,_that.aspectRatio,_that.renderer);case _: | ||||||
|  |   return null; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | @JsonSerializable() | ||||||
|  |  | ||||||
|  | class _SnPostEmbedView implements SnPostEmbedView { | ||||||
|  |   const _SnPostEmbedView({required this.uri, this.aspectRatio, this.renderer = PostEmbedViewRenderer.webView}); | ||||||
|  |   factory _SnPostEmbedView.fromJson(Map<String, dynamic> json) => _$SnPostEmbedViewFromJson(json); | ||||||
|  |  | ||||||
|  | @override final  String uri; | ||||||
|  | @override final  double? aspectRatio; | ||||||
|  | @override@JsonKey() final  PostEmbedViewRenderer renderer; | ||||||
|  |  | ||||||
|  | /// Create a copy of SnPostEmbedView | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
|  | _$SnPostEmbedViewCopyWith<_SnPostEmbedView> get copyWith => __$SnPostEmbedViewCopyWithImpl<_SnPostEmbedView>(this, _$identity); | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | Map<String, dynamic> toJson() { | ||||||
|  |   return _$SnPostEmbedViewToJson(this, ); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | bool operator ==(Object other) { | ||||||
|  |   return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnPostEmbedView&&(identical(other.uri, uri) || other.uri == uri)&&(identical(other.aspectRatio, aspectRatio) || other.aspectRatio == aspectRatio)&&(identical(other.renderer, renderer) || other.renderer == renderer)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  | @override | ||||||
|  | int get hashCode => Object.hash(runtimeType,uri,aspectRatio,renderer); | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | String toString() { | ||||||
|  |   return 'SnPostEmbedView(uri: $uri, aspectRatio: $aspectRatio, renderer: $renderer)'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | abstract mixin class _$SnPostEmbedViewCopyWith<$Res> implements $SnPostEmbedViewCopyWith<$Res> { | ||||||
|  |   factory _$SnPostEmbedViewCopyWith(_SnPostEmbedView value, $Res Function(_SnPostEmbedView) _then) = __$SnPostEmbedViewCopyWithImpl; | ||||||
|  | @override @useResult | ||||||
|  | $Res call({ | ||||||
|  |  String uri, double? aspectRatio, PostEmbedViewRenderer renderer | ||||||
|  | }); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  | /// @nodoc | ||||||
|  | class __$SnPostEmbedViewCopyWithImpl<$Res> | ||||||
|  |     implements _$SnPostEmbedViewCopyWith<$Res> { | ||||||
|  |   __$SnPostEmbedViewCopyWithImpl(this._self, this._then); | ||||||
|  |  | ||||||
|  |   final _SnPostEmbedView _self; | ||||||
|  |   final $Res Function(_SnPostEmbedView) _then; | ||||||
|  |  | ||||||
|  | /// Create a copy of SnPostEmbedView | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override @pragma('vm:prefer-inline') $Res call({Object? uri = null,Object? aspectRatio = freezed,Object? renderer = null,}) { | ||||||
|  |   return _then(_SnPostEmbedView( | ||||||
|  | uri: null == uri ? _self.uri : uri // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,aspectRatio: freezed == aspectRatio ? _self.aspectRatio : aspectRatio // ignore: cast_nullable_to_non_nullable | ||||||
|  | as double?,renderer: null == renderer ? _self.renderer : renderer // ignore: cast_nullable_to_non_nullable | ||||||
|  | as PostEmbedViewRenderer, | ||||||
|  |   )); | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
| // dart format on | // dart format on | ||||||
|   | |||||||
| @@ -24,6 +24,12 @@ _SnPost _$SnPostFromJson(Map<String, dynamic> json) => _SnPost( | |||||||
|   slug: json['slug'] as String?, |   slug: json['slug'] as String?, | ||||||
|   type: (json['type'] as num?)?.toInt() ?? 0, |   type: (json['type'] as num?)?.toInt() ?? 0, | ||||||
|   meta: json['meta'] as Map<String, dynamic>?, |   meta: json['meta'] as Map<String, dynamic>?, | ||||||
|  |   embedView: | ||||||
|  |       json['embed_view'] == null | ||||||
|  |           ? null | ||||||
|  |           : SnPostEmbedView.fromJson( | ||||||
|  |             json['embed_view'] as Map<String, dynamic>, | ||||||
|  |           ), | ||||||
|   viewsUnique: (json['views_unique'] as num?)?.toInt() ?? 0, |   viewsUnique: (json['views_unique'] as num?)?.toInt() ?? 0, | ||||||
|   viewsTotal: (json['views_total'] as num?)?.toInt() ?? 0, |   viewsTotal: (json['views_total'] as num?)?.toInt() ?? 0, | ||||||
|   upvotes: (json['upvotes'] as num?)?.toInt() ?? 0, |   upvotes: (json['upvotes'] as num?)?.toInt() ?? 0, | ||||||
| @@ -105,6 +111,7 @@ Map<String, dynamic> _$SnPostToJson(_SnPost instance) => <String, dynamic>{ | |||||||
|   'slug': instance.slug, |   'slug': instance.slug, | ||||||
|   'type': instance.type, |   'type': instance.type, | ||||||
|   'meta': instance.meta, |   'meta': instance.meta, | ||||||
|  |   'embed_view': instance.embedView?.toJson(), | ||||||
|   'views_unique': instance.viewsUnique, |   'views_unique': instance.viewsUnique, | ||||||
|   'views_total': instance.viewsTotal, |   'views_total': instance.viewsTotal, | ||||||
|   'upvotes': instance.upvotes, |   'upvotes': instance.upvotes, | ||||||
| @@ -166,3 +173,24 @@ Map<String, dynamic> _$SnSubscriptionStatusToJson( | |||||||
|   'publisher_id': instance.publisherId, |   'publisher_id': instance.publisherId, | ||||||
|   'publisher_name': instance.publisherName, |   'publisher_name': instance.publisherName, | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | _SnPostEmbedView _$SnPostEmbedViewFromJson(Map<String, dynamic> json) => | ||||||
|  |     _SnPostEmbedView( | ||||||
|  |       uri: json['uri'] as String, | ||||||
|  |       aspectRatio: (json['aspect_ratio'] as num?)?.toDouble(), | ||||||
|  |       renderer: | ||||||
|  |           $enumDecodeNullable( | ||||||
|  |             _$PostEmbedViewRendererEnumMap, | ||||||
|  |             json['renderer'], | ||||||
|  |           ) ?? | ||||||
|  |           PostEmbedViewRenderer.webView, | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  | Map<String, dynamic> _$SnPostEmbedViewToJson(_SnPostEmbedView instance) => | ||||||
|  |     <String, dynamic>{ | ||||||
|  |       'uri': instance.uri, | ||||||
|  |       'aspect_ratio': instance.aspectRatio, | ||||||
|  |       'renderer': _$PostEmbedViewRendererEnumMap[instance.renderer]!, | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  | const _$PostEmbedViewRendererEnumMap = {PostEmbedViewRenderer.webView: 0}; | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| import 'package:easy_localization/easy_localization.dart'; | import 'package:easy_localization/easy_localization.dart'; | ||||||
| import 'package:freezed_annotation/freezed_annotation.dart'; | import 'package:freezed_annotation/freezed_annotation.dart'; | ||||||
| import 'package:island/models/post.dart'; | import 'package:island/models/post.dart'; | ||||||
| import 'package:island/services/text.dart'; | import 'package:island/utils/text.dart'; | ||||||
|  |  | ||||||
| part 'post_category.freezed.dart'; | part 'post_category.freezed.dart'; | ||||||
| part 'post_category.g.dart'; | part 'post_category.g.dart'; | ||||||
|   | |||||||
| @@ -9,6 +9,7 @@ import 'package:freezed_annotation/freezed_annotation.dart'; | |||||||
| import 'package:riverpod_annotation/riverpod_annotation.dart'; | import 'package:riverpod_annotation/riverpod_annotation.dart'; | ||||||
| import 'package:island/pods/network.dart'; | import 'package:island/pods/network.dart'; | ||||||
| import 'package:island/models/chat.dart'; | import 'package:island/models/chat.dart'; | ||||||
|  | import 'package:wakelock_plus/wakelock_plus.dart'; | ||||||
|  |  | ||||||
| part 'call.g.dart'; | part 'call.g.dart'; | ||||||
| part 'call.freezed.dart'; | part 'call.freezed.dart'; | ||||||
| @@ -54,7 +55,7 @@ sealed class CallParticipantLive with _$CallParticipantLive { | |||||||
|   bool get hasAudio => remoteParticipant.hasAudio; |   bool get hasAudio => remoteParticipant.hasAudio; | ||||||
| } | } | ||||||
|  |  | ||||||
| @riverpod | @Riverpod(keepAlive: true) | ||||||
| class CallNotifier extends _$CallNotifier { | class CallNotifier extends _$CallNotifier { | ||||||
|   Room? _room; |   Room? _room; | ||||||
|   LocalParticipant? _localParticipant; |   LocalParticipant? _localParticipant; | ||||||
| @@ -277,14 +278,27 @@ class CallNotifier extends _$CallNotifier { | |||||||
|  |  | ||||||
|         // Listen for connection updates |         // Listen for connection updates | ||||||
|         _room!.addListener(() { |         _room!.addListener(() { | ||||||
|  |           final wasConnected = state.isConnected; | ||||||
|  |           final isNowConnected = | ||||||
|  |               _room!.connectionState == ConnectionState.connected; | ||||||
|           state = state.copyWith( |           state = state.copyWith( | ||||||
|             isConnected: _room!.connectionState == ConnectionState.connected, |             isConnected: isNowConnected, | ||||||
|             isMicrophoneEnabled: _localParticipant!.isMicrophoneEnabled(), |             isMicrophoneEnabled: _localParticipant!.isMicrophoneEnabled(), | ||||||
|             isCameraEnabled: _localParticipant!.isCameraEnabled(), |             isCameraEnabled: _localParticipant!.isCameraEnabled(), | ||||||
|             isScreenSharing: _localParticipant!.isScreenShareEnabled(), |             isScreenSharing: _localParticipant!.isScreenShareEnabled(), | ||||||
|           ); |           ); | ||||||
|  |           // Enable wakelock when call connects | ||||||
|  |           if (!wasConnected && isNowConnected) { | ||||||
|  |             WakelockPlus.enable(); | ||||||
|  |           } | ||||||
|  |           // Disable wakelock when call disconnects | ||||||
|  |           else if (wasConnected && !isNowConnected) { | ||||||
|  |             WakelockPlus.disable(); | ||||||
|  |           } | ||||||
|         }); |         }); | ||||||
|         state = state.copyWith(isConnected: true); |         state = state.copyWith(isConnected: true); | ||||||
|  |         // Enable wakelock when call connects | ||||||
|  |         WakelockPlus.enable(); | ||||||
|       } else { |       } else { | ||||||
|         state = state.copyWith(error: 'Failed to join room'); |         state = state.copyWith(error: 'Failed to join room'); | ||||||
|       } |       } | ||||||
| @@ -344,6 +358,8 @@ class CallNotifier extends _$CallNotifier { | |||||||
|         isCameraEnabled: false, |         isCameraEnabled: false, | ||||||
|         isScreenSharing: false, |         isScreenSharing: false, | ||||||
|       ); |       ); | ||||||
|  |       // Disable wakelock when call disconnects | ||||||
|  |       WakelockPlus.disable(); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -381,5 +397,7 @@ class CallNotifier extends _$CallNotifier { | |||||||
|     _durationTimer?.cancel(); |     _durationTimer?.cancel(); | ||||||
|     _roomId = null; |     _roomId = null; | ||||||
|     participantsVolumes = {}; |     participantsVolumes = {}; | ||||||
|  |     // Disable wakelock when disposing | ||||||
|  |     WakelockPlus.disable(); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -6,22 +6,19 @@ part of 'call.dart'; | |||||||
| // RiverpodGenerator | // RiverpodGenerator | ||||||
| // ************************************************************************** | // ************************************************************************** | ||||||
|  |  | ||||||
| String _$callNotifierHash() => r'18fb807f067eecd3ea42631c1426c3e5f1fb4280'; | String _$callNotifierHash() => r'eb9bd41b97e9b5e9d54007c8327edb6567458846'; | ||||||
|  |  | ||||||
| /// See also [CallNotifier]. | /// See also [CallNotifier]. | ||||||
| @ProviderFor(CallNotifier) | @ProviderFor(CallNotifier) | ||||||
| final callNotifierProvider = | final callNotifierProvider = NotifierProvider<CallNotifier, CallState>.internal( | ||||||
|     AutoDisposeNotifierProvider<CallNotifier, CallState>.internal( |   CallNotifier.new, | ||||||
|       CallNotifier.new, |   name: r'callNotifierProvider', | ||||||
|       name: r'callNotifierProvider', |   debugGetCreateSourceHash: | ||||||
|       debugGetCreateSourceHash: |       const bool.fromEnvironment('dart.vm.product') ? null : _$callNotifierHash, | ||||||
|           const bool.fromEnvironment('dart.vm.product') |   dependencies: null, | ||||||
|               ? null |   allTransitiveDependencies: null, | ||||||
|               : _$callNotifierHash, | ); | ||||||
|       dependencies: null, |  | ||||||
|       allTransitiveDependencies: null, |  | ||||||
|     ); |  | ||||||
|  |  | ||||||
| typedef _$CallNotifier = AutoDisposeNotifier<CallState>; | typedef _$CallNotifier = Notifier<CallState>; | ||||||
| // ignore_for_file: type=lint | // ignore_for_file: type=lint | ||||||
| // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package | // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package | ||||||
|   | |||||||
| @@ -15,10 +15,12 @@ const kNetworkServerStoreKey = 'app_server_url'; | |||||||
|  |  | ||||||
| const kAppbarTransparentStoreKey = 'app_bar_transparent'; | const kAppbarTransparentStoreKey = 'app_bar_transparent'; | ||||||
| const kAppBackgroundStoreKey = 'app_has_background'; | const kAppBackgroundStoreKey = 'app_has_background'; | ||||||
|  | const kAppShowBackgroundImage = 'app_show_background_image'; | ||||||
| const kAppColorSchemeStoreKey = 'app_color_scheme'; | const kAppColorSchemeStoreKey = 'app_color_scheme'; | ||||||
| const kAppNotifyWithHaptic = 'app_notify_with_haptic'; | const kAppNotifyWithHaptic = 'app_notify_with_haptic'; | ||||||
| const kAppCustomFonts = 'app_custom_fonts'; | const kAppCustomFonts = 'app_custom_fonts'; | ||||||
| const kAppAutoTranslate = 'app_auto_translate'; | const kAppAutoTranslate = 'app_auto_translate'; | ||||||
|  | const kAppDataSavingMode = 'app_data_saving_mode'; | ||||||
| const kAppSoundEffects = 'app_sound_effects'; | const kAppSoundEffects = 'app_sound_effects'; | ||||||
| const kAppAprilFoolFeatures = 'app_april_fool_features'; | const kAppAprilFoolFeatures = 'app_april_fool_features'; | ||||||
| const kAppWindowSize = 'app_window_size'; | const kAppWindowSize = 'app_window_size'; | ||||||
| @@ -54,10 +56,12 @@ final serverUrlProvider = Provider<String>((ref) { | |||||||
| sealed class AppSettings with _$AppSettings { | sealed class AppSettings with _$AppSettings { | ||||||
|   const factory AppSettings({ |   const factory AppSettings({ | ||||||
|     required bool autoTranslate, |     required bool autoTranslate, | ||||||
|  |     required bool dataSavingMode, | ||||||
|     required bool soundEffects, |     required bool soundEffects, | ||||||
|     required bool aprilFoolFeatures, |     required bool aprilFoolFeatures, | ||||||
|     required bool enterToSend, |     required bool enterToSend, | ||||||
|     required bool appBarTransparent, |     required bool appBarTransparent, | ||||||
|  |     required bool showBackgroundImage, | ||||||
|     required String? customFonts, |     required String? customFonts, | ||||||
|     required int? appColorScheme, // The color stored via the int type |     required int? appColorScheme, // The color stored via the int type | ||||||
|     required Size? windowSize, // The window size for desktop platforms |     required Size? windowSize, // The window size for desktop platforms | ||||||
| @@ -71,10 +75,12 @@ class AppSettingsNotifier extends _$AppSettingsNotifier { | |||||||
|     final prefs = ref.watch(sharedPreferencesProvider); |     final prefs = ref.watch(sharedPreferencesProvider); | ||||||
|     return AppSettings( |     return AppSettings( | ||||||
|       autoTranslate: prefs.getBool(kAppAutoTranslate) ?? false, |       autoTranslate: prefs.getBool(kAppAutoTranslate) ?? false, | ||||||
|  |       dataSavingMode: prefs.getBool(kAppDataSavingMode) ?? false, | ||||||
|       soundEffects: prefs.getBool(kAppSoundEffects) ?? true, |       soundEffects: prefs.getBool(kAppSoundEffects) ?? true, | ||||||
|       aprilFoolFeatures: prefs.getBool(kAppAprilFoolFeatures) ?? true, |       aprilFoolFeatures: prefs.getBool(kAppAprilFoolFeatures) ?? true, | ||||||
|       enterToSend: prefs.getBool(kAppEnterToSend) ?? true, |       enterToSend: prefs.getBool(kAppEnterToSend) ?? true, | ||||||
|       appBarTransparent: prefs.getBool(kAppbarTransparentStoreKey) ?? false, |       appBarTransparent: prefs.getBool(kAppbarTransparentStoreKey) ?? false, | ||||||
|  |       showBackgroundImage: prefs.getBool(kAppShowBackgroundImage) ?? true, | ||||||
|       customFonts: prefs.getString(kAppCustomFonts), |       customFonts: prefs.getString(kAppCustomFonts), | ||||||
|       appColorScheme: prefs.getInt(kAppColorSchemeStoreKey), |       appColorScheme: prefs.getInt(kAppColorSchemeStoreKey), | ||||||
|       windowSize: _getWindowSizeFromPrefs(prefs), |       windowSize: _getWindowSizeFromPrefs(prefs), | ||||||
| @@ -104,6 +110,12 @@ class AppSettingsNotifier extends _$AppSettingsNotifier { | |||||||
|     state = state.copyWith(autoTranslate: value); |     state = state.copyWith(autoTranslate: value); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   void setDataSavingMode(bool value){ | ||||||
|  |     final prefs = ref.read(sharedPreferencesProvider); | ||||||
|  |     prefs.setBool(kAppDataSavingMode, value); | ||||||
|  |     state = state.copyWith(dataSavingMode: value); | ||||||
|  |   } | ||||||
|  |  | ||||||
|   void setSoundEffects(bool value) { |   void setSoundEffects(bool value) { | ||||||
|     final prefs = ref.read(sharedPreferencesProvider); |     final prefs = ref.read(sharedPreferencesProvider); | ||||||
|     prefs.setBool(kAppSoundEffects, value); |     prefs.setBool(kAppSoundEffects, value); | ||||||
| @@ -129,6 +141,12 @@ class AppSettingsNotifier extends _$AppSettingsNotifier { | |||||||
|     ref.read(themeProvider.notifier).reloadTheme(); |     ref.read(themeProvider.notifier).reloadTheme(); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   void setShowBackgroundImage(bool value) { | ||||||
|  |     final prefs = ref.read(sharedPreferencesProvider); | ||||||
|  |     prefs.setBool(kAppShowBackgroundImage, value); | ||||||
|  |     state = state.copyWith(showBackgroundImage: value); | ||||||
|  |   } | ||||||
|  |  | ||||||
|   void setCustomFonts(String? value) { |   void setCustomFonts(String? value) { | ||||||
|     final prefs = ref.read(sharedPreferencesProvider); |     final prefs = ref.read(sharedPreferencesProvider); | ||||||
|     prefs.setString(kAppCustomFonts, value ?? ''); |     prefs.setString(kAppCustomFonts, value ?? ''); | ||||||
|   | |||||||
| @@ -14,7 +14,7 @@ T _$identity<T>(T value) => value; | |||||||
| /// @nodoc | /// @nodoc | ||||||
| mixin _$AppSettings { | mixin _$AppSettings { | ||||||
|  |  | ||||||
|  bool get autoTranslate; bool get soundEffects; bool get aprilFoolFeatures; bool get enterToSend; bool get appBarTransparent; String? get customFonts; int? get appColorScheme;// The color stored via the int type |  bool get autoTranslate; bool get dataSavingMode; bool get soundEffects; bool get aprilFoolFeatures; bool get enterToSend; bool get appBarTransparent; bool get showBackgroundImage; String? get customFonts; int? get appColorScheme;// The color stored via the int type | ||||||
|  Size? get windowSize; |  Size? get windowSize; | ||||||
| /// Create a copy of AppSettings | /// Create a copy of AppSettings | ||||||
| /// with the given fields replaced by the non-null parameter values. | /// with the given fields replaced by the non-null parameter values. | ||||||
| @@ -26,16 +26,16 @@ $AppSettingsCopyWith<AppSettings> get copyWith => _$AppSettingsCopyWithImpl<AppS | |||||||
|  |  | ||||||
| @override | @override | ||||||
| bool operator ==(Object other) { | bool operator ==(Object other) { | ||||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is AppSettings&&(identical(other.autoTranslate, autoTranslate) || other.autoTranslate == autoTranslate)&&(identical(other.soundEffects, soundEffects) || other.soundEffects == soundEffects)&&(identical(other.aprilFoolFeatures, aprilFoolFeatures) || other.aprilFoolFeatures == aprilFoolFeatures)&&(identical(other.enterToSend, enterToSend) || other.enterToSend == enterToSend)&&(identical(other.appBarTransparent, appBarTransparent) || other.appBarTransparent == appBarTransparent)&&(identical(other.customFonts, customFonts) || other.customFonts == customFonts)&&(identical(other.appColorScheme, appColorScheme) || other.appColorScheme == appColorScheme)&&(identical(other.windowSize, windowSize) || other.windowSize == windowSize)); |   return identical(this, other) || (other.runtimeType == runtimeType&&other is AppSettings&&(identical(other.autoTranslate, autoTranslate) || other.autoTranslate == autoTranslate)&&(identical(other.dataSavingMode, dataSavingMode) || other.dataSavingMode == dataSavingMode)&&(identical(other.soundEffects, soundEffects) || other.soundEffects == soundEffects)&&(identical(other.aprilFoolFeatures, aprilFoolFeatures) || other.aprilFoolFeatures == aprilFoolFeatures)&&(identical(other.enterToSend, enterToSend) || other.enterToSend == enterToSend)&&(identical(other.appBarTransparent, appBarTransparent) || other.appBarTransparent == appBarTransparent)&&(identical(other.showBackgroundImage, showBackgroundImage) || other.showBackgroundImage == showBackgroundImage)&&(identical(other.customFonts, customFonts) || other.customFonts == customFonts)&&(identical(other.appColorScheme, appColorScheme) || other.appColorScheme == appColorScheme)&&(identical(other.windowSize, windowSize) || other.windowSize == windowSize)); | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| @override | @override | ||||||
| int get hashCode => Object.hash(runtimeType,autoTranslate,soundEffects,aprilFoolFeatures,enterToSend,appBarTransparent,customFonts,appColorScheme,windowSize); | int get hashCode => Object.hash(runtimeType,autoTranslate,dataSavingMode,soundEffects,aprilFoolFeatures,enterToSend,appBarTransparent,showBackgroundImage,customFonts,appColorScheme,windowSize); | ||||||
|  |  | ||||||
| @override | @override | ||||||
| String toString() { | String toString() { | ||||||
|   return 'AppSettings(autoTranslate: $autoTranslate, soundEffects: $soundEffects, aprilFoolFeatures: $aprilFoolFeatures, enterToSend: $enterToSend, appBarTransparent: $appBarTransparent, customFonts: $customFonts, appColorScheme: $appColorScheme, windowSize: $windowSize)'; |   return 'AppSettings(autoTranslate: $autoTranslate, dataSavingMode: $dataSavingMode, soundEffects: $soundEffects, aprilFoolFeatures: $aprilFoolFeatures, enterToSend: $enterToSend, appBarTransparent: $appBarTransparent, showBackgroundImage: $showBackgroundImage, customFonts: $customFonts, appColorScheme: $appColorScheme, windowSize: $windowSize)'; | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -46,7 +46,7 @@ abstract mixin class $AppSettingsCopyWith<$Res>  { | |||||||
|   factory $AppSettingsCopyWith(AppSettings value, $Res Function(AppSettings) _then) = _$AppSettingsCopyWithImpl; |   factory $AppSettingsCopyWith(AppSettings value, $Res Function(AppSettings) _then) = _$AppSettingsCopyWithImpl; | ||||||
| @useResult | @useResult | ||||||
| $Res call({ | $Res call({ | ||||||
|  bool autoTranslate, bool soundEffects, bool aprilFoolFeatures, bool enterToSend, bool appBarTransparent, String? customFonts, int? appColorScheme, Size? windowSize |  bool autoTranslate, bool dataSavingMode, bool soundEffects, bool aprilFoolFeatures, bool enterToSend, bool appBarTransparent, bool showBackgroundImage, String? customFonts, int? appColorScheme, Size? windowSize | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -63,13 +63,15 @@ class _$AppSettingsCopyWithImpl<$Res> | |||||||
|  |  | ||||||
| /// Create a copy of AppSettings | /// Create a copy of AppSettings | ||||||
| /// with the given fields replaced by the non-null parameter values. | /// with the given fields replaced by the non-null parameter values. | ||||||
| @pragma('vm:prefer-inline') @override $Res call({Object? autoTranslate = null,Object? soundEffects = null,Object? aprilFoolFeatures = null,Object? enterToSend = null,Object? appBarTransparent = null,Object? customFonts = freezed,Object? appColorScheme = freezed,Object? windowSize = freezed,}) { | @pragma('vm:prefer-inline') @override $Res call({Object? autoTranslate = null,Object? dataSavingMode = null,Object? soundEffects = null,Object? aprilFoolFeatures = null,Object? enterToSend = null,Object? appBarTransparent = null,Object? showBackgroundImage = null,Object? customFonts = freezed,Object? appColorScheme = freezed,Object? windowSize = freezed,}) { | ||||||
|   return _then(_self.copyWith( |   return _then(_self.copyWith( | ||||||
| autoTranslate: null == autoTranslate ? _self.autoTranslate : autoTranslate // ignore: cast_nullable_to_non_nullable | autoTranslate: null == autoTranslate ? _self.autoTranslate : autoTranslate // ignore: cast_nullable_to_non_nullable | ||||||
|  | as bool,dataSavingMode: null == dataSavingMode ? _self.dataSavingMode : dataSavingMode // ignore: cast_nullable_to_non_nullable | ||||||
| as bool,soundEffects: null == soundEffects ? _self.soundEffects : soundEffects // ignore: cast_nullable_to_non_nullable | as bool,soundEffects: null == soundEffects ? _self.soundEffects : soundEffects // ignore: cast_nullable_to_non_nullable | ||||||
| as bool,aprilFoolFeatures: null == aprilFoolFeatures ? _self.aprilFoolFeatures : aprilFoolFeatures // ignore: cast_nullable_to_non_nullable | as bool,aprilFoolFeatures: null == aprilFoolFeatures ? _self.aprilFoolFeatures : aprilFoolFeatures // ignore: cast_nullable_to_non_nullable | ||||||
| as bool,enterToSend: null == enterToSend ? _self.enterToSend : enterToSend // ignore: cast_nullable_to_non_nullable | as bool,enterToSend: null == enterToSend ? _self.enterToSend : enterToSend // ignore: cast_nullable_to_non_nullable | ||||||
| as bool,appBarTransparent: null == appBarTransparent ? _self.appBarTransparent : appBarTransparent // ignore: cast_nullable_to_non_nullable | as bool,appBarTransparent: null == appBarTransparent ? _self.appBarTransparent : appBarTransparent // ignore: cast_nullable_to_non_nullable | ||||||
|  | as bool,showBackgroundImage: null == showBackgroundImage ? _self.showBackgroundImage : showBackgroundImage // ignore: cast_nullable_to_non_nullable | ||||||
| as bool,customFonts: freezed == customFonts ? _self.customFonts : customFonts // ignore: cast_nullable_to_non_nullable | as bool,customFonts: freezed == customFonts ? _self.customFonts : customFonts // ignore: cast_nullable_to_non_nullable | ||||||
| as String?,appColorScheme: freezed == appColorScheme ? _self.appColorScheme : appColorScheme // ignore: cast_nullable_to_non_nullable | as String?,appColorScheme: freezed == appColorScheme ? _self.appColorScheme : appColorScheme // ignore: cast_nullable_to_non_nullable | ||||||
| as int?,windowSize: freezed == windowSize ? _self.windowSize : windowSize // ignore: cast_nullable_to_non_nullable | as int?,windowSize: freezed == windowSize ? _self.windowSize : windowSize // ignore: cast_nullable_to_non_nullable | ||||||
| @@ -155,10 +157,10 @@ return $default(_that);case _: | |||||||
| /// } | /// } | ||||||
| /// ``` | /// ``` | ||||||
|  |  | ||||||
| @optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( bool autoTranslate,  bool soundEffects,  bool aprilFoolFeatures,  bool enterToSend,  bool appBarTransparent,  String? customFonts,  int? appColorScheme,  Size? windowSize)?  $default,{required TResult orElse(),}) {final _that = this; | @optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( bool autoTranslate,  bool dataSavingMode,  bool soundEffects,  bool aprilFoolFeatures,  bool enterToSend,  bool appBarTransparent,  bool showBackgroundImage,  String? customFonts,  int? appColorScheme,  Size? windowSize)?  $default,{required TResult orElse(),}) {final _that = this; | ||||||
| switch (_that) { | switch (_that) { | ||||||
| case _AppSettings() when $default != null: | case _AppSettings() when $default != null: | ||||||
| return $default(_that.autoTranslate,_that.soundEffects,_that.aprilFoolFeatures,_that.enterToSend,_that.appBarTransparent,_that.customFonts,_that.appColorScheme,_that.windowSize);case _: | return $default(_that.autoTranslate,_that.dataSavingMode,_that.soundEffects,_that.aprilFoolFeatures,_that.enterToSend,_that.appBarTransparent,_that.showBackgroundImage,_that.customFonts,_that.appColorScheme,_that.windowSize);case _: | ||||||
|   return orElse(); |   return orElse(); | ||||||
|  |  | ||||||
| } | } | ||||||
| @@ -176,10 +178,10 @@ return $default(_that.autoTranslate,_that.soundEffects,_that.aprilFoolFeatures,_ | |||||||
| /// } | /// } | ||||||
| /// ``` | /// ``` | ||||||
|  |  | ||||||
| @optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( bool autoTranslate,  bool soundEffects,  bool aprilFoolFeatures,  bool enterToSend,  bool appBarTransparent,  String? customFonts,  int? appColorScheme,  Size? windowSize)  $default,) {final _that = this; | @optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( bool autoTranslate,  bool dataSavingMode,  bool soundEffects,  bool aprilFoolFeatures,  bool enterToSend,  bool appBarTransparent,  bool showBackgroundImage,  String? customFonts,  int? appColorScheme,  Size? windowSize)  $default,) {final _that = this; | ||||||
| switch (_that) { | switch (_that) { | ||||||
| case _AppSettings(): | case _AppSettings(): | ||||||
| return $default(_that.autoTranslate,_that.soundEffects,_that.aprilFoolFeatures,_that.enterToSend,_that.appBarTransparent,_that.customFonts,_that.appColorScheme,_that.windowSize);} | return $default(_that.autoTranslate,_that.dataSavingMode,_that.soundEffects,_that.aprilFoolFeatures,_that.enterToSend,_that.appBarTransparent,_that.showBackgroundImage,_that.customFonts,_that.appColorScheme,_that.windowSize);} | ||||||
| } | } | ||||||
| /// A variant of `when` that fallback to returning `null` | /// A variant of `when` that fallback to returning `null` | ||||||
| /// | /// | ||||||
| @@ -193,10 +195,10 @@ return $default(_that.autoTranslate,_that.soundEffects,_that.aprilFoolFeatures,_ | |||||||
| /// } | /// } | ||||||
| /// ``` | /// ``` | ||||||
|  |  | ||||||
| @optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( bool autoTranslate,  bool soundEffects,  bool aprilFoolFeatures,  bool enterToSend,  bool appBarTransparent,  String? customFonts,  int? appColorScheme,  Size? windowSize)?  $default,) {final _that = this; | @optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( bool autoTranslate,  bool dataSavingMode,  bool soundEffects,  bool aprilFoolFeatures,  bool enterToSend,  bool appBarTransparent,  bool showBackgroundImage,  String? customFonts,  int? appColorScheme,  Size? windowSize)?  $default,) {final _that = this; | ||||||
| switch (_that) { | switch (_that) { | ||||||
| case _AppSettings() when $default != null: | case _AppSettings() when $default != null: | ||||||
| return $default(_that.autoTranslate,_that.soundEffects,_that.aprilFoolFeatures,_that.enterToSend,_that.appBarTransparent,_that.customFonts,_that.appColorScheme,_that.windowSize);case _: | return $default(_that.autoTranslate,_that.dataSavingMode,_that.soundEffects,_that.aprilFoolFeatures,_that.enterToSend,_that.appBarTransparent,_that.showBackgroundImage,_that.customFonts,_that.appColorScheme,_that.windowSize);case _: | ||||||
|   return null; |   return null; | ||||||
|  |  | ||||||
| } | } | ||||||
| @@ -208,14 +210,16 @@ return $default(_that.autoTranslate,_that.soundEffects,_that.aprilFoolFeatures,_ | |||||||
|  |  | ||||||
|  |  | ||||||
| class _AppSettings implements AppSettings { | class _AppSettings implements AppSettings { | ||||||
|   const _AppSettings({required this.autoTranslate, required this.soundEffects, required this.aprilFoolFeatures, required this.enterToSend, required this.appBarTransparent, required this.customFonts, required this.appColorScheme, required this.windowSize}); |   const _AppSettings({required this.autoTranslate, required this.dataSavingMode, required this.soundEffects, required this.aprilFoolFeatures, required this.enterToSend, required this.appBarTransparent, required this.showBackgroundImage, required this.customFonts, required this.appColorScheme, required this.windowSize}); | ||||||
|    |    | ||||||
|  |  | ||||||
| @override final  bool autoTranslate; | @override final  bool autoTranslate; | ||||||
|  | @override final  bool dataSavingMode; | ||||||
| @override final  bool soundEffects; | @override final  bool soundEffects; | ||||||
| @override final  bool aprilFoolFeatures; | @override final  bool aprilFoolFeatures; | ||||||
| @override final  bool enterToSend; | @override final  bool enterToSend; | ||||||
| @override final  bool appBarTransparent; | @override final  bool appBarTransparent; | ||||||
|  | @override final  bool showBackgroundImage; | ||||||
| @override final  String? customFonts; | @override final  String? customFonts; | ||||||
| @override final  int? appColorScheme; | @override final  int? appColorScheme; | ||||||
| // The color stored via the int type | // The color stored via the int type | ||||||
| @@ -231,16 +235,16 @@ _$AppSettingsCopyWith<_AppSettings> get copyWith => __$AppSettingsCopyWithImpl<_ | |||||||
|  |  | ||||||
| @override | @override | ||||||
| bool operator ==(Object other) { | bool operator ==(Object other) { | ||||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is _AppSettings&&(identical(other.autoTranslate, autoTranslate) || other.autoTranslate == autoTranslate)&&(identical(other.soundEffects, soundEffects) || other.soundEffects == soundEffects)&&(identical(other.aprilFoolFeatures, aprilFoolFeatures) || other.aprilFoolFeatures == aprilFoolFeatures)&&(identical(other.enterToSend, enterToSend) || other.enterToSend == enterToSend)&&(identical(other.appBarTransparent, appBarTransparent) || other.appBarTransparent == appBarTransparent)&&(identical(other.customFonts, customFonts) || other.customFonts == customFonts)&&(identical(other.appColorScheme, appColorScheme) || other.appColorScheme == appColorScheme)&&(identical(other.windowSize, windowSize) || other.windowSize == windowSize)); |   return identical(this, other) || (other.runtimeType == runtimeType&&other is _AppSettings&&(identical(other.autoTranslate, autoTranslate) || other.autoTranslate == autoTranslate)&&(identical(other.dataSavingMode, dataSavingMode) || other.dataSavingMode == dataSavingMode)&&(identical(other.soundEffects, soundEffects) || other.soundEffects == soundEffects)&&(identical(other.aprilFoolFeatures, aprilFoolFeatures) || other.aprilFoolFeatures == aprilFoolFeatures)&&(identical(other.enterToSend, enterToSend) || other.enterToSend == enterToSend)&&(identical(other.appBarTransparent, appBarTransparent) || other.appBarTransparent == appBarTransparent)&&(identical(other.showBackgroundImage, showBackgroundImage) || other.showBackgroundImage == showBackgroundImage)&&(identical(other.customFonts, customFonts) || other.customFonts == customFonts)&&(identical(other.appColorScheme, appColorScheme) || other.appColorScheme == appColorScheme)&&(identical(other.windowSize, windowSize) || other.windowSize == windowSize)); | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| @override | @override | ||||||
| int get hashCode => Object.hash(runtimeType,autoTranslate,soundEffects,aprilFoolFeatures,enterToSend,appBarTransparent,customFonts,appColorScheme,windowSize); | int get hashCode => Object.hash(runtimeType,autoTranslate,dataSavingMode,soundEffects,aprilFoolFeatures,enterToSend,appBarTransparent,showBackgroundImage,customFonts,appColorScheme,windowSize); | ||||||
|  |  | ||||||
| @override | @override | ||||||
| String toString() { | String toString() { | ||||||
|   return 'AppSettings(autoTranslate: $autoTranslate, soundEffects: $soundEffects, aprilFoolFeatures: $aprilFoolFeatures, enterToSend: $enterToSend, appBarTransparent: $appBarTransparent, customFonts: $customFonts, appColorScheme: $appColorScheme, windowSize: $windowSize)'; |   return 'AppSettings(autoTranslate: $autoTranslate, dataSavingMode: $dataSavingMode, soundEffects: $soundEffects, aprilFoolFeatures: $aprilFoolFeatures, enterToSend: $enterToSend, appBarTransparent: $appBarTransparent, showBackgroundImage: $showBackgroundImage, customFonts: $customFonts, appColorScheme: $appColorScheme, windowSize: $windowSize)'; | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -251,7 +255,7 @@ abstract mixin class _$AppSettingsCopyWith<$Res> implements $AppSettingsCopyWith | |||||||
|   factory _$AppSettingsCopyWith(_AppSettings value, $Res Function(_AppSettings) _then) = __$AppSettingsCopyWithImpl; |   factory _$AppSettingsCopyWith(_AppSettings value, $Res Function(_AppSettings) _then) = __$AppSettingsCopyWithImpl; | ||||||
| @override @useResult | @override @useResult | ||||||
| $Res call({ | $Res call({ | ||||||
|  bool autoTranslate, bool soundEffects, bool aprilFoolFeatures, bool enterToSend, bool appBarTransparent, String? customFonts, int? appColorScheme, Size? windowSize |  bool autoTranslate, bool dataSavingMode, bool soundEffects, bool aprilFoolFeatures, bool enterToSend, bool appBarTransparent, bool showBackgroundImage, String? customFonts, int? appColorScheme, Size? windowSize | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -268,13 +272,15 @@ class __$AppSettingsCopyWithImpl<$Res> | |||||||
|  |  | ||||||
| /// Create a copy of AppSettings | /// Create a copy of AppSettings | ||||||
| /// with the given fields replaced by the non-null parameter values. | /// with the given fields replaced by the non-null parameter values. | ||||||
| @override @pragma('vm:prefer-inline') $Res call({Object? autoTranslate = null,Object? soundEffects = null,Object? aprilFoolFeatures = null,Object? enterToSend = null,Object? appBarTransparent = null,Object? customFonts = freezed,Object? appColorScheme = freezed,Object? windowSize = freezed,}) { | @override @pragma('vm:prefer-inline') $Res call({Object? autoTranslate = null,Object? dataSavingMode = null,Object? soundEffects = null,Object? aprilFoolFeatures = null,Object? enterToSend = null,Object? appBarTransparent = null,Object? showBackgroundImage = null,Object? customFonts = freezed,Object? appColorScheme = freezed,Object? windowSize = freezed,}) { | ||||||
|   return _then(_AppSettings( |   return _then(_AppSettings( | ||||||
| autoTranslate: null == autoTranslate ? _self.autoTranslate : autoTranslate // ignore: cast_nullable_to_non_nullable | autoTranslate: null == autoTranslate ? _self.autoTranslate : autoTranslate // ignore: cast_nullable_to_non_nullable | ||||||
|  | as bool,dataSavingMode: null == dataSavingMode ? _self.dataSavingMode : dataSavingMode // ignore: cast_nullable_to_non_nullable | ||||||
| as bool,soundEffects: null == soundEffects ? _self.soundEffects : soundEffects // ignore: cast_nullable_to_non_nullable | as bool,soundEffects: null == soundEffects ? _self.soundEffects : soundEffects // ignore: cast_nullable_to_non_nullable | ||||||
| as bool,aprilFoolFeatures: null == aprilFoolFeatures ? _self.aprilFoolFeatures : aprilFoolFeatures // ignore: cast_nullable_to_non_nullable | as bool,aprilFoolFeatures: null == aprilFoolFeatures ? _self.aprilFoolFeatures : aprilFoolFeatures // ignore: cast_nullable_to_non_nullable | ||||||
| as bool,enterToSend: null == enterToSend ? _self.enterToSend : enterToSend // ignore: cast_nullable_to_non_nullable | as bool,enterToSend: null == enterToSend ? _self.enterToSend : enterToSend // ignore: cast_nullable_to_non_nullable | ||||||
| as bool,appBarTransparent: null == appBarTransparent ? _self.appBarTransparent : appBarTransparent // ignore: cast_nullable_to_non_nullable | as bool,appBarTransparent: null == appBarTransparent ? _self.appBarTransparent : appBarTransparent // ignore: cast_nullable_to_non_nullable | ||||||
|  | as bool,showBackgroundImage: null == showBackgroundImage ? _self.showBackgroundImage : showBackgroundImage // ignore: cast_nullable_to_non_nullable | ||||||
| as bool,customFonts: freezed == customFonts ? _self.customFonts : customFonts // ignore: cast_nullable_to_non_nullable | as bool,customFonts: freezed == customFonts ? _self.customFonts : customFonts // ignore: cast_nullable_to_non_nullable | ||||||
| as String?,appColorScheme: freezed == appColorScheme ? _self.appColorScheme : appColorScheme // ignore: cast_nullable_to_non_nullable | as String?,appColorScheme: freezed == appColorScheme ? _self.appColorScheme : appColorScheme // ignore: cast_nullable_to_non_nullable | ||||||
| as int?,windowSize: freezed == windowSize ? _self.windowSize : windowSize // ignore: cast_nullable_to_non_nullable | as int?,windowSize: freezed == windowSize ? _self.windowSize : windowSize // ignore: cast_nullable_to_non_nullable | ||||||
|   | |||||||
| @@ -7,7 +7,7 @@ part of 'config.dart'; | |||||||
| // ************************************************************************** | // ************************************************************************** | ||||||
|  |  | ||||||
| String _$appSettingsNotifierHash() => | String _$appSettingsNotifierHash() => | ||||||
|     r'c4f40a3bc4311c6360c2b5e44f8df5e5d7c1bd75'; |     r'cd18bff2614a94e3523634e6c577cefad0367eba'; | ||||||
|  |  | ||||||
| /// See also [AppSettingsNotifier]. | /// See also [AppSettingsNotifier]. | ||||||
| @ProviderFor(AppSettingsNotifier) | @ProviderFor(AppSettingsNotifier) | ||||||
|   | |||||||
| @@ -1,11 +1,9 @@ | |||||||
| import 'dart:convert'; | import 'dart:convert'; | ||||||
| import 'dart:developer'; |  | ||||||
|  |  | ||||||
| import 'package:freezed_annotation/freezed_annotation.dart'; | import 'package:freezed_annotation/freezed_annotation.dart'; | ||||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
| import 'package:island/pods/network.dart'; | import 'package:island/pods/network.dart'; | ||||||
| import 'package:riverpod_annotation/riverpod_annotation.dart'; | import 'package:riverpod_annotation/riverpod_annotation.dart'; | ||||||
| import 'package:flutter_langdetect/flutter_langdetect.dart' as langdetect; |  | ||||||
|  |  | ||||||
| part 'translate.freezed.dart'; | part 'translate.freezed.dart'; | ||||||
| part 'translate.g.dart'; | part 'translate.g.dart'; | ||||||
| @@ -29,10 +27,17 @@ Future<String> translateString(Ref ref, TranslateQuery query) async { | |||||||
|  |  | ||||||
| @riverpod | @riverpod | ||||||
| String? detectStringLanguage(Ref ref, String text) { | String? detectStringLanguage(Ref ref, String text) { | ||||||
|   try { |   bool isChinese(String text) { | ||||||
|     return langdetect.detectLangs(text).firstOrNull?.lang; |     final chineseRegex = RegExp(r'[\u4e00-\u9fff]'); | ||||||
|   } catch (err) { |     return chineseRegex.hasMatch(text); | ||||||
|     log('[Language] Unable to detect text\'s language: $text'); |  | ||||||
|     return null; |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   bool isEnglish(String text) { | ||||||
|  |     final englishRegex = RegExp(r'[a-zA-Z]'); | ||||||
|  |     return englishRegex.hasMatch(text) && !isChinese(text); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (isChinese(text)) return "zh"; | ||||||
|  |   if (isEnglish(text)) return "en"; | ||||||
|  |   return null; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -149,7 +149,7 @@ class _TranslateStringProviderElement | |||||||
| } | } | ||||||
|  |  | ||||||
| String _$detectStringLanguageHash() => | String _$detectStringLanguageHash() => | ||||||
|     r'697b68464b3d00927cc43ccc1ba8ba93f2a470ed'; |     r'24fbf52edbbffcc8dc4f09f7206f82d69728e703'; | ||||||
|  |  | ||||||
| /// See also [detectStringLanguage]. | /// See also [detectStringLanguage]. | ||||||
| @ProviderFor(detectStringLanguage) | @ProviderFor(detectStringLanguage) | ||||||
|   | |||||||
| @@ -68,6 +68,7 @@ class AccountScreen extends HookConsumerWidget { | |||||||
|       body: SingleChildScrollView( |       body: SingleChildScrollView( | ||||||
|         padding: getTabbedPadding(context), |         padding: getTabbedPadding(context), | ||||||
|         child: Column( |         child: Column( | ||||||
|  |           spacing: 4, | ||||||
|           children: <Widget>[ |           children: <Widget>[ | ||||||
|             Card( |             Card( | ||||||
|               child: Column( |               child: Column( | ||||||
| @@ -112,20 +113,22 @@ class AccountScreen extends HookConsumerWidget { | |||||||
|                               crossAxisAlignment: CrossAxisAlignment.baseline, |                               crossAxisAlignment: CrossAxisAlignment.baseline, | ||||||
|                               textBaseline: TextBaseline.alphabetic, |                               textBaseline: TextBaseline.alphabetic, | ||||||
|                               children: [ |                               children: [ | ||||||
|                                 AccountName( |                                 Flexible( | ||||||
|                                   account: user.value!, |                                   child: AccountName( | ||||||
|                                   style: TextStyle( |                                     account: user.value!, | ||||||
|                                     fontSize: 16, |                                     style: TextStyle( | ||||||
|                                     fontWeight: FontWeight.bold, |                                       fontSize: 16, | ||||||
|  |                                       fontWeight: FontWeight.bold, | ||||||
|  |                                     ), | ||||||
|                                   ), |                                   ), | ||||||
|                                 ), |                                 ), | ||||||
|                                 Text('@${user.value!.name}'), |                                 Flexible(child: Text('@${user.value!.name}')), | ||||||
|                               ], |                               ], | ||||||
|                             ), |                             ), | ||||||
|                             Text( |                             Text( | ||||||
|                               (user.value!.profile.bio.isNotEmpty) |                               (user.value!.profile.bio.isNotEmpty) | ||||||
|                                   ? user.value!.profile.bio |                                   ? user.value!.profile.bio | ||||||
|                                   : 'No description yet.', |                                   : 'descriptionNone'.tr(), | ||||||
|                               maxLines: 1, |                               maxLines: 1, | ||||||
|                               overflow: TextOverflow.ellipsis, |                               overflow: TextOverflow.ellipsis, | ||||||
|                             ), |                             ), | ||||||
| @@ -158,8 +161,16 @@ class AccountScreen extends HookConsumerWidget { | |||||||
|                         crossAxisAlignment: CrossAxisAlignment.start, |                         crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|                         children: [ |                         children: [ | ||||||
|                           Icon(Symbols.draw, size: 28).padding(bottom: 8), |                           Icon(Symbols.draw, size: 28).padding(bottom: 8), | ||||||
|                           Text('creatorHub').tr().fontSize(16).bold(), |                           Text( | ||||||
|                           Text('creatorHubDescription').tr(), |                             'creatorHub', | ||||||
|  |                             maxLines: 1, | ||||||
|  |                             overflow: TextOverflow.ellipsis, | ||||||
|  |                           ).tr().fontSize(16).bold(), | ||||||
|  |                           Text( | ||||||
|  |                             'creatorHubDescription', | ||||||
|  |                             maxLines: 2, | ||||||
|  |                             overflow: TextOverflow.ellipsis, | ||||||
|  |                           ).tr(), | ||||||
|                         ], |                         ], | ||||||
|                       ).padding(horizontal: 16, vertical: 12), |                       ).padding(horizontal: 16, vertical: 12), | ||||||
|                       onTap: () { |                       onTap: () { | ||||||
| @@ -176,8 +187,16 @@ class AccountScreen extends HookConsumerWidget { | |||||||
|                         crossAxisAlignment: CrossAxisAlignment.start, |                         crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|                         children: [ |                         children: [ | ||||||
|                           Icon(Symbols.code, size: 28).padding(bottom: 8), |                           Icon(Symbols.code, size: 28).padding(bottom: 8), | ||||||
|                           Text('developerPortal').tr().fontSize(16).bold(), |                           Text( | ||||||
|                           Text('developerPortalDescription').tr(), |                             'developerPortal', | ||||||
|  |                             maxLines: 1, | ||||||
|  |                             overflow: TextOverflow.ellipsis, | ||||||
|  |                           ).tr().fontSize(16).bold(), | ||||||
|  |                           Text( | ||||||
|  |                             'developerPortalDescription', | ||||||
|  |                             maxLines: 2, | ||||||
|  |                             overflow: TextOverflow.ellipsis, | ||||||
|  |                           ).tr(), | ||||||
|                         ], |                         ], | ||||||
|                       ).padding(horizontal: 16, vertical: 12), |                       ).padding(horizontal: 16, vertical: 12), | ||||||
|                       onTap: () { |                       onTap: () { | ||||||
|   | |||||||
| @@ -4,6 +4,7 @@ import 'package:easy_localization/easy_localization.dart'; | |||||||
| import 'package:flutter/foundation.dart'; | import 'package:flutter/foundation.dart'; | ||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
| import 'package:flutter/services.dart'; | import 'package:flutter/services.dart'; | ||||||
|  | import 'package:gap/gap.dart'; | ||||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
| import 'package:island/models/auth.dart'; | import 'package:island/models/auth.dart'; | ||||||
| import 'package:island/models/account.dart'; | import 'package:island/models/account.dart'; | ||||||
| @@ -14,7 +15,6 @@ import 'package:island/screens/account/me/settings_connections.dart'; | |||||||
| import 'package:island/screens/account/me/settings_contacts.dart'; | import 'package:island/screens/account/me/settings_contacts.dart'; | ||||||
| import 'package:island/screens/auth/captcha.dart'; | import 'package:island/screens/auth/captcha.dart'; | ||||||
| import 'package:island/screens/auth/login.dart'; | import 'package:island/screens/auth/login.dart'; | ||||||
| import 'package:island/services/responsive.dart'; |  | ||||||
| import 'package:island/widgets/account/account_devices.dart'; | import 'package:island/widgets/account/account_devices.dart'; | ||||||
| import 'package:island/widgets/alert.dart'; | import 'package:island/widgets/alert.dart'; | ||||||
| import 'package:island/widgets/app_scaffold.dart'; | import 'package:island/widgets/app_scaffold.dart'; | ||||||
| @@ -57,7 +57,6 @@ class AccountSettingsScreen extends HookConsumerWidget { | |||||||
|   Widget build(BuildContext context, WidgetRef ref) { |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|     final isDesktop = |     final isDesktop = | ||||||
|         !kIsWeb && (Platform.isWindows || Platform.isMacOS || Platform.isLinux); |         !kIsWeb && (Platform.isWindows || Platform.isMacOS || Platform.isLinux); | ||||||
|     final isWide = isWideScreen(context); |  | ||||||
|  |  | ||||||
|     Future<void> requestAccountDeletion() async { |     Future<void> requestAccountDeletion() async { | ||||||
|       final confirm = await showConfirmAlert( |       final confirm = await showConfirmAlert( | ||||||
| @@ -440,51 +439,19 @@ class AccountSettingsScreen extends HookConsumerWidget { | |||||||
|  |  | ||||||
|     // Create a responsive layout based on screen width |     // Create a responsive layout based on screen width | ||||||
|     Widget buildSettingsList() { |     Widget buildSettingsList() { | ||||||
|       if (isWide) { |       return Column( | ||||||
|         // Two-column layout for wide screens |         crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|         return Row( |         children: [ | ||||||
|           crossAxisAlignment: CrossAxisAlignment.start, |           _SettingsSection( | ||||||
|           children: [ |             title: 'accountSecurityTitle', | ||||||
|             Expanded( |             children: securitySettings, | ||||||
|               child: Column( |           ), | ||||||
|                 crossAxisAlignment: CrossAxisAlignment.start, |           _SettingsSection( | ||||||
|                 children: [ |             title: 'accountDangerZoneTitle', | ||||||
|                   _SettingsSection( |             children: dangerZoneSettings, | ||||||
|                     title: 'accountSecurityTitle', |           ), | ||||||
|                     children: securitySettings, |         ], | ||||||
|                   ), |       ); | ||||||
|                 ], |  | ||||||
|               ), |  | ||||||
|             ), |  | ||||||
|             Expanded( |  | ||||||
|               child: Column( |  | ||||||
|                 crossAxisAlignment: CrossAxisAlignment.start, |  | ||||||
|                 children: [ |  | ||||||
|                   _SettingsSection( |  | ||||||
|                     title: 'accountDangerZoneTitle', |  | ||||||
|                     children: dangerZoneSettings, |  | ||||||
|                   ), |  | ||||||
|                 ], |  | ||||||
|               ), |  | ||||||
|             ), |  | ||||||
|           ], |  | ||||||
|         ); |  | ||||||
|       } else { |  | ||||||
|         // Single column layout for narrow screens |  | ||||||
|         return Column( |  | ||||||
|           crossAxisAlignment: CrossAxisAlignment.start, |  | ||||||
|           children: [ |  | ||||||
|             _SettingsSection( |  | ||||||
|               title: 'accountSecurityTitle', |  | ||||||
|               children: securitySettings, |  | ||||||
|             ), |  | ||||||
|             _SettingsSection( |  | ||||||
|               title: 'accountDangerZoneTitle', |  | ||||||
|               children: dangerZoneSettings, |  | ||||||
|             ), |  | ||||||
|           ], |  | ||||||
|         ); |  | ||||||
|       } |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     return AppScaffold( |     return AppScaffold( | ||||||
| @@ -513,6 +480,7 @@ class AccountSettingsScreen extends HookConsumerWidget { | |||||||
|                       ); |                       ); | ||||||
|                     }, |                     }, | ||||||
|                   ), |                   ), | ||||||
|  |                   const Gap(8), | ||||||
|                 ] |                 ] | ||||||
|                 : null, |                 : null, | ||||||
|       ), |       ), | ||||||
|   | |||||||
| @@ -21,6 +21,7 @@ import 'package:material_symbols_icons/symbols.dart'; | |||||||
| import 'package:styled_widget/styled_widget.dart'; | import 'package:styled_widget/styled_widget.dart'; | ||||||
|  |  | ||||||
| const kServerSupportedLanguages = {'en-US': 'en-us', 'zh-CN': 'zh-hans'}; | const kServerSupportedLanguages = {'en-US': 'en-us', 'zh-CN': 'zh-hans'}; | ||||||
|  | const kServerSupportedRegions = ['US', 'JP', 'CN']; | ||||||
|  |  | ||||||
| class UpdateProfileScreen extends HookConsumerWidget { | class UpdateProfileScreen extends HookConsumerWidget { | ||||||
|   const UpdateProfileScreen({super.key}); |   const UpdateProfileScreen({super.key}); | ||||||
| @@ -97,6 +98,7 @@ class UpdateProfileScreen extends HookConsumerWidget { | |||||||
|     final usernameController = useTextEditingController(text: user.value!.name); |     final usernameController = useTextEditingController(text: user.value!.name); | ||||||
|     final nicknameController = useTextEditingController(text: user.value!.nick); |     final nicknameController = useTextEditingController(text: user.value!.nick); | ||||||
|     final language = useState(user.value!.language); |     final language = useState(user.value!.language); | ||||||
|  |     final region = useState(user.value!.region); | ||||||
|     final links = useState<List<ProfileLink>>(user.value!.profile.links); |     final links = useState<List<ProfileLink>>(user.value!.profile.links); | ||||||
|  |  | ||||||
|     void updateBasicInfo() async { |     void updateBasicInfo() async { | ||||||
| @@ -111,6 +113,7 @@ class UpdateProfileScreen extends HookConsumerWidget { | |||||||
|             'name': usernameController.text, |             'name': usernameController.text, | ||||||
|             'nick': nicknameController.text, |             'nick': nicknameController.text, | ||||||
|             'language': language.value, |             'language': language.value, | ||||||
|  |             'region': region.value, | ||||||
|           }, |           }, | ||||||
|         ); |         ); | ||||||
|         final userNotifier = ref.read(userInfoProvider.notifier); |         final userNotifier = ref.read(userInfoProvider.notifier); | ||||||
| @@ -291,6 +294,32 @@ class UpdateProfileScreen extends HookConsumerWidget { | |||||||
|                       ], |                       ], | ||||||
|                     ), |                     ), | ||||||
|                   ), |                   ), | ||||||
|  |                   DropdownButtonFormField2<String>( | ||||||
|  |                     decoration: InputDecoration( | ||||||
|  |                       labelText: 'region'.tr(), | ||||||
|  |                       helperText: 'accountRegionHint'.tr(), | ||||||
|  |                     ), | ||||||
|  |                     items: [ | ||||||
|  |                       ...kServerSupportedRegions.map( | ||||||
|  |                         (e) => DropdownMenuItem(value: e, child: Text(e)), | ||||||
|  |                       ), | ||||||
|  |                       if (!kServerSupportedRegions.contains(region.value)) | ||||||
|  |                         DropdownMenuItem( | ||||||
|  |                           value: region.value, | ||||||
|  |                           child: Text(region.value), | ||||||
|  |                         ), | ||||||
|  |                     ], | ||||||
|  |                     value: region.value, | ||||||
|  |                     onChanged: (value) { | ||||||
|  |                       region.value = value ?? region.value; | ||||||
|  |                     }, | ||||||
|  |                     customButton: Row( | ||||||
|  |                       children: [ | ||||||
|  |                         Expanded(child: Text(region.value)), | ||||||
|  |                         Icon(Symbols.arrow_drop_down), | ||||||
|  |                       ], | ||||||
|  |                     ), | ||||||
|  |                   ), | ||||||
|                   Align( |                   Align( | ||||||
|                     alignment: Alignment.centerRight, |                     alignment: Alignment.centerRight, | ||||||
|                     child: TextButton.icon( |                     child: TextButton.icon( | ||||||
|   | |||||||
| @@ -1,14 +1,16 @@ | |||||||
| import 'package:easy_localization/easy_localization.dart'; | import 'package:easy_localization/easy_localization.dart'; | ||||||
|  | import 'package:flutter/foundation.dart'; | ||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
| import 'package:flutter_hooks/flutter_hooks.dart'; | import 'package:flutter_hooks/flutter_hooks.dart'; | ||||||
| import 'package:flutter_svg/flutter_svg.dart'; | import 'package:flutter_svg/flutter_svg.dart'; | ||||||
| import 'package:gap/gap.dart'; | import 'package:gap/gap.dart'; | ||||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
| import 'package:island/models/auth.dart'; | import 'package:island/models/auth.dart'; | ||||||
|  | import 'package:island/pods/config.dart'; | ||||||
| import 'package:island/pods/network.dart'; | import 'package:island/pods/network.dart'; | ||||||
| import 'package:island/screens/account/me/account_settings.dart'; | import 'package:island/screens/account/me/account_settings.dart'; | ||||||
| import 'package:island/screens/auth/oidc.native.dart'; | import 'package:island/screens/auth/oidc.native.dart'; | ||||||
| import 'package:island/services/text.dart'; | import 'package:island/utils/text.dart'; | ||||||
| import 'package:island/services/time.dart'; | import 'package:island/services/time.dart'; | ||||||
| import 'package:island/widgets/alert.dart'; | import 'package:island/widgets/alert.dart'; | ||||||
| import 'package:island/widgets/content/sheet.dart'; | import 'package:island/widgets/content/sheet.dart'; | ||||||
| @@ -16,6 +18,7 @@ import 'package:island/widgets/response.dart'; | |||||||
| import 'package:material_symbols_icons/symbols.dart'; | import 'package:material_symbols_icons/symbols.dart'; | ||||||
| import 'package:sign_in_with_apple/sign_in_with_apple.dart'; | import 'package:sign_in_with_apple/sign_in_with_apple.dart'; | ||||||
| import 'package:styled_widget/styled_widget.dart'; | import 'package:styled_widget/styled_widget.dart'; | ||||||
|  | import 'package:url_launcher/url_launcher_string.dart'; | ||||||
|  |  | ||||||
| // Helper function to get provider icon and localized name | // Helper function to get provider icon and localized name | ||||||
| Widget getProviderIcon(String provider, {double size = 24, Color? color}) { | Widget getProviderIcon(String provider, {double size = 24, Color? color}) { | ||||||
| @@ -165,9 +168,7 @@ class AccountConnectionNewSheet extends HookConsumerWidget { | |||||||
|               scopes: [AppleIDAuthorizationScopes.email], |               scopes: [AppleIDAuthorizationScopes.email], | ||||||
|               webAuthenticationOptions: WebAuthenticationOptions( |               webAuthenticationOptions: WebAuthenticationOptions( | ||||||
|                 clientId: 'dev.solsynth.solarpass', |                 clientId: 'dev.solsynth.solarpass', | ||||||
|                 redirectUri: Uri.parse( |                 redirectUri: Uri.parse('https://id.solian.app/auth/callback'), | ||||||
|                   'https://id.solian.app/auth/callback/apple', |  | ||||||
|                 ), |  | ||||||
|               ), |               ), | ||||||
|             ); |             ); | ||||||
|  |  | ||||||
| @@ -195,17 +196,25 @@ class AccountConnectionNewSheet extends HookConsumerWidget { | |||||||
|         case 'github': |         case 'github': | ||||||
|         case 'discord': |         case 'discord': | ||||||
|         case 'afdian': |         case 'afdian': | ||||||
|           await Navigator.of(context, rootNavigator: true).push( |           if (kIsWeb) { | ||||||
|             MaterialPageRoute( |             final serverUrl = ref.watch(serverUrlProvider); | ||||||
|               builder: |             final accessToken = ref.watch(tokenProvider); | ||||||
|                   (context) => OidcScreen( |             launchUrlString( | ||||||
|                     provider: selectedProvider.value.toLowerCase(), |               '$serverUrl/id/auth/login/${selectedProvider.value}?tk=${accessToken!.token}', | ||||||
|                     title: |             ); | ||||||
|                         'Connect with ${selectedProvider.value.capitalizeEachWord()}', |           } else { | ||||||
|                   ), |             await Navigator.of(context, rootNavigator: true).push( | ||||||
|             ), |               MaterialPageRoute( | ||||||
|           ); |                 builder: | ||||||
|           if (context.mounted) Navigator.pop(context, true); |                     (context) => OidcScreen( | ||||||
|  |                       provider: selectedProvider.value.toLowerCase(), | ||||||
|  |                       title: | ||||||
|  |                           'Connect with ${selectedProvider.value.capitalizeEachWord()}', | ||||||
|  |                     ), | ||||||
|  |               ), | ||||||
|  |             ); | ||||||
|  |             if (context.mounted) Navigator.pop(context, true); | ||||||
|  |           } | ||||||
|           break; |           break; | ||||||
|         default: |         default: | ||||||
|           showSnackBar('accountConnectionAddError'.tr()); |           showSnackBar('accountConnectionAddError'.tr()); | ||||||
|   | |||||||
| @@ -62,6 +62,32 @@ class ContactMethodSheet extends HookConsumerWidget { | |||||||
|       } |       } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     Future<void> makeContactMethodPublic() async { | ||||||
|  |       try { | ||||||
|  |         showLoadingModal(context); | ||||||
|  |         final client = ref.read(apiClientProvider); | ||||||
|  |         await client.post('/id/accounts/me/contacts/${contact.id}/public'); | ||||||
|  |         if (context.mounted) Navigator.pop(context, true); | ||||||
|  |       } catch (err) { | ||||||
|  |         showErrorAlert(err); | ||||||
|  |       } finally { | ||||||
|  |         if (context.mounted) hideLoadingModal(context); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     Future<void> makeContactMethodPrivate() async { | ||||||
|  |       try { | ||||||
|  |         showLoadingModal(context); | ||||||
|  |         final client = ref.read(apiClientProvider); | ||||||
|  |         await client.delete('/id/accounts/me/contacts/${contact.id}/public'); | ||||||
|  |         if (context.mounted) Navigator.pop(context, true); | ||||||
|  |       } catch (err) { | ||||||
|  |         showErrorAlert(err); | ||||||
|  |       } finally { | ||||||
|  |         if (context.mounted) hideLoadingModal(context); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|     return SheetScaffold( |     return SheetScaffold( | ||||||
|       titleText: 'contactMethod'.tr(), |       titleText: 'contactMethod'.tr(), | ||||||
|       child: Column( |       child: Column( | ||||||
| @@ -111,6 +137,27 @@ class ContactMethodSheet extends HookConsumerWidget { | |||||||
|                         backgroundColor: Theme.of(context).colorScheme.tertiary, |                         backgroundColor: Theme.of(context).colorScheme.tertiary, | ||||||
|                       ), |                       ), | ||||||
|                     ), |                     ), | ||||||
|  |                   if (contact.isPublic) | ||||||
|  |                     Padding( | ||||||
|  |                       padding: const EdgeInsets.only(left: 8.0), | ||||||
|  |                       child: Badge( | ||||||
|  |                         label: Text('contactMethodPublic'.tr()), | ||||||
|  |                         textColor: Theme.of(context).colorScheme.onPrimary, | ||||||
|  |                         backgroundColor: Theme.of(context).colorScheme.primary, | ||||||
|  |                       ), | ||||||
|  |                     ), | ||||||
|  |                   if (!contact.isPublic) | ||||||
|  |                     Padding( | ||||||
|  |                       padding: const EdgeInsets.only(left: 8.0), | ||||||
|  |                       child: Badge( | ||||||
|  |                         label: Text('contactMethodPrivate'.tr()), | ||||||
|  |                         textColor: Theme.of(context).colorScheme.onSurface, | ||||||
|  |                         backgroundColor: | ||||||
|  |                             Theme.of( | ||||||
|  |                               context, | ||||||
|  |                             ).colorScheme.surfaceContainerHighest, | ||||||
|  |                       ), | ||||||
|  |                     ), | ||||||
|                 ], |                 ], | ||||||
|               ), |               ), | ||||||
|             ], |             ], | ||||||
| @@ -130,6 +177,20 @@ class ContactMethodSheet extends HookConsumerWidget { | |||||||
|               onTap: setContactMethodAsPrimary, |               onTap: setContactMethodAsPrimary, | ||||||
|               contentPadding: EdgeInsets.symmetric(horizontal: 20), |               contentPadding: EdgeInsets.symmetric(horizontal: 20), | ||||||
|             ), |             ), | ||||||
|  |           if (contact.verifiedAt != null && !contact.isPublic) | ||||||
|  |             ListTile( | ||||||
|  |               leading: const Icon(Symbols.public), | ||||||
|  |               title: Text('contactMethodMakePublic').tr(), | ||||||
|  |               onTap: makeContactMethodPublic, | ||||||
|  |               contentPadding: EdgeInsets.symmetric(horizontal: 20), | ||||||
|  |             ), | ||||||
|  |           if (contact.verifiedAt != null && contact.isPublic) | ||||||
|  |             ListTile( | ||||||
|  |               leading: const Icon(Symbols.visibility_off), | ||||||
|  |               title: Text('contactMethodMakePrivate').tr(), | ||||||
|  |               onTap: makeContactMethodPrivate, | ||||||
|  |               contentPadding: EdgeInsets.symmetric(horizontal: 20), | ||||||
|  |             ), | ||||||
|           ListTile( |           ListTile( | ||||||
|             leading: const Icon(Symbols.delete), |             leading: const Icon(Symbols.delete), | ||||||
|             title: Text('contactMethodDelete').tr(), |             title: Text('contactMethodDelete').tr(), | ||||||
|   | |||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -762,5 +762,127 @@ class _AccountBotDeveloperProviderElement | |||||||
|   String get uname => (origin as AccountBotDeveloperProvider).uname; |   String get uname => (origin as AccountBotDeveloperProvider).uname; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | String _$accountPublishersHash() => r'25f5695b4a5154163d77f1769876d826bf736609'; | ||||||
|  |  | ||||||
|  | /// See also [accountPublishers]. | ||||||
|  | @ProviderFor(accountPublishers) | ||||||
|  | const accountPublishersProvider = AccountPublishersFamily(); | ||||||
|  |  | ||||||
|  | /// See also [accountPublishers]. | ||||||
|  | class AccountPublishersFamily extends Family<AsyncValue<List<SnPublisher>>> { | ||||||
|  |   /// See also [accountPublishers]. | ||||||
|  |   const AccountPublishersFamily(); | ||||||
|  |  | ||||||
|  |   /// See also [accountPublishers]. | ||||||
|  |   AccountPublishersProvider call(String id) { | ||||||
|  |     return AccountPublishersProvider(id); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   AccountPublishersProvider getProviderOverride( | ||||||
|  |     covariant AccountPublishersProvider provider, | ||||||
|  |   ) { | ||||||
|  |     return call(provider.id); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   static const Iterable<ProviderOrFamily>? _dependencies = null; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Iterable<ProviderOrFamily>? get dependencies => _dependencies; | ||||||
|  |  | ||||||
|  |   static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Iterable<ProviderOrFamily>? get allTransitiveDependencies => | ||||||
|  |       _allTransitiveDependencies; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   String? get name => r'accountPublishersProvider'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// See also [accountPublishers]. | ||||||
|  | class AccountPublishersProvider | ||||||
|  |     extends AutoDisposeFutureProvider<List<SnPublisher>> { | ||||||
|  |   /// See also [accountPublishers]. | ||||||
|  |   AccountPublishersProvider(String id) | ||||||
|  |     : this._internal( | ||||||
|  |         (ref) => accountPublishers(ref as AccountPublishersRef, id), | ||||||
|  |         from: accountPublishersProvider, | ||||||
|  |         name: r'accountPublishersProvider', | ||||||
|  |         debugGetCreateSourceHash: | ||||||
|  |             const bool.fromEnvironment('dart.vm.product') | ||||||
|  |                 ? null | ||||||
|  |                 : _$accountPublishersHash, | ||||||
|  |         dependencies: AccountPublishersFamily._dependencies, | ||||||
|  |         allTransitiveDependencies: | ||||||
|  |             AccountPublishersFamily._allTransitiveDependencies, | ||||||
|  |         id: id, | ||||||
|  |       ); | ||||||
|  |  | ||||||
|  |   AccountPublishersProvider._internal( | ||||||
|  |     super._createNotifier, { | ||||||
|  |     required super.name, | ||||||
|  |     required super.dependencies, | ||||||
|  |     required super.allTransitiveDependencies, | ||||||
|  |     required super.debugGetCreateSourceHash, | ||||||
|  |     required super.from, | ||||||
|  |     required this.id, | ||||||
|  |   }) : super.internal(); | ||||||
|  |  | ||||||
|  |   final String id; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Override overrideWith( | ||||||
|  |     FutureOr<List<SnPublisher>> Function(AccountPublishersRef provider) create, | ||||||
|  |   ) { | ||||||
|  |     return ProviderOverride( | ||||||
|  |       origin: this, | ||||||
|  |       override: AccountPublishersProvider._internal( | ||||||
|  |         (ref) => create(ref as AccountPublishersRef), | ||||||
|  |         from: from, | ||||||
|  |         name: null, | ||||||
|  |         dependencies: null, | ||||||
|  |         allTransitiveDependencies: null, | ||||||
|  |         debugGetCreateSourceHash: null, | ||||||
|  |         id: id, | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   AutoDisposeFutureProviderElement<List<SnPublisher>> createElement() { | ||||||
|  |     return _AccountPublishersProviderElement(this); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   bool operator ==(Object other) { | ||||||
|  |     return other is AccountPublishersProvider && other.id == id; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   int get hashCode { | ||||||
|  |     var hash = _SystemHash.combine(0, runtimeType.hashCode); | ||||||
|  |     hash = _SystemHash.combine(hash, id.hashCode); | ||||||
|  |  | ||||||
|  |     return _SystemHash.finish(hash); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @Deprecated('Will be removed in 3.0. Use Ref instead') | ||||||
|  | // ignore: unused_element | ||||||
|  | mixin AccountPublishersRef on AutoDisposeFutureProviderRef<List<SnPublisher>> { | ||||||
|  |   /// The parameter `id` of this provider. | ||||||
|  |   String get id; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class _AccountPublishersProviderElement | ||||||
|  |     extends AutoDisposeFutureProviderElement<List<SnPublisher>> | ||||||
|  |     with AccountPublishersRef { | ||||||
|  |   _AccountPublishersProviderElement(super.provider); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   String get id => (origin as AccountPublishersProvider).id; | ||||||
|  | } | ||||||
|  |  | ||||||
| // ignore_for_file: type=lint | // ignore_for_file: type=lint | ||||||
| // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package | // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package | ||||||
|   | |||||||
| @@ -4,6 +4,7 @@ import 'package:flutter/services.dart'; | |||||||
| import 'package:flutter_hooks/flutter_hooks.dart'; | import 'package:flutter_hooks/flutter_hooks.dart'; | ||||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
| import 'package:island/pods/userinfo.dart'; | import 'package:island/pods/userinfo.dart'; | ||||||
|  | import 'package:island/widgets/account/account_pfc.dart'; | ||||||
| import 'package:island/widgets/account/account_picker.dart'; | import 'package:island/widgets/account/account_picker.dart'; | ||||||
| import 'package:island/widgets/alert.dart'; | import 'package:island/widgets/alert.dart'; | ||||||
| import 'package:island/widgets/app_scaffold.dart'; | import 'package:island/widgets/app_scaffold.dart'; | ||||||
| @@ -99,7 +100,10 @@ class RelationshipListTile extends StatelessWidget { | |||||||
|  |  | ||||||
|     return ListTile( |     return ListTile( | ||||||
|       contentPadding: const EdgeInsets.only(left: 16, right: 12), |       contentPadding: const EdgeInsets.only(left: 16, right: 12), | ||||||
|       leading: ProfilePictureWidget(fileId: account.profile.picture?.id), |       leading: AccountPfcGestureDetector( | ||||||
|  |         uname: account.name, | ||||||
|  |         child: ProfilePictureWidget(fileId: account.profile.picture?.id), | ||||||
|  |       ), | ||||||
|       title: Row( |       title: Row( | ||||||
|         spacing: 6, |         spacing: 6, | ||||||
|         children: [ |         children: [ | ||||||
|   | |||||||
| @@ -700,45 +700,48 @@ class _LoginLookupScreen extends HookConsumerWidget { | |||||||
|           onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), |           onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), | ||||||
|           onSubmitted: isBusy.value ? null : (_) => performNewTicket(), |           onSubmitted: isBusy.value ? null : (_) => performNewTicket(), | ||||||
|         ).padding(horizontal: 7), |         ).padding(horizontal: 7), | ||||||
|         Row( |         if (!kIsWeb) | ||||||
|           spacing: 6, |           Row( | ||||||
|           crossAxisAlignment: CrossAxisAlignment.center, |             spacing: 6, | ||||||
|           children: <Widget>[ |             crossAxisAlignment: CrossAxisAlignment.center, | ||||||
|             Text("loginOr").tr().fontSize(11).opacity(0.85), |             children: <Widget>[ | ||||||
|             const Gap(8), |               Text("loginOr").tr().fontSize(11).opacity(0.85), | ||||||
|             Spacer(), |               const Gap(8), | ||||||
|             IconButton.filledTonal( |               Spacer(), | ||||||
|               onPressed: () => withOidc('github'), |               IconButton.filledTonal( | ||||||
|               padding: EdgeInsets.zero, |                 onPressed: () => withOidc('github'), | ||||||
|               icon: getProviderIcon( |                 padding: EdgeInsets.zero, | ||||||
|                 "github", |                 icon: getProviderIcon( | ||||||
|                 size: 16, |                   "github", | ||||||
|                 color: Theme.of(context).colorScheme.onPrimaryContainer, |                   size: 16, | ||||||
|  |                   color: Theme.of(context).colorScheme.onPrimaryContainer, | ||||||
|  |                 ), | ||||||
|  |                 tooltip: 'GitHub', | ||||||
|               ), |               ), | ||||||
|               tooltip: 'GitHub', |               IconButton.filledTonal( | ||||||
|             ), |                 onPressed: () => withOidc('google'), | ||||||
|             IconButton.filledTonal( |                 padding: EdgeInsets.zero, | ||||||
|               onPressed: () => withOidc('google'), |                 icon: getProviderIcon( | ||||||
|               padding: EdgeInsets.zero, |                   "google", | ||||||
|               icon: getProviderIcon( |                   size: 16, | ||||||
|                 "google", |                   color: Theme.of(context).colorScheme.onPrimaryContainer, | ||||||
|                 size: 16, |                 ), | ||||||
|                 color: Theme.of(context).colorScheme.onPrimaryContainer, |                 tooltip: 'Google', | ||||||
|               ), |               ), | ||||||
|               tooltip: 'Google', |               IconButton.filledTonal( | ||||||
|             ), |                 onPressed: withApple, | ||||||
|             IconButton.filledTonal( |                 padding: EdgeInsets.zero, | ||||||
|               onPressed: withApple, |                 icon: getProviderIcon( | ||||||
|               padding: EdgeInsets.zero, |                   "apple", | ||||||
|               icon: getProviderIcon( |                   size: 16, | ||||||
|                 "apple", |                   color: Theme.of(context).colorScheme.onPrimaryContainer, | ||||||
|                 size: 16, |                 ), | ||||||
|                 color: Theme.of(context).colorScheme.onPrimaryContainer, |                 tooltip: 'Apple Account', | ||||||
|               ), |               ), | ||||||
|               tooltip: 'Apple Account', |             ], | ||||||
|             ), |           ).padding(horizontal: 8, vertical: 8) | ||||||
|           ], |         else | ||||||
|         ).padding(horizontal: 8, vertical: 8), |           const Gap(12), | ||||||
|         Row( |         Row( | ||||||
|           mainAxisAlignment: MainAxisAlignment.spaceBetween, |           mainAxisAlignment: MainAxisAlignment.spaceBetween, | ||||||
|           children: [ |           children: [ | ||||||
|   | |||||||
| @@ -4,6 +4,7 @@ import "dart:developer" as developer; | |||||||
| import "dart:io"; | import "dart:io"; | ||||||
| import "package:dio/dio.dart"; | import "package:dio/dio.dart"; | ||||||
| import "package:easy_localization/easy_localization.dart"; | import "package:easy_localization/easy_localization.dart"; | ||||||
|  | import "package:file_picker/file_picker.dart"; | ||||||
| import "package:flutter/foundation.dart"; | import "package:flutter/foundation.dart"; | ||||||
| import "package:flutter/material.dart"; | import "package:flutter/material.dart"; | ||||||
| import "package:go_router/go_router.dart"; | import "package:go_router/go_router.dart"; | ||||||
| @@ -1251,26 +1252,32 @@ class ChatRoomScreen extends HookConsumerWidget { | |||||||
|     }, [id]); |     }, [id]); | ||||||
|  |  | ||||||
|     Future<void> pickPhotoMedia() async { |     Future<void> pickPhotoMedia() async { | ||||||
|       final result = await ref |       final result = await FilePicker.platform.pickFiles( | ||||||
|           .watch(imagePickerProvider) |         type: FileType.image, | ||||||
|           .pickMultiImage(requestFullMetadata: true); |         allowMultiple: true, | ||||||
|       if (result.isEmpty) return; |         allowCompression: false, | ||||||
|  |       ); | ||||||
|  |       if (result == null || result.count == 0) return; | ||||||
|       attachments.value = [ |       attachments.value = [ | ||||||
|         ...attachments.value, |         ...attachments.value, | ||||||
|         ...result.map( |         ...result.files.map( | ||||||
|           (e) => UniversalFile(data: e, type: UniversalFileType.image), |           (e) => UniversalFile(data: e.xFile, type: UniversalFileType.image), | ||||||
|         ), |         ), | ||||||
|       ]; |       ]; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     Future<void> pickVideoMedia() async { |     Future<void> pickVideoMedia() async { | ||||||
|       final result = await ref |       final result = await FilePicker.platform.pickFiles( | ||||||
|           .watch(imagePickerProvider) |         type: FileType.video, | ||||||
|           .pickVideo(source: ImageSource.gallery); |         allowMultiple: true, | ||||||
|       if (result == null) return; |         allowCompression: false, | ||||||
|  |       ); | ||||||
|  |       if (result == null || result.count == 0) return; | ||||||
|       attachments.value = [ |       attachments.value = [ | ||||||
|         ...attachments.value, |         ...attachments.value, | ||||||
|         UniversalFile(data: result, type: UniversalFileType.video), |         ...result.files.map( | ||||||
|  |           (e) => UniversalFile(data: e.xFile, type: UniversalFileType.video), | ||||||
|  |         ), | ||||||
|       ]; |       ]; | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -1387,6 +1394,8 @@ class ChatRoomScreen extends HookConsumerWidget { | |||||||
|       ], |       ], | ||||||
|     ); |     ); | ||||||
|  |  | ||||||
|  |     const messageKeyPrefix = 'message-'; | ||||||
|  |  | ||||||
|     Widget chatMessageListWidget(List<LocalChatMessage> messageList) => |     Widget chatMessageListWidget(List<LocalChatMessage> messageList) => | ||||||
|         SuperListView.builder( |         SuperListView.builder( | ||||||
|           listController: listController, |           listController: listController, | ||||||
| @@ -1396,7 +1405,9 @@ class ChatRoomScreen extends HookConsumerWidget { | |||||||
|           itemCount: messageList.length, |           itemCount: messageList.length, | ||||||
|           findChildIndexCallback: (key) { |           findChildIndexCallback: (key) { | ||||||
|             final valueKey = key as ValueKey; |             final valueKey = key as ValueKey; | ||||||
|             final messageId = valueKey.value as String; |             final messageId = (valueKey.value as String).substring( | ||||||
|  |               messageKeyPrefix.length, | ||||||
|  |             ); | ||||||
|             return messageList.indexWhere((m) => m.id == messageId); |             return messageList.indexWhere((m) => m.id == messageId); | ||||||
|           }, |           }, | ||||||
|           extentEstimation: (_, _) => 40, |           extentEstimation: (_, _) => 40, | ||||||
| @@ -1413,10 +1424,13 @@ class ChatRoomScreen extends HookConsumerWidget { | |||||||
|                         .abs() > |                         .abs() > | ||||||
|                     3; |                     3; | ||||||
|  |  | ||||||
|  |             final key = ValueKey('$messageKeyPrefix${message.id}'); | ||||||
|  |  | ||||||
|             return chatIdentity.when( |             return chatIdentity.when( | ||||||
|               skipError: true, |               skipError: true, | ||||||
|               data: |               data: | ||||||
|                   (identity) => MessageItem( |                   (identity) => MessageItem( | ||||||
|  |                     key: key, | ||||||
|                     message: message, |                     message: message, | ||||||
|                     isCurrentUser: identity?.id == message.senderId, |                     isCurrentUser: identity?.id == message.senderId, | ||||||
|                     onAction: (action) { |                     onAction: (action) { | ||||||
| @@ -1459,6 +1473,7 @@ class ChatRoomScreen extends HookConsumerWidget { | |||||||
|                   ), |                   ), | ||||||
|               loading: |               loading: | ||||||
|                   () => MessageItem( |                   () => MessageItem( | ||||||
|  |                     key: key, | ||||||
|                     message: message, |                     message: message, | ||||||
|                     isCurrentUser: false, |                     isCurrentUser: false, | ||||||
|                     onAction: null, |                     onAction: null, | ||||||
| @@ -1466,7 +1481,7 @@ class ChatRoomScreen extends HookConsumerWidget { | |||||||
|                     showAvatar: false, |                     showAvatar: false, | ||||||
|                     onJump: (_) {}, |                     onJump: (_) {}, | ||||||
|                   ), |                   ), | ||||||
|               error: (_, _) => const SizedBox.shrink(), |               error: (_, _) => SizedBox.shrink(key: key), | ||||||
|             ); |             ); | ||||||
|           }, |           }, | ||||||
|         ); |         ); | ||||||
|   | |||||||
| @@ -153,7 +153,9 @@ class ChatDetailScreen extends HookConsumerWidget { | |||||||
|                   ), |                   ), | ||||||
|                   ListTile( |                   ListTile( | ||||||
|                     title: const Text('chatBreak5m').tr(), |                     title: const Text('chatBreak5m').tr(), | ||||||
|                     subtitle: const Text('chatBreakHour').tr(args: ['chatBreak5m'.tr()]), |                     subtitle: const Text( | ||||||
|  |                       'chatBreakHour', | ||||||
|  |                     ).tr(args: ['chatBreak5m'.tr()]), | ||||||
|                     leading: const Icon(Symbols.circle), |                     leading: const Icon(Symbols.circle), | ||||||
|                     onTap: () { |                     onTap: () { | ||||||
|                       setChatBreak(now.add(const Duration(minutes: 5))); |                       setChatBreak(now.add(const Duration(minutes: 5))); | ||||||
| @@ -165,7 +167,9 @@ class ChatDetailScreen extends HookConsumerWidget { | |||||||
|                   ), |                   ), | ||||||
|                   ListTile( |                   ListTile( | ||||||
|                     title: const Text('chatBreak10m').tr(), |                     title: const Text('chatBreak10m').tr(), | ||||||
|                     subtitle: const Text('chatBreakHour').tr(args: ['chatBreak10m'.tr()]), |                     subtitle: const Text( | ||||||
|  |                       'chatBreakHour', | ||||||
|  |                     ).tr(args: ['chatBreak10m'.tr()]), | ||||||
|                     leading: const Icon(Symbols.circle), |                     leading: const Icon(Symbols.circle), | ||||||
|                     onTap: () { |                     onTap: () { | ||||||
|                       setChatBreak(now.add(const Duration(minutes: 10))); |                       setChatBreak(now.add(const Duration(minutes: 10))); | ||||||
| @@ -177,7 +181,9 @@ class ChatDetailScreen extends HookConsumerWidget { | |||||||
|                   ), |                   ), | ||||||
|                   ListTile( |                   ListTile( | ||||||
|                     title: const Text('chatBreak15m').tr(), |                     title: const Text('chatBreak15m').tr(), | ||||||
|                     subtitle: const Text('chatBreakHour').tr(args: ['chatBreak15m'.tr()]), |                     subtitle: const Text( | ||||||
|  |                       'chatBreakHour', | ||||||
|  |                     ).tr(args: ['chatBreak15m'.tr()]), | ||||||
|                     leading: const Icon(Symbols.timer_3), |                     leading: const Icon(Symbols.timer_3), | ||||||
|                     onTap: () { |                     onTap: () { | ||||||
|                       setChatBreak(now.add(const Duration(minutes: 15))); |                       setChatBreak(now.add(const Duration(minutes: 15))); | ||||||
| @@ -189,7 +195,9 @@ class ChatDetailScreen extends HookConsumerWidget { | |||||||
|                   ), |                   ), | ||||||
|                   ListTile( |                   ListTile( | ||||||
|                     title: const Text('chatBreak30m').tr(), |                     title: const Text('chatBreak30m').tr(), | ||||||
|                     subtitle: const Text('chatBreakHour').tr(args: ['chatBreak30m'.tr()]), |                     subtitle: const Text( | ||||||
|  |                       'chatBreakHour', | ||||||
|  |                     ).tr(args: ['chatBreak30m'.tr()]), | ||||||
|                     leading: const Icon(Symbols.timer), |                     leading: const Icon(Symbols.timer), | ||||||
|                     onTap: () { |                     onTap: () { | ||||||
|                       setChatBreak(now.add(const Duration(minutes: 30))); |                       setChatBreak(now.add(const Duration(minutes: 30))); | ||||||
| @@ -247,7 +255,10 @@ class ChatDetailScreen extends HookConsumerWidget { | |||||||
|     return AppScaffold( |     return AppScaffold( | ||||||
|       body: roomState.when( |       body: roomState.when( | ||||||
|         loading: () => const Center(child: CircularProgressIndicator()), |         loading: () => const Center(child: CircularProgressIndicator()), | ||||||
|         error: (error, _) => Center(child: Text('errorGeneric'.tr(args: [error.toString()]))), |         error: | ||||||
|  |             (error, _) => Center( | ||||||
|  |               child: Text('errorGeneric'.tr(args: [error.toString()])), | ||||||
|  |             ), | ||||||
|         data: |         data: | ||||||
|             (currentRoom) => CustomScrollView( |             (currentRoom) => CustomScrollView( | ||||||
|               slivers: [ |               slivers: [ | ||||||
| @@ -375,12 +386,26 @@ class ChatDetailScreen extends HookConsumerWidget { | |||||||
|                                   trailing: const Icon(Symbols.chevron_right), |                                   trailing: const Icon(Symbols.chevron_right), | ||||||
|                                   title: const Text('searchMessages').tr(), |                                   title: const Text('searchMessages').tr(), | ||||||
|                                   subtitle: totalMessages.when( |                                   subtitle: totalMessages.when( | ||||||
|                                     data: (count) => Text('messagesCount'.tr(args: [count.toString()])), |                                     data: | ||||||
|                                     loading: () => const CircularProgressIndicator(), |                                         (count) => Text( | ||||||
|                                     error: (err, stack) => Text('errorGeneric'.tr(args: [err.toString()])), |                                           'messagesCount'.tr( | ||||||
|  |                                             args: [count.toString()], | ||||||
|  |                                           ), | ||||||
|  |                                         ), | ||||||
|  |                                     loading: | ||||||
|  |                                         () => const CircularProgressIndicator(), | ||||||
|  |                                     error: | ||||||
|  |                                         (err, stack) => Text( | ||||||
|  |                                           'errorGeneric'.tr( | ||||||
|  |                                             args: [err.toString()], | ||||||
|  |                                           ), | ||||||
|  |                                         ), | ||||||
|                                   ), |                                   ), | ||||||
|                                   onTap: () { |                                   onTap: () { | ||||||
|                                     context.pushNamed('searchMessages', pathParameters: {'id': id}); |                                     context.pushNamed( | ||||||
|  |                                       'searchMessages', | ||||||
|  |                                       pathParameters: {'id': id}, | ||||||
|  |                                     ); | ||||||
|                                   }, |                                   }, | ||||||
|                                 ), |                                 ), | ||||||
|                               ], |                               ], | ||||||
| @@ -702,7 +727,11 @@ class _ChatMemberListSheet extends HookConsumerWidget { | |||||||
|                         children: [ |                         children: [ | ||||||
|                           Flexible(child: Text(member.account.nick)), |                           Flexible(child: Text(member.account.nick)), | ||||||
|                           if (member.status != null) |                           if (member.status != null) | ||||||
|                             AccountStatusLabel(status: member.status!), |                             AccountStatusLabel( | ||||||
|  |                               status: member.status!, | ||||||
|  |                               maxLines: 1, | ||||||
|  |                               overflow: TextOverflow.ellipsis, | ||||||
|  |                             ), | ||||||
|                           if (member.joinedAt == null) |                           if (member.joinedAt == null) | ||||||
|                             const Icon(Symbols.pending_actions, size: 20), |                             const Icon(Symbols.pending_actions, size: 20), | ||||||
|                         ], |                         ], | ||||||
| @@ -716,7 +745,7 @@ class _ChatMemberListSheet extends HookConsumerWidget { | |||||||
|                                 ? 'permissionModerator' |                                 ? 'permissionModerator' | ||||||
|                                 : 'permissionMember', |                                 : 'permissionMember', | ||||||
|                           ).tr(), |                           ).tr(), | ||||||
|                           Text('dotSeparator').bold().padding(horizontal: 6), |                           Text('·').bold().padding(horizontal: 6), | ||||||
|                           Expanded(child: Text("@${member.account.name}")), |                           Expanded(child: Text("@${member.account.name}")), | ||||||
|                         ], |                         ], | ||||||
|                       ), |                       ), | ||||||
|   | |||||||
| @@ -7,7 +7,7 @@ part of 'room_detail.dart'; | |||||||
| // ************************************************************************** | // ************************************************************************** | ||||||
|  |  | ||||||
| String _$totalMessagesCountHash() => | String _$totalMessagesCountHash() => | ||||||
|     r'a15c03461f25c2d4d39c0926509bf626ae2550a6'; |     r'd55f1507aba2acdce5e468c1c2e15dba7640c571'; | ||||||
|  |  | ||||||
| /// Copied from Dart SDK | /// Copied from Dart SDK | ||||||
| class _SystemHash { | class _SystemHash { | ||||||
|   | |||||||
| @@ -11,7 +11,7 @@ import 'package:island/models/publisher.dart'; | |||||||
| import 'package:island/pods/network.dart'; | import 'package:island/pods/network.dart'; | ||||||
| import 'package:island/screens/creators/publishers.dart'; | import 'package:island/screens/creators/publishers.dart'; | ||||||
| import 'package:island/services/responsive.dart'; | import 'package:island/services/responsive.dart'; | ||||||
| import 'package:island/services/text.dart'; | import 'package:island/utils/text.dart'; | ||||||
| import 'package:island/widgets/account/account_picker.dart'; | import 'package:island/widgets/account/account_picker.dart'; | ||||||
| import 'package:island/widgets/alert.dart'; | import 'package:island/widgets/alert.dart'; | ||||||
| import 'package:island/widgets/app_scaffold.dart'; | import 'package:island/widgets/app_scaffold.dart'; | ||||||
|   | |||||||
| @@ -9,6 +9,7 @@ import 'package:island/widgets/poll/poll_feedback.dart'; | |||||||
| import 'package:material_symbols_icons/symbols.dart'; | import 'package:material_symbols_icons/symbols.dart'; | ||||||
| import 'package:riverpod_annotation/riverpod_annotation.dart'; | import 'package:riverpod_annotation/riverpod_annotation.dart'; | ||||||
| import 'package:riverpod_paging_utils/riverpod_paging_utils.dart'; | import 'package:riverpod_paging_utils/riverpod_paging_utils.dart'; | ||||||
|  | import 'package:island/widgets/extended_refresh_indicator.dart'; | ||||||
|  |  | ||||||
| part 'poll_list.g.dart'; | part 'poll_list.g.dart'; | ||||||
|  |  | ||||||
| @@ -86,7 +87,7 @@ class CreatorPollListScreen extends HookConsumerWidget { | |||||||
|         onPressed: () => _createPoll(context), |         onPressed: () => _createPoll(context), | ||||||
|         child: const Icon(Icons.add), |         child: const Icon(Icons.add), | ||||||
|       ), |       ), | ||||||
|       body: RefreshIndicator( |       body: ExtendedRefreshIndicator( | ||||||
|         onRefresh: () => ref.refresh(pollListNotifierProvider(pubName).future), |         onRefresh: () => ref.refresh(pollListNotifierProvider(pubName).future), | ||||||
|         child: CustomScrollView( |         child: CustomScrollView( | ||||||
|           slivers: [ |           slivers: [ | ||||||
|   | |||||||
| @@ -4,6 +4,7 @@ import 'package:go_router/go_router.dart'; | |||||||
| import 'package:island/pods/webfeed.dart'; | import 'package:island/pods/webfeed.dart'; | ||||||
| import 'package:island/widgets/app_scaffold.dart'; | import 'package:island/widgets/app_scaffold.dart'; | ||||||
| import 'package:island/widgets/empty_state.dart'; | import 'package:island/widgets/empty_state.dart'; | ||||||
|  | import 'package:island/widgets/extended_refresh_indicator.dart'; | ||||||
| import 'package:material_symbols_icons/symbols.dart'; | import 'package:material_symbols_icons/symbols.dart'; | ||||||
|  |  | ||||||
| class WebFeedListScreen extends ConsumerWidget { | class WebFeedListScreen extends ConsumerWidget { | ||||||
| @@ -20,7 +21,10 @@ class WebFeedListScreen extends ConsumerWidget { | |||||||
|       floatingActionButton: FloatingActionButton( |       floatingActionButton: FloatingActionButton( | ||||||
|         child: const Icon(Symbols.add), |         child: const Icon(Symbols.add), | ||||||
|         onPressed: () { |         onPressed: () { | ||||||
|           context.pushNamed('creatorFeedNew', pathParameters: {'name': pubName}); |           context.pushNamed( | ||||||
|  |             'creatorFeedNew', | ||||||
|  |             pathParameters: {'name': pubName}, | ||||||
|  |           ); | ||||||
|         }, |         }, | ||||||
|       ), |       ), | ||||||
|       body: feedsAsync.when( |       body: feedsAsync.when( | ||||||
| @@ -32,7 +36,7 @@ class WebFeedListScreen extends ConsumerWidget { | |||||||
|               description: 'Add a new web feed to get started', |               description: 'Add a new web feed to get started', | ||||||
|             ); |             ); | ||||||
|           } |           } | ||||||
|           return RefreshIndicator( |           return ExtendedRefreshIndicator( | ||||||
|             onRefresh: () => ref.refresh(webFeedListProvider(pubName).future), |             onRefresh: () => ref.refresh(webFeedListProvider(pubName).future), | ||||||
|             child: ListView.builder( |             child: ListView.builder( | ||||||
|               padding: EdgeInsets.only(top: 8), |               padding: EdgeInsets.only(top: 8), | ||||||
| @@ -62,7 +66,10 @@ class WebFeedListScreen extends ConsumerWidget { | |||||||
|                     ), |                     ), | ||||||
|                     trailing: const Icon(Symbols.chevron_right), |                     trailing: const Icon(Symbols.chevron_right), | ||||||
|                     onTap: () { |                     onTap: () { | ||||||
|                       context.pushNamed('creatorFeedEdit', pathParameters: {'name': pubName, 'feedId': feed.id}); |                       context.pushNamed( | ||||||
|  |                         'creatorFeedEdit', | ||||||
|  |                         pathParameters: {'name': pubName, 'feedId': feed.id}, | ||||||
|  |                       ); | ||||||
|                     }, |                     }, | ||||||
|                   ), |                   ), | ||||||
|                 ); |                 ); | ||||||
|   | |||||||
| @@ -9,6 +9,7 @@ import 'package:island/widgets/content/cloud_files.dart'; | |||||||
| import 'package:island/widgets/response.dart'; | import 'package:island/widgets/response.dart'; | ||||||
| import 'package:material_symbols_icons/symbols.dart'; | import 'package:material_symbols_icons/symbols.dart'; | ||||||
| import 'package:riverpod_annotation/riverpod_annotation.dart'; | import 'package:riverpod_annotation/riverpod_annotation.dart'; | ||||||
|  | import 'package:island/widgets/extended_refresh_indicator.dart'; | ||||||
|  |  | ||||||
| part 'bots.g.dart'; | part 'bots.g.dart'; | ||||||
|  |  | ||||||
| @@ -60,7 +61,7 @@ class BotsScreen extends HookConsumerWidget { | |||||||
|             ), |             ), | ||||||
|           ); |           ); | ||||||
|         } |         } | ||||||
|         return RefreshIndicator( |         return ExtendedRefreshIndicator( | ||||||
|           onRefresh: |           onRefresh: | ||||||
|               () => ref.refresh(botsProvider(publisherName, projectId).future), |               () => ref.refresh(botsProvider(publisherName, projectId).future), | ||||||
|           child: ListView.builder( |           child: ListView.builder( | ||||||
|   | |||||||
| @@ -72,7 +72,7 @@ class MarketplaceWebFeedsScreen extends HookConsumerWidget { | |||||||
|         searchController.clear(); |         searchController.clear(); | ||||||
|       } |       } | ||||||
|       return null; |       return null; | ||||||
|     }, [query.value]); |     }, [query]); | ||||||
|  |  | ||||||
|     // Clean up timer on dispose |     // Clean up timer on dispose | ||||||
|     useEffect(() { |     useEffect(() { | ||||||
|   | |||||||
| @@ -27,6 +27,7 @@ import 'package:island/pods/network.dart'; | |||||||
| import 'package:island/widgets/realm/realm_card.dart'; | import 'package:island/widgets/realm/realm_card.dart'; | ||||||
| import 'package:island/widgets/publisher/publisher_card.dart'; | import 'package:island/widgets/publisher/publisher_card.dart'; | ||||||
| import 'package:island/widgets/web_article_card.dart'; | import 'package:island/widgets/web_article_card.dart'; | ||||||
|  | import 'package:island/widgets/extended_refresh_indicator.dart'; | ||||||
| import 'package:styled_widget/styled_widget.dart'; | import 'package:styled_widget/styled_widget.dart'; | ||||||
|  |  | ||||||
| part 'explore.g.dart'; | part 'explore.g.dart'; | ||||||
| @@ -368,7 +369,7 @@ class ExploreScreen extends HookConsumerWidget { | |||||||
|  |  | ||||||
|     final isWide = isWideScreen(context); |     final isWide = isWideScreen(context); | ||||||
|  |  | ||||||
|     return RefreshIndicator( |     return ExtendedRefreshIndicator( | ||||||
|       onRefresh: () => Future.sync(activitiesNotifier.forceRefresh), |       onRefresh: () => Future.sync(activitiesNotifier.forceRefresh), | ||||||
|       child: PagingHelperView( |       child: PagingHelperView( | ||||||
|         provider: activityListNotifierProvider(filter), |         provider: activityListNotifierProvider(filter), | ||||||
| @@ -399,6 +400,69 @@ class _DiscoveryActivityItem extends StatelessWidget { | |||||||
|     final items = data['items'] as List; |     final items = data['items'] as List; | ||||||
|     final type = items.firstOrNull?['type'] ?? 'unknown'; |     final type = items.firstOrNull?['type'] ?? 'unknown'; | ||||||
|  |  | ||||||
|  |     var flexWeights = isWideScreen(context) ? <int>[3, 2, 1] : <int>[4, 1]; | ||||||
|  |     if (type == 'post') flexWeights = <int>[3, 2]; | ||||||
|  |  | ||||||
|  |     final height = type == 'post' ? 280.0 : 180.0; | ||||||
|  |  | ||||||
|  |     final contentWidget = switch (type) { | ||||||
|  |       'post' => ListView.separated( | ||||||
|  |         scrollDirection: Axis.horizontal, | ||||||
|  |         itemCount: items.length, | ||||||
|  |         separatorBuilder: (context, index) => const Gap(12), | ||||||
|  |         padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4), | ||||||
|  |         itemBuilder: (context, index) { | ||||||
|  |           final item = items[index]; | ||||||
|  |           return Container( | ||||||
|  |             width: 320, | ||||||
|  |             decoration: BoxDecoration( | ||||||
|  |               border: Border.all( | ||||||
|  |                 width: 1 / MediaQuery.of(context).devicePixelRatio, | ||||||
|  |                 color: Theme.of(context).dividerColor.withOpacity(0.5), | ||||||
|  |               ), | ||||||
|  |               borderRadius: const BorderRadius.all(Radius.circular(8)), | ||||||
|  |             ), | ||||||
|  |             child: ClipRRect( | ||||||
|  |               borderRadius: const BorderRadius.all(Radius.circular(8)), | ||||||
|  |               child: SingleChildScrollView( | ||||||
|  |                 child: PostActionableItem( | ||||||
|  |                   item: SnPost.fromJson(item['data']), | ||||||
|  |                   isCompact: true, | ||||||
|  |                 ), | ||||||
|  |               ), | ||||||
|  |             ), | ||||||
|  |           ); | ||||||
|  |         }, | ||||||
|  |       ), | ||||||
|  |       _ => CarouselView.weighted( | ||||||
|  |         flexWeights: flexWeights, | ||||||
|  |         consumeMaxWeight: false, | ||||||
|  |         enableSplash: false, | ||||||
|  |         shape: const RoundedRectangleBorder( | ||||||
|  |           borderRadius: BorderRadius.all(Radius.circular(8)), | ||||||
|  |         ), | ||||||
|  |         itemSnapping: false, | ||||||
|  |         children: [ | ||||||
|  |           for (final item in items) | ||||||
|  |             switch (type) { | ||||||
|  |               'realm' => RealmCard( | ||||||
|  |                 realm: SnRealm.fromJson(item['data']), | ||||||
|  |                 maxWidth: 280, | ||||||
|  |               ), | ||||||
|  |               'publisher' => PublisherCard( | ||||||
|  |                 publisher: SnPublisher.fromJson(item['data']), | ||||||
|  |                 maxWidth: 280, | ||||||
|  |               ), | ||||||
|  |               'article' => WebArticleCard( | ||||||
|  |                 article: SnWebArticle.fromJson(item['data']), | ||||||
|  |                 maxWidth: 280, | ||||||
|  |               ), | ||||||
|  |               _ => const Placeholder(), | ||||||
|  |             }, | ||||||
|  |         ], | ||||||
|  |       ), | ||||||
|  |     }; | ||||||
|  |  | ||||||
|     return Card( |     return Card( | ||||||
|       margin: EdgeInsets.symmetric(horizontal: 8, vertical: 4), |       margin: EdgeInsets.symmetric(horizontal: 8, vertical: 4), | ||||||
|       child: Column( |       child: Column( | ||||||
| @@ -407,13 +471,20 @@ class _DiscoveryActivityItem extends StatelessWidget { | |||||||
|           Row( |           Row( | ||||||
|             crossAxisAlignment: CrossAxisAlignment.center, |             crossAxisAlignment: CrossAxisAlignment.center, | ||||||
|             children: [ |             children: [ | ||||||
|               const Icon(Symbols.explore, size: 19), |               Icon(switch (type) { | ||||||
|  |                 'realm' => Symbols.public, | ||||||
|  |                 'publisher' => Symbols.account_circle, | ||||||
|  |                 'article' => Symbols.auto_stories, | ||||||
|  |                 'post' => Symbols.shuffle, | ||||||
|  |                 _ => Symbols.explore, | ||||||
|  |               }, size: 19), | ||||||
|               const Gap(8), |               const Gap(8), | ||||||
|               Text( |               Text( | ||||||
|                 (switch (type) { |                 (switch (type) { | ||||||
|                   'realm' => 'discoverRealms', |                   'realm' => 'discoverRealms', | ||||||
|                   'publisher' => 'discoverPublishers', |                   'publisher' => 'discoverPublishers', | ||||||
|                   'article' => 'discoverWebArticles', |                   'article' => 'discoverWebArticles', | ||||||
|  |                   'post' => 'discoverShuffledPost', | ||||||
|                   _ => 'unknown', |                   _ => 'unknown', | ||||||
|                 }).tr(), |                 }).tr(), | ||||||
|                 style: Theme.of(context).textTheme.titleMedium, |                 style: Theme.of(context).textTheme.titleMedium, | ||||||
| @@ -421,37 +492,8 @@ class _DiscoveryActivityItem extends StatelessWidget { | |||||||
|             ], |             ], | ||||||
|           ).padding(horizontal: 20, top: 8, bottom: 4), |           ).padding(horizontal: 20, top: 8, bottom: 4), | ||||||
|           SizedBox( |           SizedBox( | ||||||
|             height: 180, |             height: height, | ||||||
|             child: ConstrainedBox( |             child: contentWidget, | ||||||
|               constraints: const BoxConstraints(maxHeight: 200), |  | ||||||
|               child: CarouselView.weighted( |  | ||||||
|                 flexWeights: |  | ||||||
|                     isWideScreen(context) ? <int>[3, 2, 1] : <int>[4, 1], |  | ||||||
|                 consumeMaxWeight: false, |  | ||||||
|                 enableSplash: false, |  | ||||||
|                 shape: RoundedRectangleBorder( |  | ||||||
|                   borderRadius: BorderRadius.all(Radius.circular(8)), |  | ||||||
|                 ), |  | ||||||
|                 children: [ |  | ||||||
|                   for (final item in items) |  | ||||||
|                     switch (type) { |  | ||||||
|                       'realm' => RealmCard( |  | ||||||
|                         realm: SnRealm.fromJson(item['data']), |  | ||||||
|                         maxWidth: 280, |  | ||||||
|                       ), |  | ||||||
|                       'publisher' => PublisherCard( |  | ||||||
|                         publisher: SnPublisher.fromJson(item['data']), |  | ||||||
|                         maxWidth: 280, |  | ||||||
|                       ), |  | ||||||
|                       'article' => WebArticleCard( |  | ||||||
|                         article: SnWebArticle.fromJson(item['data']), |  | ||||||
|                         maxWidth: 280, |  | ||||||
|                       ), |  | ||||||
|                       _ => Placeholder(), |  | ||||||
|                     }, |  | ||||||
|                 ], |  | ||||||
|               ), |  | ||||||
|             ), |  | ||||||
|           ).padding(bottom: 8, horizontal: 8), |           ).padding(bottom: 8, horizontal: 8), | ||||||
|         ], |         ], | ||||||
|       ), |       ), | ||||||
| @@ -569,7 +611,8 @@ class ActivityListNotifier extends _$ActivityListNotifier | |||||||
|       if (cursor != null) 'cursor': cursor, |       if (cursor != null) 'cursor': cursor, | ||||||
|       'take': take, |       'take': take, | ||||||
|       if (filter != null) 'filter': filter, |       if (filter != null) 'filter': filter, | ||||||
|       if (kDebugMode) 'debugInclude': 'realms,publishers,articles', |       if (kDebugMode) | ||||||
|  |         'debugInclude': 'realms,publishers,articles,shuffledPosts', | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     final response = await client.get( |     final response = await client.get( | ||||||
| @@ -584,12 +627,13 @@ class ActivityListNotifier extends _$ActivityListNotifier | |||||||
|  |  | ||||||
|     final hasMore = (items.firstOrNull?.type ?? 'empty') != 'empty'; |     final hasMore = (items.firstOrNull?.type ?? 'empty') != 'empty'; | ||||||
|     final nextCursor = |     final nextCursor = | ||||||
|         items |         items.isNotEmpty | ||||||
|             .map((x) => x.createdAt) |             ? items | ||||||
|             .lastOrNull |                 .map((x) => x.createdAt) | ||||||
|             ?.toUtc() |                 .reduce((a, b) => a.isBefore(b) ? a : b) | ||||||
|             .toIso8601String() |                 .toUtc() | ||||||
|             .toString(); |                 .toIso8601String() | ||||||
|  |             : null; | ||||||
|  |  | ||||||
|     return CursorPagingData( |     return CursorPagingData( | ||||||
|       items: items, |       items: items, | ||||||
|   | |||||||
| @@ -7,7 +7,7 @@ part of 'explore.dart'; | |||||||
| // ************************************************************************** | // ************************************************************************** | ||||||
|  |  | ||||||
| String _$activityListNotifierHash() => | String _$activityListNotifierHash() => | ||||||
|     r'b75fd5c08d5f84ca433e16b7387d317ea72b91c9'; |     r'167021cada54da7c8d8437eef1ffb387a92ea2e3'; | ||||||
|  |  | ||||||
| /// Copied from Dart SDK | /// Copied from Dart SDK | ||||||
| class _SystemHash { | class _SystemHash { | ||||||
|   | |||||||
| @@ -1,15 +1,26 @@ | |||||||
| import 'package:easy_localization/easy_localization.dart'; | import 'package:easy_localization/easy_localization.dart'; | ||||||
|  | import 'package:flutter/foundation.dart'; | ||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:flutter/services.dart'; | ||||||
| import 'package:gap/gap.dart'; | import 'package:gap/gap.dart'; | ||||||
|  | import 'package:go_router/go_router.dart'; | ||||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
| import 'package:island/models/post.dart'; | import 'package:island/models/post.dart'; | ||||||
| import 'package:island/pods/network.dart'; | import 'package:island/pods/network.dart'; | ||||||
| import 'package:island/pods/userinfo.dart'; | import 'package:island/pods/userinfo.dart'; | ||||||
|  | import 'package:island/screens/posts/compose.dart'; | ||||||
|  | import 'package:island/widgets/alert.dart'; | ||||||
| import 'package:island/widgets/app_scaffold.dart'; | import 'package:island/widgets/app_scaffold.dart'; | ||||||
|  | import 'package:island/widgets/extended_refresh_indicator.dart'; | ||||||
| import 'package:island/widgets/post/post_item.dart'; | import 'package:island/widgets/post/post_item.dart'; | ||||||
|  | import 'package:island/widgets/post/post_pin_sheet.dart'; | ||||||
| import 'package:island/widgets/post/post_quick_reply.dart'; | import 'package:island/widgets/post/post_quick_reply.dart'; | ||||||
| import 'package:island/widgets/post/post_replies.dart'; | import 'package:island/widgets/post/post_replies.dart'; | ||||||
| import 'package:island/widgets/response.dart'; | import 'package:island/widgets/response.dart'; | ||||||
|  | import 'package:island/utils/share_utils.dart'; | ||||||
|  | import 'package:island/widgets/safety/abuse_report_helper.dart'; | ||||||
|  | import 'package:island/widgets/share/share_sheet.dart'; | ||||||
|  | import 'package:material_symbols_icons/symbols.dart'; | ||||||
| import 'package:riverpod_annotation/riverpod_annotation.dart'; | import 'package:riverpod_annotation/riverpod_annotation.dart'; | ||||||
| import 'package:styled_widget/styled_widget.dart'; | import 'package:styled_widget/styled_widget.dart'; | ||||||
|  |  | ||||||
| @@ -46,6 +57,321 @@ class PostState extends StateNotifier<AsyncValue<SnPost?>> { | |||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | class PostActionButtons extends HookConsumerWidget { | ||||||
|  |   final SnPost post; | ||||||
|  |   final EdgeInsets renderingPadding; | ||||||
|  |   final VoidCallback? onRefresh; | ||||||
|  |   final Function(SnPost)? onUpdate; | ||||||
|  |  | ||||||
|  |   const PostActionButtons({ | ||||||
|  |     super.key, | ||||||
|  |     required this.post, | ||||||
|  |     this.renderingPadding = EdgeInsets.zero, | ||||||
|  |     this.onRefresh, | ||||||
|  |     this.onUpdate, | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|  |     final user = ref.watch(userInfoProvider); | ||||||
|  |     final isAuthor = | ||||||
|  |         user.value != null && user.value?.id == post.publisher.accountId; | ||||||
|  |  | ||||||
|  |     final actions = <Widget>[]; | ||||||
|  |  | ||||||
|  |     const kButtonHeight = 40.0; | ||||||
|  |     const kButtonRadius = 20.0; | ||||||
|  |  | ||||||
|  |     // 1. Author-only actions first | ||||||
|  |     if (isAuthor) { | ||||||
|  |       // Combined edit/delete actions using custom segmented-style buttons | ||||||
|  |       final editButtons = <Widget>[ | ||||||
|  |         FilledButton.tonal( | ||||||
|  |           onPressed: () { | ||||||
|  |             context.pushNamed('postEdit', pathParameters: {'id': post.id}).then( | ||||||
|  |               (value) { | ||||||
|  |                 if (value != null) { | ||||||
|  |                   onRefresh?.call(); | ||||||
|  |                 } | ||||||
|  |               }, | ||||||
|  |             ); | ||||||
|  |           }, | ||||||
|  |           style: FilledButton.styleFrom( | ||||||
|  |             shape: const RoundedRectangleBorder( | ||||||
|  |               borderRadius: BorderRadius.only( | ||||||
|  |                 topLeft: Radius.circular(kButtonRadius), | ||||||
|  |                 bottomLeft: Radius.circular(kButtonRadius), | ||||||
|  |               ), | ||||||
|  |             ), | ||||||
|  |           ), | ||||||
|  |           child: Row( | ||||||
|  |             mainAxisSize: MainAxisSize.min, | ||||||
|  |             children: [ | ||||||
|  |               const Icon(Symbols.edit, size: 18), | ||||||
|  |               const Gap(4), | ||||||
|  |               Text('edit'.tr()), | ||||||
|  |             ], | ||||||
|  |           ), | ||||||
|  |         ), | ||||||
|  |         Tooltip( | ||||||
|  |           message: 'delete'.tr(), | ||||||
|  |           child: FilledButton.tonal( | ||||||
|  |             onPressed: () { | ||||||
|  |               showConfirmAlert('deletePostHint'.tr(), 'deletePost'.tr()).then(( | ||||||
|  |                 confirm, | ||||||
|  |               ) { | ||||||
|  |                 if (confirm) { | ||||||
|  |                   final client = ref.watch(apiClientProvider); | ||||||
|  |                   client | ||||||
|  |                       .delete('/sphere/posts/${post.id}') | ||||||
|  |                       .catchError((err) { | ||||||
|  |                         showErrorAlert(err); | ||||||
|  |                         return err; | ||||||
|  |                       }) | ||||||
|  |                       .then((_) { | ||||||
|  |                         onRefresh?.call(); | ||||||
|  |                       }); | ||||||
|  |                 } | ||||||
|  |               }); | ||||||
|  |             }, | ||||||
|  |             style: FilledButton.styleFrom( | ||||||
|  |               shape: const RoundedRectangleBorder( | ||||||
|  |                 borderRadius: BorderRadius.only( | ||||||
|  |                   topRight: Radius.circular(kButtonRadius), | ||||||
|  |                   bottomRight: Radius.circular(kButtonRadius), | ||||||
|  |                 ), | ||||||
|  |               ), | ||||||
|  |             ), | ||||||
|  |             child: const Icon(Symbols.delete, size: 18), | ||||||
|  |           ), | ||||||
|  |         ), | ||||||
|  |       ]; | ||||||
|  |  | ||||||
|  |       actions.add( | ||||||
|  |         Row( | ||||||
|  |           mainAxisSize: MainAxisSize.min, | ||||||
|  |           children: | ||||||
|  |               editButtons | ||||||
|  |                   .map((e) => SizedBox(height: kButtonHeight, child: e)) | ||||||
|  |                   .expand((widget) => [widget, const VerticalDivider(width: 1)]) | ||||||
|  |                   .toList() | ||||||
|  |                 ..removeLast(), | ||||||
|  |         ), | ||||||
|  |       ); | ||||||
|  |  | ||||||
|  |       // Pin/Unpin actions (also author-only) | ||||||
|  |       if (post.pinMode == null) { | ||||||
|  |         actions.add( | ||||||
|  |           FilledButton.tonalIcon( | ||||||
|  |             onPressed: () { | ||||||
|  |               showModalBottomSheet( | ||||||
|  |                 context: context, | ||||||
|  |                 isScrollControlled: true, | ||||||
|  |                 builder: (context) => PostPinSheet(post: post), | ||||||
|  |               ).then((value) { | ||||||
|  |                 if (value is int) { | ||||||
|  |                   onUpdate?.call(post.copyWith(pinMode: value)); | ||||||
|  |                 } | ||||||
|  |               }); | ||||||
|  |             }, | ||||||
|  |             icon: const Icon(Symbols.keep), | ||||||
|  |             label: Text('pinPost'.tr()), | ||||||
|  |           ), | ||||||
|  |         ); | ||||||
|  |       } else { | ||||||
|  |         actions.add( | ||||||
|  |           FilledButton.tonalIcon( | ||||||
|  |             onPressed: () { | ||||||
|  |               showConfirmAlert('unpinPostHint'.tr(), 'unpinPost'.tr()).then(( | ||||||
|  |                 confirm, | ||||||
|  |               ) async { | ||||||
|  |                 if (confirm) { | ||||||
|  |                   final client = ref.watch(apiClientProvider); | ||||||
|  |                   try { | ||||||
|  |                     if (context.mounted) showLoadingModal(context); | ||||||
|  |                     await client.delete('/sphere/posts/${post.id}/pin'); | ||||||
|  |                     onUpdate?.call(post.copyWith(pinMode: null)); | ||||||
|  |                   } catch (err) { | ||||||
|  |                     showErrorAlert(err); | ||||||
|  |                   } finally { | ||||||
|  |                     if (context.mounted) hideLoadingModal(context); | ||||||
|  |                   } | ||||||
|  |                 } | ||||||
|  |               }); | ||||||
|  |             }, | ||||||
|  |             icon: const Icon(Symbols.keep_off), | ||||||
|  |             label: Text('unpinPost'.tr()), | ||||||
|  |           ), | ||||||
|  |         ); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // 2. Replies and forwards | ||||||
|  |     final replyButtons = <Widget>[ | ||||||
|  |       FilledButton.tonal( | ||||||
|  |         onPressed: () { | ||||||
|  |           context.pushNamed( | ||||||
|  |             'postCompose', | ||||||
|  |             extra: PostComposeInitialState(replyingTo: post), | ||||||
|  |           ); | ||||||
|  |         }, | ||||||
|  |         style: FilledButton.styleFrom( | ||||||
|  |           shape: const RoundedRectangleBorder( | ||||||
|  |             borderRadius: BorderRadius.only( | ||||||
|  |               topLeft: Radius.circular(kButtonRadius), | ||||||
|  |               bottomLeft: Radius.circular(kButtonRadius), | ||||||
|  |             ), | ||||||
|  |           ), | ||||||
|  |         ), | ||||||
|  |         child: Row( | ||||||
|  |           mainAxisSize: MainAxisSize.min, | ||||||
|  |           children: [ | ||||||
|  |             const Icon(Symbols.reply, size: 18), | ||||||
|  |             const Gap(4), | ||||||
|  |             Text('reply'.tr()), | ||||||
|  |           ], | ||||||
|  |         ), | ||||||
|  |       ), | ||||||
|  |       Tooltip( | ||||||
|  |         message: 'forward'.tr(), | ||||||
|  |         child: FilledButton.tonal( | ||||||
|  |           onPressed: () { | ||||||
|  |             context.pushNamed( | ||||||
|  |               'postCompose', | ||||||
|  |               extra: PostComposeInitialState(forwardingTo: post), | ||||||
|  |             ); | ||||||
|  |           }, | ||||||
|  |           style: FilledButton.styleFrom( | ||||||
|  |             shape: const RoundedRectangleBorder( | ||||||
|  |               borderRadius: BorderRadius.only( | ||||||
|  |                 topRight: Radius.circular(kButtonRadius), | ||||||
|  |                 bottomRight: Radius.circular(kButtonRadius), | ||||||
|  |               ), | ||||||
|  |             ), | ||||||
|  |           ), | ||||||
|  |           child: const Icon(Symbols.forward, size: 18), | ||||||
|  |         ), | ||||||
|  |       ), | ||||||
|  |     ]; | ||||||
|  |  | ||||||
|  |     actions.add( | ||||||
|  |       Row( | ||||||
|  |         mainAxisSize: MainAxisSize.min, | ||||||
|  |         children: | ||||||
|  |             replyButtons | ||||||
|  |                 .map((e) => SizedBox(height: kButtonHeight, child: e)) | ||||||
|  |                 .toList(), | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  |     // 3. Share, copy link, and report | ||||||
|  |     final shareButtons = <Widget>[ | ||||||
|  |       FilledButton.tonal( | ||||||
|  |         onPressed: () { | ||||||
|  |           showShareSheetLink( | ||||||
|  |             context: context, | ||||||
|  |             link: 'https://solian.app/posts/${post.id}', | ||||||
|  |             title: 'sharePost'.tr(), | ||||||
|  |             toSystem: true, | ||||||
|  |           ); | ||||||
|  |         }, | ||||||
|  |         style: FilledButton.styleFrom( | ||||||
|  |           shape: const RoundedRectangleBorder( | ||||||
|  |             borderRadius: BorderRadius.only( | ||||||
|  |               topLeft: Radius.circular(kButtonRadius), | ||||||
|  |               bottomLeft: Radius.circular(kButtonRadius), | ||||||
|  |             ), | ||||||
|  |           ), | ||||||
|  |         ), | ||||||
|  |         child: Row( | ||||||
|  |           mainAxisSize: MainAxisSize.min, | ||||||
|  |           children: [ | ||||||
|  |             const Icon(Symbols.share, size: 18), | ||||||
|  |             const Gap(4), | ||||||
|  |             Text('share'.tr()), | ||||||
|  |           ], | ||||||
|  |         ), | ||||||
|  |       ), | ||||||
|  |     ]; | ||||||
|  |  | ||||||
|  |     if (!kIsWeb) { | ||||||
|  |       shareButtons.add( | ||||||
|  |         Tooltip( | ||||||
|  |           message: 'sharePostPhoto'.tr(), | ||||||
|  |           child: FilledButton.tonal( | ||||||
|  |             onPressed: () => sharePostAsScreenshot(context, ref, post), | ||||||
|  |             style: FilledButton.styleFrom( | ||||||
|  |               shape: const RoundedRectangleBorder( | ||||||
|  |                 borderRadius: BorderRadius.only( | ||||||
|  |                   topRight: Radius.circular(kButtonRadius), | ||||||
|  |                   bottomRight: Radius.circular(kButtonRadius), | ||||||
|  |                 ), | ||||||
|  |               ), | ||||||
|  |             ), | ||||||
|  |             child: const Icon(Symbols.share_reviews, size: 18), | ||||||
|  |           ), | ||||||
|  |         ), | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     actions.add( | ||||||
|  |       Row( | ||||||
|  |         mainAxisSize: MainAxisSize.min, | ||||||
|  |         children: | ||||||
|  |             shareButtons | ||||||
|  |                 .map((e) => SizedBox(height: kButtonHeight, child: e)) | ||||||
|  |                 .expand((widget) => [widget, const VerticalDivider(width: 1)]) | ||||||
|  |                 .toList() | ||||||
|  |               ..removeLast(), | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  |     actions.add( | ||||||
|  |       FilledButton.tonalIcon( | ||||||
|  |         onPressed: () { | ||||||
|  |           Clipboard.setData( | ||||||
|  |             ClipboardData(text: 'https://solian.app/posts/${post.id}'), | ||||||
|  |           ); | ||||||
|  |         }, | ||||||
|  |         icon: const Icon(Symbols.link), | ||||||
|  |         label: Text('copyLink'.tr()), | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  |     actions.add( | ||||||
|  |       FilledButton.tonalIcon( | ||||||
|  |         onPressed: () { | ||||||
|  |           showAbuseReportSheet(context, resourceIdentifier: 'post/${post.id}'); | ||||||
|  |         }, | ||||||
|  |         icon: const Icon(Symbols.flag), | ||||||
|  |         label: Text('abuseReport'.tr()), | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  |     // Add gaps between actions (excluding first one) using FP style | ||||||
|  |     final children = | ||||||
|  |         actions.asMap().entries.expand((entry) { | ||||||
|  |           final index = entry.key; | ||||||
|  |           final action = entry.value; | ||||||
|  |           if (index == 0) { | ||||||
|  |             return [action]; | ||||||
|  |           } else { | ||||||
|  |             return [const Gap(8), action]; | ||||||
|  |           } | ||||||
|  |         }).toList(); | ||||||
|  |  | ||||||
|  |     return Container( | ||||||
|  |       height: kButtonHeight, | ||||||
|  |       margin: const EdgeInsets.only(bottom: 12), | ||||||
|  |       child: ListView( | ||||||
|  |         scrollDirection: Axis.horizontal, | ||||||
|  |         padding: EdgeInsets.symmetric(horizontal: renderingPadding.horizontal), | ||||||
|  |         children: children, | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
| class PostDetailScreen extends HookConsumerWidget { | class PostDetailScreen extends HookConsumerWidget { | ||||||
|   final String id; |   final String id; | ||||||
|   const PostDetailScreen({super.key, required this.id}); |   const PostDetailScreen({super.key, required this.id}); | ||||||
| @@ -66,29 +392,58 @@ class PostDetailScreen extends HookConsumerWidget { | |||||||
|           return Stack( |           return Stack( | ||||||
|             fit: StackFit.expand, |             fit: StackFit.expand, | ||||||
|             children: [ |             children: [ | ||||||
|               CustomScrollView( |               ExtendedRefreshIndicator( | ||||||
|                 slivers: [ |                 onRefresh: () async { | ||||||
|                   SliverToBoxAdapter( |                   ref.invalidate(postProvider(id)); | ||||||
|                     child: Center( |                   ref.invalidate(postRepliesNotifierProvider(id)); | ||||||
|                       child: ConstrainedBox( |                 }, | ||||||
|                         constraints: BoxConstraints(maxWidth: 600), |                 child: CustomScrollView( | ||||||
|                         child: PostItem( |                   physics: const AlwaysScrollableScrollPhysics(), | ||||||
|                           item: post!, |                   slivers: [ | ||||||
|                           isFullPost: true, |                     SliverToBoxAdapter( | ||||||
|                           isEmbedReply: false, |                       child: Center( | ||||||
|                           onUpdate: (newItem) { |                         child: ConstrainedBox( | ||||||
|                             // Update the local state with the new post data |                           constraints: BoxConstraints(maxWidth: 600), | ||||||
|                             ref |                           child: PostItem( | ||||||
|                                 .read(postStateProvider(id).notifier) |                             item: post!, | ||||||
|                                 .updatePost(newItem); |                             isFullPost: true, | ||||||
|                           }, |                             isEmbedReply: false, | ||||||
|  |                             onUpdate: (newItem) { | ||||||
|  |                               // Update the local state with the new post data | ||||||
|  |                               ref | ||||||
|  |                                   .read(postStateProvider(id).notifier) | ||||||
|  |                                   .updatePost(newItem); | ||||||
|  |                             }, | ||||||
|  |                           ), | ||||||
|                         ), |                         ), | ||||||
|                       ), |                       ), | ||||||
|                     ), |                     ), | ||||||
|                   ), |                     SliverToBoxAdapter( | ||||||
|                   PostRepliesList(postId: id, maxWidth: 600), |                       child: Center( | ||||||
|                   SliverGap(MediaQuery.of(context).padding.bottom + 80), |                         child: ConstrainedBox( | ||||||
|                 ], |                           constraints: BoxConstraints(maxWidth: 600), | ||||||
|  |                           child: PostActionButtons( | ||||||
|  |                             post: post, | ||||||
|  |                             renderingPadding: const EdgeInsets.symmetric( | ||||||
|  |                               horizontal: 8, | ||||||
|  |                             ), | ||||||
|  |                             onRefresh: () { | ||||||
|  |                               ref.invalidate(postProvider(id)); | ||||||
|  |                               ref.invalidate(postRepliesNotifierProvider(id)); | ||||||
|  |                             }, | ||||||
|  |                             onUpdate: (newItem) { | ||||||
|  |                               ref | ||||||
|  |                                   .read(postStateProvider(id).notifier) | ||||||
|  |                                   .updatePost(newItem); | ||||||
|  |                             }, | ||||||
|  |                           ), | ||||||
|  |                         ), | ||||||
|  |                       ), | ||||||
|  |                     ), | ||||||
|  |                     PostRepliesList(postId: id, maxWidth: 600), | ||||||
|  |                     SliverGap(MediaQuery.of(context).padding.bottom + 80), | ||||||
|  |                   ], | ||||||
|  |                 ), | ||||||
|               ), |               ), | ||||||
|               if (user.value != null) |               if (user.value != null) | ||||||
|                 Positioned( |                 Positioned( | ||||||
| @@ -126,7 +481,7 @@ class PostDetailScreen extends HookConsumerWidget { | |||||||
|         error: |         error: | ||||||
|             (e, _) => ResponseErrorWidget( |             (e, _) => ResponseErrorWidget( | ||||||
|               error: e, |               error: e, | ||||||
|               onRetry: () => ref.invalidate(postStateProvider(id)), |               onRetry: () => ref.invalidate(postProvider(id)), | ||||||
|             ), |             ), | ||||||
|       ), |       ), | ||||||
|     ); |     ); | ||||||
|   | |||||||
| @@ -1,5 +1,7 @@ | |||||||
| import 'dart:async'; | import 'dart:async'; | ||||||
|  | import 'package:easy_localization/easy_localization.dart'; | ||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:flutter_hooks/flutter_hooks.dart'; | ||||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
| import 'package:island/models/post.dart'; | import 'package:island/models/post.dart'; | ||||||
| import 'package:island/pods/network.dart'; | import 'package:island/pods/network.dart'; | ||||||
| @@ -7,6 +9,7 @@ import 'package:island/widgets/app_scaffold.dart'; | |||||||
| import 'package:island/widgets/post/post_item.dart'; | import 'package:island/widgets/post/post_item.dart'; | ||||||
| import 'package:island/widgets/response.dart'; | import 'package:island/widgets/response.dart'; | ||||||
| import 'package:riverpod_paging_utils/riverpod_paging_utils.dart'; | import 'package:riverpod_paging_utils/riverpod_paging_utils.dart'; | ||||||
|  | import 'package:styled_widget/styled_widget.dart'; | ||||||
|  |  | ||||||
| final postSearchNotifierProvider = StateNotifierProvider.autoDispose< | final postSearchNotifierProvider = StateNotifierProvider.autoDispose< | ||||||
|   PostSearchNotifier, |   PostSearchNotifier, | ||||||
| @@ -18,6 +21,13 @@ class PostSearchNotifier | |||||||
|   final AutoDisposeRef ref; |   final AutoDisposeRef ref; | ||||||
|   static const int _pageSize = 20; |   static const int _pageSize = 20; | ||||||
|   String _currentQuery = ''; |   String _currentQuery = ''; | ||||||
|  |   String? _pubName; | ||||||
|  |   String? _realm; | ||||||
|  |   int? _type; | ||||||
|  |   List<String>? _categories; | ||||||
|  |   List<String>? _tags; | ||||||
|  |   bool _shuffle = false; | ||||||
|  |   bool? _pinned; | ||||||
|   bool _isLoading = false; |   bool _isLoading = false; | ||||||
|  |  | ||||||
|   PostSearchNotifier(this.ref) : super(const AsyncValue.loading()) { |   PostSearchNotifier(this.ref) : super(const AsyncValue.loading()) { | ||||||
| @@ -26,11 +36,38 @@ class PostSearchNotifier | |||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   Future<void> search(String query) async { |   Future<void> search( | ||||||
|  |     String query, { | ||||||
|  |     String? pubName, | ||||||
|  |     String? realm, | ||||||
|  |     int? type, | ||||||
|  |     List<String>? categories, | ||||||
|  |     List<String>? tags, | ||||||
|  |     bool shuffle = false, | ||||||
|  |     bool? pinned, | ||||||
|  |   }) async { | ||||||
|     if (_isLoading) return; |     if (_isLoading) return; | ||||||
|  |  | ||||||
|     _currentQuery = query.trim(); |     _currentQuery = query.trim(); | ||||||
|     if (_currentQuery.isEmpty) { |     _pubName = pubName; | ||||||
|  |     _realm = realm; | ||||||
|  |     _type = type; | ||||||
|  |     _categories = categories; | ||||||
|  |     _tags = tags; | ||||||
|  |     _shuffle = shuffle; | ||||||
|  |     _pinned = pinned; | ||||||
|  |  | ||||||
|  |     // Allow search even with empty query if any filters are applied | ||||||
|  |     final hasFilters = | ||||||
|  |         pubName != null || | ||||||
|  |         realm != null || | ||||||
|  |         type != null || | ||||||
|  |         categories != null || | ||||||
|  |         tags != null || | ||||||
|  |         shuffle || | ||||||
|  |         pinned != null; | ||||||
|  |  | ||||||
|  |     if (_currentQuery.isEmpty && !hasFilters) { | ||||||
|       state = AsyncValue.data( |       state = AsyncValue.data( | ||||||
|         CursorPagingData(items: [], hasMore: false, nextCursor: null), |         CursorPagingData(items: [], hasMore: false, nextCursor: null), | ||||||
|       ); |       ); | ||||||
| @@ -57,6 +94,13 @@ class PostSearchNotifier | |||||||
|           'offset': offset, |           'offset': offset, | ||||||
|           'take': _pageSize, |           'take': _pageSize, | ||||||
|           'vector': false, |           'vector': false, | ||||||
|  |           if (_pubName != null) 'pub': _pubName, | ||||||
|  |           if (_realm != null) 'realm': _realm, | ||||||
|  |           if (_type != null) 'type': _type, | ||||||
|  |           if (_tags != null) 'tags': _tags, | ||||||
|  |           if (_categories != null) 'categories': _categories, | ||||||
|  |           if (_shuffle) 'shuffle': true, | ||||||
|  |           if (_pinned != null) 'pinned': _pinned, | ||||||
|         }, |         }, | ||||||
|       ); |       ); | ||||||
|  |  | ||||||
| @@ -80,100 +124,267 @@ class PostSearchNotifier | |||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| class PostSearchScreen extends ConsumerStatefulWidget { | class PostSearchScreen extends HookConsumerWidget { | ||||||
|   const PostSearchScreen({super.key}); |   const PostSearchScreen({super.key}); | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   ConsumerState<PostSearchScreen> createState() => _PostSearchScreenState(); |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
| } |     final searchController = useTextEditingController(); | ||||||
|  |     final debounce = useMemoized(() => Duration(milliseconds: 500)); | ||||||
|  |     final debounceTimer = useRef<Timer?>(null); | ||||||
|  |     final showFilters = useState(false); | ||||||
|  |     final pubNameController = useTextEditingController(); | ||||||
|  |     final realmController = useTextEditingController(); | ||||||
|  |     final typeValue = useState<int?>(null); | ||||||
|  |     final selectedCategories = useState<List<String>>([]); | ||||||
|  |     final selectedTags = useState<List<String>>([]); | ||||||
|  |     final shuffleValue = useState(false); | ||||||
|  |     final pinnedValue = useState<bool?>(null); | ||||||
|  |  | ||||||
| class _PostSearchScreenState extends ConsumerState<PostSearchScreen> { |     useEffect(() { | ||||||
|   final _searchController = TextEditingController(); |       return () { | ||||||
|   final _debounce = Duration(milliseconds: 500); |         searchController.dispose(); | ||||||
|   Timer? _debounceTimer; |         pubNameController.dispose(); | ||||||
|  |         realmController.dispose(); | ||||||
|  |         debounceTimer.value?.cancel(); | ||||||
|  |       }; | ||||||
|  |     }, []); | ||||||
|  |  | ||||||
|   @override |     void onSearchChanged(String query) { | ||||||
|   void dispose() { |       if (debounceTimer.value?.isActive ?? false) debounceTimer.value!.cancel(); | ||||||
|     _searchController.dispose(); |  | ||||||
|     _debounceTimer?.cancel(); |  | ||||||
|     super.dispose(); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   void _onSearchChanged(String query) { |       debounceTimer.value = Timer(debounce, () { | ||||||
|     if (_debounceTimer?.isActive ?? false) _debounceTimer!.cancel(); |         ref.read(postSearchNotifierProvider.notifier).search(query); | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     _debounceTimer = Timer(_debounce, () { |     void onSearchWithFilters(String query) { | ||||||
|       ref.read(postSearchNotifierProvider.notifier).search(query); |       if (debounceTimer.value?.isActive ?? false) debounceTimer.value!.cancel(); | ||||||
|     }); |  | ||||||
|   } |       debounceTimer.value = Timer(debounce, () { | ||||||
|  |         ref | ||||||
|  |             .read(postSearchNotifierProvider.notifier) | ||||||
|  |             .search( | ||||||
|  |               query, | ||||||
|  |               pubName: | ||||||
|  |                   pubNameController.text.isNotEmpty | ||||||
|  |                       ? pubNameController.text | ||||||
|  |                       : null, | ||||||
|  |               realm: | ||||||
|  |                   realmController.text.isNotEmpty ? realmController.text : null, | ||||||
|  |               type: typeValue.value, | ||||||
|  |               categories: | ||||||
|  |                   selectedCategories.value.isNotEmpty | ||||||
|  |                       ? selectedCategories.value | ||||||
|  |                       : null, | ||||||
|  |               tags: selectedTags.value.isNotEmpty ? selectedTags.value : null, | ||||||
|  |               shuffle: shuffleValue.value, | ||||||
|  |               pinned: pinnedValue.value, | ||||||
|  |             ); | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     void toggleFilters() { | ||||||
|  |       showFilters.value = !showFilters.value; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     void applyFilters() { | ||||||
|  |       onSearchWithFilters(searchController.text); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     void clearFilters() { | ||||||
|  |       pubNameController.clear(); | ||||||
|  |       realmController.clear(); | ||||||
|  |       typeValue.value = null; | ||||||
|  |       selectedCategories.value = []; | ||||||
|  |       selectedTags.value = []; | ||||||
|  |       shuffleValue.value = false; | ||||||
|  |       pinnedValue.value = null; | ||||||
|  |       onSearchChanged(searchController.text); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     Widget buildFilterPanel() { | ||||||
|  |       return Card( | ||||||
|  |         margin: EdgeInsets.symmetric(vertical: 8, horizontal: 8), | ||||||
|  |         child: Padding( | ||||||
|  |           padding: EdgeInsets.all(16), | ||||||
|  |           child: Column( | ||||||
|  |             crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|  |             children: [ | ||||||
|  |               Row( | ||||||
|  |                 mainAxisAlignment: MainAxisAlignment.spaceBetween, | ||||||
|  |                 children: [ | ||||||
|  |                   Text( | ||||||
|  |                     'filters'.tr(), | ||||||
|  |                     style: Theme.of(context).textTheme.titleMedium, | ||||||
|  |                   ).padding(left: 4), | ||||||
|  |                   Row( | ||||||
|  |                     children: [ | ||||||
|  |                       TextButton( | ||||||
|  |                         onPressed: applyFilters, | ||||||
|  |                         child: Text('apply'.tr()), | ||||||
|  |                       ), | ||||||
|  |                       TextButton( | ||||||
|  |                         onPressed: clearFilters, | ||||||
|  |                         child: Text('clear'.tr()), | ||||||
|  |                       ), | ||||||
|  |                     ], | ||||||
|  |                   ), | ||||||
|  |                 ], | ||||||
|  |               ), | ||||||
|  |               SizedBox(height: 16), | ||||||
|  |               TextField( | ||||||
|  |                 controller: pubNameController, | ||||||
|  |                 decoration: InputDecoration( | ||||||
|  |                   labelText: 'pubName'.tr(), | ||||||
|  |                   border: OutlineInputBorder(), | ||||||
|  |                 ), | ||||||
|  |                 onChanged: | ||||||
|  |                     (value) => onSearchWithFilters(searchController.text), | ||||||
|  |               ), | ||||||
|  |               SizedBox(height: 8), | ||||||
|  |               TextField( | ||||||
|  |                 controller: realmController, | ||||||
|  |                 decoration: InputDecoration( | ||||||
|  |                   labelText: 'realm'.tr(), | ||||||
|  |                   border: OutlineInputBorder(), | ||||||
|  |                 ), | ||||||
|  |                 onChanged: | ||||||
|  |                     (value) => onSearchWithFilters(searchController.text), | ||||||
|  |               ), | ||||||
|  |               SizedBox(height: 8), | ||||||
|  |               Row( | ||||||
|  |                 children: [ | ||||||
|  |                   Checkbox( | ||||||
|  |                     value: shuffleValue.value, | ||||||
|  |                     onChanged: (value) { | ||||||
|  |                       shuffleValue.value = value ?? false; | ||||||
|  |                       onSearchWithFilters(searchController.text); | ||||||
|  |                     }, | ||||||
|  |                   ), | ||||||
|  |                   Text('shuffle'.tr()), | ||||||
|  |                 ], | ||||||
|  |               ), | ||||||
|  |               Row( | ||||||
|  |                 children: [ | ||||||
|  |                   Checkbox( | ||||||
|  |                     value: pinnedValue.value ?? false, | ||||||
|  |                     onChanged: (value) { | ||||||
|  |                       pinnedValue.value = value; | ||||||
|  |                       onSearchWithFilters(searchController.text); | ||||||
|  |                     }, | ||||||
|  |                   ), | ||||||
|  |                   Text('pinned'.tr()), | ||||||
|  |                 ], | ||||||
|  |               ), | ||||||
|  |             ], | ||||||
|  |           ), | ||||||
|  |         ), | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|  |  | ||||||
|   @override |  | ||||||
|   Widget build(BuildContext context) { |  | ||||||
|     return AppScaffold( |     return AppScaffold( | ||||||
|       isNoBackground: false, |       isNoBackground: false, | ||||||
|       appBar: AppBar( |       appBar: AppBar( | ||||||
|         title: TextField( |         title: Row( | ||||||
|           controller: _searchController, |           children: [ | ||||||
|           decoration: InputDecoration( |             Expanded( | ||||||
|             hintText: 'Search posts...', |               child: TextField( | ||||||
|             border: InputBorder.none, |                 controller: searchController, | ||||||
|             hintStyle: TextStyle( |                 decoration: InputDecoration( | ||||||
|               color: Theme.of(context).appBarTheme.foregroundColor, |                   hintText: 'search'.tr(), | ||||||
|  |                   border: InputBorder.none, | ||||||
|  |                   hintStyle: TextStyle( | ||||||
|  |                     color: Theme.of(context).appBarTheme.foregroundColor, | ||||||
|  |                   ), | ||||||
|  |                 ), | ||||||
|  |                 style: TextStyle( | ||||||
|  |                   color: Theme.of(context).appBarTheme.foregroundColor, | ||||||
|  |                 ), | ||||||
|  |                 onChanged: onSearchChanged, | ||||||
|  |                 onSubmitted: (value) { | ||||||
|  |                   onSearchWithFilters(value); | ||||||
|  |                 }, | ||||||
|  |                 autofocus: true, | ||||||
|  |               ), | ||||||
|             ), |             ), | ||||||
|           ), |             IconButton( | ||||||
|           style: TextStyle( |               icon: Icon( | ||||||
|             color: Theme.of(context).appBarTheme.foregroundColor, |                 showFilters.value | ||||||
|           ), |                     ? Icons.filter_alt | ||||||
|           onChanged: _onSearchChanged, |                     : Icons.filter_alt_outlined, | ||||||
|           onSubmitted: (value) { |               ), | ||||||
|             ref.read(postSearchNotifierProvider.notifier).search(value); |               onPressed: toggleFilters, | ||||||
|           }, |               tooltip: 'toggleFilters'.tr(), | ||||||
|           autofocus: true, |             ), | ||||||
|  |           ], | ||||||
|         ), |         ), | ||||||
|       ), |       ), | ||||||
|       body: Consumer( |       body: Consumer( | ||||||
|         builder: (context, ref, child) { |         builder: (context, ref, child) { | ||||||
|           final searchState = ref.watch(postSearchNotifierProvider); |           final searchState = ref.watch(postSearchNotifierProvider); | ||||||
|  |  | ||||||
|           return searchState.when( |           return CustomScrollView( | ||||||
|             data: (data) { |             slivers: [ | ||||||
|               if (data.items.isEmpty && _searchController.text.isNotEmpty) { |               if (showFilters.value) | ||||||
|                 return const Center(child: Text('No results found')); |                 SliverToBoxAdapter( | ||||||
|               } |                   child: Center( | ||||||
|  |                     child: ConstrainedBox( | ||||||
|               return ListView.builder( |                       constraints: const BoxConstraints(maxWidth: 600), | ||||||
|                 padding: EdgeInsets.zero, |                       child: buildFilterPanel(), | ||||||
|                 itemCount: data.items.length + (data.hasMore ? 1 : 0), |                     ), | ||||||
|                 itemBuilder: (context, index) { |                   ), | ||||||
|                   if (index >= data.items.length) { |                 ), | ||||||
|                     ref |               searchState.when( | ||||||
|                         .read(postSearchNotifierProvider.notifier) |                 data: (data) { | ||||||
|                         .fetch(cursor: data.nextCursor); |                   if (data.items.isEmpty && searchController.text.isNotEmpty) { | ||||||
|                     return const Center(child: CircularProgressIndicator()); |                     return SliverFillRemaining( | ||||||
|  |                       child: Center(child: Text('noResultsFound'.tr())), | ||||||
|  |                     ); | ||||||
|                   } |                   } | ||||||
|  |  | ||||||
|                   final post = data.items[index]; |                   return SliverList( | ||||||
|                   return Center( |                     delegate: SliverChildBuilderDelegate((context, index) { | ||||||
|                     child: ConstrainedBox( |                       if (index >= data.items.length) { | ||||||
|                       constraints: BoxConstraints(maxWidth: 600), |                         ref | ||||||
|                       child: Card( |                             .read(postSearchNotifierProvider.notifier) | ||||||
|                         margin: EdgeInsets.symmetric( |                             .fetch(cursor: data.nextCursor); | ||||||
|                           horizontal: 8, |                         return Center(child: CircularProgressIndicator()); | ||||||
|                           vertical: 4, |                       } | ||||||
|  |  | ||||||
|  |                       final post = data.items[index]; | ||||||
|  |                       return Center( | ||||||
|  |                         child: ConstrainedBox( | ||||||
|  |                           constraints: BoxConstraints(maxWidth: 600), | ||||||
|  |                           child: Card( | ||||||
|  |                             margin: EdgeInsets.symmetric( | ||||||
|  |                               horizontal: 8, | ||||||
|  |                               vertical: 4, | ||||||
|  |                             ), | ||||||
|  |                             child: PostActionableItem( | ||||||
|  |                               item: post, | ||||||
|  |                               borderRadius: 8, | ||||||
|  |                             ), | ||||||
|  |                           ), | ||||||
|                         ), |                         ), | ||||||
|                         child: PostActionableItem(item: post, borderRadius: 8), |                       ); | ||||||
|                       ), |                     }, childCount: data.items.length + (data.hasMore ? 1 : 0)), | ||||||
|                     ), |  | ||||||
|                   ); |                   ); | ||||||
|                 }, |                 }, | ||||||
|               ); |                 loading: | ||||||
|             }, |                     () => SliverFillRemaining( | ||||||
|             loading: () => const Center(child: CircularProgressIndicator()), |                       child: Center(child: CircularProgressIndicator()), | ||||||
|             error: |                     ), | ||||||
|                 (error, stack) => ResponseErrorWidget( |                 error: | ||||||
|                   error: error, |                     (error, stack) => SliverFillRemaining( | ||||||
|                   onRetry: () => ref.invalidate(postSearchNotifierProvider), |                       child: ResponseErrorWidget( | ||||||
|                 ), |                         error: error, | ||||||
|  |                         onRetry: | ||||||
|  |                             () => ref.invalidate(postSearchNotifierProvider), | ||||||
|  |                       ), | ||||||
|  |                     ), | ||||||
|  |               ), | ||||||
|  |             ], | ||||||
|           ); |           ); | ||||||
|         }, |         }, | ||||||
|       ), |       ), | ||||||
|   | |||||||
| @@ -27,6 +27,224 @@ import 'package:styled_widget/styled_widget.dart'; | |||||||
|  |  | ||||||
| part 'pub_profile.g.dart'; | part 'pub_profile.g.dart'; | ||||||
|  |  | ||||||
|  | class _PublisherBasisWidget extends StatelessWidget { | ||||||
|  |   final SnPublisher data; | ||||||
|  |   final AsyncValue<SnSubscriptionStatus> subStatus; | ||||||
|  |   final ValueNotifier<bool> subscribing; | ||||||
|  |   final VoidCallback subscribe; | ||||||
|  |   final VoidCallback unsubscribe; | ||||||
|  |  | ||||||
|  |   const _PublisherBasisWidget({ | ||||||
|  |     required this.data, | ||||||
|  |     required this.subStatus, | ||||||
|  |     required this.subscribing, | ||||||
|  |     required this.subscribe, | ||||||
|  |     required this.unsubscribe, | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context) { | ||||||
|  |     return Row( | ||||||
|  |       crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|  |       spacing: 20, | ||||||
|  |       children: [ | ||||||
|  |         GestureDetector( | ||||||
|  |           child: Badge( | ||||||
|  |             isLabelVisible: data.type == 0, | ||||||
|  |             padding: EdgeInsets.all(4), | ||||||
|  |             label: Icon( | ||||||
|  |               Symbols.launch, | ||||||
|  |               size: 16, | ||||||
|  |               color: Theme.of(context).colorScheme.onPrimary, | ||||||
|  |             ), | ||||||
|  |             backgroundColor: Theme.of(context).colorScheme.primary, | ||||||
|  |             offset: Offset(0, 48), | ||||||
|  |             child: ProfilePictureWidget( | ||||||
|  |               file: data.picture, | ||||||
|  |               radius: 32, | ||||||
|  |               borderRadius: data.type == 0 ? null : 12, | ||||||
|  |             ), | ||||||
|  |           ), | ||||||
|  |           onTap: () { | ||||||
|  |             if (data.account?.name != null) { | ||||||
|  |               Navigator.pop(context, true); | ||||||
|  |               context.pushNamed( | ||||||
|  |                 'accountProfile', | ||||||
|  |                 pathParameters: {'name': data.account!.name}, | ||||||
|  |               ); | ||||||
|  |             } | ||||||
|  |           }, | ||||||
|  |         ), | ||||||
|  |         Expanded( | ||||||
|  |           child: Column( | ||||||
|  |             crossAxisAlignment: CrossAxisAlignment.stretch, | ||||||
|  |             children: [ | ||||||
|  |               Row( | ||||||
|  |                 spacing: 6, | ||||||
|  |                 children: [ | ||||||
|  |                   Text(data.nick).fontSize(20), | ||||||
|  |                   if (data.verification != null) | ||||||
|  |                     VerificationMark(mark: data.verification!), | ||||||
|  |                   Expanded( | ||||||
|  |                     child: Text( | ||||||
|  |                       '@${data.name}', | ||||||
|  |                       maxLines: 1, | ||||||
|  |                       overflow: TextOverflow.ellipsis, | ||||||
|  |                     ).fontSize(14).opacity(0.85), | ||||||
|  |                   ), | ||||||
|  |                 ], | ||||||
|  |               ), | ||||||
|  |               if (data.type == 0 && data.account != null) | ||||||
|  |                 Row( | ||||||
|  |                   crossAxisAlignment: CrossAxisAlignment.center, | ||||||
|  |                   spacing: 6, | ||||||
|  |                   children: [ | ||||||
|  |                     Icon( | ||||||
|  |                       data.type == 0 ? Symbols.person : Symbols.workspaces, | ||||||
|  |                       fill: 1, | ||||||
|  |                       size: 17, | ||||||
|  |                     ), | ||||||
|  |                     Text( | ||||||
|  |                       'publisherBelongsTo'.tr(args: ['@${data.account!.name}']), | ||||||
|  |                     ).fontSize(14), | ||||||
|  |                   ], | ||||||
|  |                 ).opacity(0.85), | ||||||
|  |               const Gap(4), | ||||||
|  |               if (data.type == 0 && data.account != null) | ||||||
|  |                 AccountStatusWidget( | ||||||
|  |                   uname: data.account!.name, | ||||||
|  |                   padding: EdgeInsets.zero, | ||||||
|  |                 ), | ||||||
|  |               subStatus | ||||||
|  |                   .when( | ||||||
|  |                     data: | ||||||
|  |                         (status) => FilledButton.icon( | ||||||
|  |                           onPressed: | ||||||
|  |                               subscribing.value | ||||||
|  |                                   ? null | ||||||
|  |                                   : (status.isSubscribed | ||||||
|  |                                       ? unsubscribe | ||||||
|  |                                       : subscribe), | ||||||
|  |                           icon: Icon( | ||||||
|  |                             status.isSubscribed | ||||||
|  |                                 ? Symbols.remove_circle | ||||||
|  |                                 : Symbols.add_circle, | ||||||
|  |                           ), | ||||||
|  |                           label: | ||||||
|  |                               Text( | ||||||
|  |                                 status.isSubscribed | ||||||
|  |                                     ? 'unsubscribe' | ||||||
|  |                                     : 'subscribe', | ||||||
|  |                               ).tr(), | ||||||
|  |                           style: ButtonStyle( | ||||||
|  |                             visualDensity: VisualDensity(vertical: -2), | ||||||
|  |                           ), | ||||||
|  |                         ), | ||||||
|  |                     error: (_, _) => const SizedBox(), | ||||||
|  |                     loading: | ||||||
|  |                         () => const SizedBox( | ||||||
|  |                           height: 36, | ||||||
|  |                           child: Center( | ||||||
|  |                             child: SizedBox( | ||||||
|  |                               width: 20, | ||||||
|  |                               height: 20, | ||||||
|  |                               child: CircularProgressIndicator(strokeWidth: 2), | ||||||
|  |                             ), | ||||||
|  |                           ), | ||||||
|  |                         ), | ||||||
|  |                   ) | ||||||
|  |                   .padding(top: 8), | ||||||
|  |             ], | ||||||
|  |           ), | ||||||
|  |         ), | ||||||
|  |       ], | ||||||
|  |     ).padding(horizontal: 24, top: 24); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class _PublisherBadgesWidget extends StatelessWidget { | ||||||
|  |   final SnPublisher data; | ||||||
|  |   final AsyncValue<List<SnAccountBadge>> badges; | ||||||
|  |  | ||||||
|  |   const _PublisherBadgesWidget({required this.data, required this.badges}); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context) { | ||||||
|  |     return (badges.value?.isNotEmpty ?? false) | ||||||
|  |         ? Card( | ||||||
|  |           child: BadgeList( | ||||||
|  |             badges: badges.value!, | ||||||
|  |           ).padding(horizontal: 26, vertical: 20), | ||||||
|  |         ).padding(horizontal: 4) | ||||||
|  |         : const SizedBox.shrink(); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class _PublisherVerificationWidget extends StatelessWidget { | ||||||
|  |   final SnPublisher data; | ||||||
|  |  | ||||||
|  |   const _PublisherVerificationWidget({required this.data}); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context) { | ||||||
|  |     return (data.verification != null) | ||||||
|  |         ? Card( | ||||||
|  |           margin: EdgeInsets.symmetric(horizontal: 8, vertical: 4), | ||||||
|  |           child: VerificationStatusCard(mark: data.verification!), | ||||||
|  |         ) | ||||||
|  |         : const SizedBox.shrink(); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class _PublisherBioWidget extends StatelessWidget { | ||||||
|  |   final SnPublisher data; | ||||||
|  |  | ||||||
|  |   const _PublisherBioWidget({required this.data}); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context) { | ||||||
|  |     return Card( | ||||||
|  |       margin: EdgeInsets.symmetric(horizontal: 8, vertical: 4), | ||||||
|  |       child: Column( | ||||||
|  |         crossAxisAlignment: CrossAxisAlignment.stretch, | ||||||
|  |         children: [ | ||||||
|  |           Text('bio').tr().bold().fontSize(15).padding(bottom: 8), | ||||||
|  |           if (data.bio.isEmpty) | ||||||
|  |             Text('descriptionNone').tr().italic() | ||||||
|  |           else | ||||||
|  |             MarkdownTextContent( | ||||||
|  |               content: data.bio, | ||||||
|  |               linesMargin: EdgeInsets.zero, | ||||||
|  |             ), | ||||||
|  |         ], | ||||||
|  |       ).padding(horizontal: 20, vertical: 16), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class _PublisherCategoryTabWidget extends StatelessWidget { | ||||||
|  |   final TabController categoryTabController; | ||||||
|  |  | ||||||
|  |   const _PublisherCategoryTabWidget({required this.categoryTabController}); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context) { | ||||||
|  |     return Card( | ||||||
|  |       margin: EdgeInsets.symmetric(horizontal: 8, vertical: 4), | ||||||
|  |       child: TabBar( | ||||||
|  |         controller: categoryTabController, | ||||||
|  |         dividerColor: Colors.transparent, | ||||||
|  |         splashBorderRadius: const BorderRadius.all(Radius.circular(8)), | ||||||
|  |         tabs: [ | ||||||
|  |           Tab(text: 'all'.tr()), | ||||||
|  |           Tab(text: 'postTypePost'.tr()), | ||||||
|  |           Tab(text: 'postArticle'.tr()), | ||||||
|  |         ], | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
| @riverpod | @riverpod | ||||||
| Future<SnPublisher> publisher(Ref ref, String uname) async { | Future<SnPublisher> publisher(Ref ref, String uname) async { | ||||||
|   final apiClient = ref.watch(apiClientProvider); |   final apiClient = ref.watch(apiClientProvider); | ||||||
| @@ -132,170 +350,6 @@ class PublisherProfileScreen extends HookConsumerWidget { | |||||||
|       offset: Offset(1.0, 1.0), |       offset: Offset(1.0, 1.0), | ||||||
|     ); |     ); | ||||||
|  |  | ||||||
|     Widget publisherBasisWidget(SnPublisher data) => Row( |  | ||||||
|       crossAxisAlignment: CrossAxisAlignment.start, |  | ||||||
|       spacing: 20, |  | ||||||
|       children: [ |  | ||||||
|         GestureDetector( |  | ||||||
|           child: Badge( |  | ||||||
|             isLabelVisible: data.type == 0, |  | ||||||
|             padding: EdgeInsets.all(4), |  | ||||||
|             label: Icon( |  | ||||||
|               Symbols.launch, |  | ||||||
|               size: 16, |  | ||||||
|               color: Theme.of(context).colorScheme.onPrimary, |  | ||||||
|             ), |  | ||||||
|             backgroundColor: Theme.of(context).colorScheme.primary, |  | ||||||
|             offset: Offset(0, 48), |  | ||||||
|             child: ProfilePictureWidget( |  | ||||||
|               file: data.picture, |  | ||||||
|               radius: 32, |  | ||||||
|               borderRadius: data.type == 0 ? null : 12, |  | ||||||
|             ), |  | ||||||
|           ), |  | ||||||
|           onTap: () { |  | ||||||
|             Navigator.pop(context, true); |  | ||||||
|             if (data.account?.name != null) { |  | ||||||
|               context.pushNamed( |  | ||||||
|                 'accountProfile', |  | ||||||
|                 pathParameters: {'name': data.account!.name}, |  | ||||||
|               ); |  | ||||||
|             } |  | ||||||
|           }, |  | ||||||
|         ), |  | ||||||
|         Expanded( |  | ||||||
|           child: Column( |  | ||||||
|             crossAxisAlignment: CrossAxisAlignment.stretch, |  | ||||||
|             children: [ |  | ||||||
|               Row( |  | ||||||
|                 spacing: 6, |  | ||||||
|                 children: [ |  | ||||||
|                   Text(data.nick).fontSize(20), |  | ||||||
|                   if (data.verification != null) |  | ||||||
|                     VerificationMark(mark: data.verification!), |  | ||||||
|                   Expanded( |  | ||||||
|                     child: Text( |  | ||||||
|                       '@${data.name}', |  | ||||||
|                       maxLines: 1, |  | ||||||
|                       overflow: TextOverflow.ellipsis, |  | ||||||
|                     ).fontSize(14).opacity(0.85), |  | ||||||
|                   ), |  | ||||||
|                 ], |  | ||||||
|               ), |  | ||||||
|               if (data.type == 0 && data.account != null) |  | ||||||
|                 Row( |  | ||||||
|                   crossAxisAlignment: CrossAxisAlignment.center, |  | ||||||
|                   spacing: 6, |  | ||||||
|                   children: [ |  | ||||||
|                     Icon( |  | ||||||
|                       data.type == 0 ? Symbols.person : Symbols.workspaces, |  | ||||||
|                       fill: 1, |  | ||||||
|                       size: 17, |  | ||||||
|                     ), |  | ||||||
|                     Text( |  | ||||||
|                       'publisherBelongsTo'.tr(args: ['@${data.account!.name}']), |  | ||||||
|                     ).fontSize(14), |  | ||||||
|                   ], |  | ||||||
|                 ).opacity(0.85), |  | ||||||
|               const Gap(4), |  | ||||||
|               if (data.type == 0 && data.account != null) |  | ||||||
|                 AccountStatusWidget( |  | ||||||
|                   uname: data.account!.name, |  | ||||||
|                   padding: EdgeInsets.zero, |  | ||||||
|                 ), |  | ||||||
|               subStatus |  | ||||||
|                   .when( |  | ||||||
|                     data: |  | ||||||
|                         (status) => FilledButton.icon( |  | ||||||
|                           onPressed: |  | ||||||
|                               subscribing.value |  | ||||||
|                                   ? null |  | ||||||
|                                   : (status.isSubscribed |  | ||||||
|                                       ? unsubscribe |  | ||||||
|                                       : subscribe), |  | ||||||
|                           icon: Icon( |  | ||||||
|                             status.isSubscribed |  | ||||||
|                                 ? Symbols.remove_circle |  | ||||||
|                                 : Symbols.add_circle, |  | ||||||
|                           ), |  | ||||||
|                           label: |  | ||||||
|                               Text( |  | ||||||
|                                 status.isSubscribed |  | ||||||
|                                     ? 'unsubscribe' |  | ||||||
|                                     : 'subscribe', |  | ||||||
|                               ).tr(), |  | ||||||
|                           style: ButtonStyle( |  | ||||||
|                             visualDensity: VisualDensity(vertical: -2), |  | ||||||
|                           ), |  | ||||||
|                         ), |  | ||||||
|                     error: (_, _) => const SizedBox(), |  | ||||||
|                     loading: |  | ||||||
|                         () => const SizedBox( |  | ||||||
|                           height: 36, |  | ||||||
|                           child: Center( |  | ||||||
|                             child: SizedBox( |  | ||||||
|                               width: 20, |  | ||||||
|                               height: 20, |  | ||||||
|                               child: CircularProgressIndicator(strokeWidth: 2), |  | ||||||
|                             ), |  | ||||||
|                           ), |  | ||||||
|                         ), |  | ||||||
|                   ) |  | ||||||
|                   .padding(top: 8), |  | ||||||
|             ], |  | ||||||
|           ), |  | ||||||
|         ), |  | ||||||
|       ], |  | ||||||
|     ).padding(horizontal: 24, top: 24); |  | ||||||
|  |  | ||||||
|     Widget publisherBadgesWidget(SnPublisher data) => |  | ||||||
|         (badges.value?.isNotEmpty ?? false) |  | ||||||
|             ? Card( |  | ||||||
|               child: BadgeList( |  | ||||||
|                 badges: badges.value!, |  | ||||||
|               ).padding(horizontal: 26, vertical: 20), |  | ||||||
|             ).padding(horizontal: 4) |  | ||||||
|             : const SizedBox.shrink(); |  | ||||||
|  |  | ||||||
|     Widget publisherVerificationWidget(SnPublisher data) => |  | ||||||
|         (data.verification != null) |  | ||||||
|             ? Card( |  | ||||||
|               margin: EdgeInsets.symmetric(horizontal: 8, vertical: 4), |  | ||||||
|               child: VerificationStatusCard(mark: data.verification!), |  | ||||||
|             ) |  | ||||||
|             : const SizedBox.shrink(); |  | ||||||
|  |  | ||||||
|     Widget publisherBioWidget(SnPublisher data) => Card( |  | ||||||
|       margin: EdgeInsets.symmetric(horizontal: 8, vertical: 4), |  | ||||||
|       child: Column( |  | ||||||
|         crossAxisAlignment: CrossAxisAlignment.stretch, |  | ||||||
|         children: [ |  | ||||||
|           Text('bio').tr().bold().fontSize(15).padding(bottom: 8), |  | ||||||
|           if (data.bio.isEmpty) |  | ||||||
|             Text('descriptionNone').tr().italic() |  | ||||||
|           else |  | ||||||
|             MarkdownTextContent( |  | ||||||
|               content: data.bio, |  | ||||||
|               linesMargin: EdgeInsets.zero, |  | ||||||
|             ), |  | ||||||
|         ], |  | ||||||
|       ).padding(horizontal: 20, vertical: 16), |  | ||||||
|     ); |  | ||||||
|  |  | ||||||
|     Widget publisherCategoryTabWidget() => Card( |  | ||||||
|       margin: EdgeInsets.symmetric(horizontal: 8, vertical: 4), |  | ||||||
|       child: TabBar( |  | ||||||
|         controller: categoryTabController, |  | ||||||
|         dividerColor: Colors.transparent, |  | ||||||
|         splashBorderRadius: const BorderRadius.all(Radius.circular(8)), |  | ||||||
|         tabs: [ |  | ||||||
|           Tab(text: 'all'.tr()), |  | ||||||
|           Tab(text: 'postTypePost'.tr()), |  | ||||||
|           Tab(text: 'postArticle'.tr()), |  | ||||||
|         ], |  | ||||||
|       ), |  | ||||||
|     ); |  | ||||||
|  |  | ||||||
|     return publisher.when( |     return publisher.when( | ||||||
|       data: |       data: | ||||||
|           (data) => AppScaffold( |           (data) => AppScaffold( | ||||||
| @@ -351,7 +405,9 @@ class PublisherProfileScreen extends HookConsumerWidget { | |||||||
|                               SliverGap(16), |                               SliverGap(16), | ||||||
|                               SliverPostList(pubName: name, pinned: true), |                               SliverPostList(pubName: name, pinned: true), | ||||||
|                               SliverToBoxAdapter( |                               SliverToBoxAdapter( | ||||||
|                                 child: publisherCategoryTabWidget(), |                                 child: _PublisherCategoryTabWidget( | ||||||
|  |                                   categoryTabController: categoryTabController, | ||||||
|  |                                 ), | ||||||
|                               ), |                               ), | ||||||
|                               SliverPostList( |                               SliverPostList( | ||||||
|                                 key: ValueKey(categoryTab.value), |                                 key: ValueKey(categoryTab.value), | ||||||
| @@ -377,10 +433,19 @@ class PublisherProfileScreen extends HookConsumerWidget { | |||||||
|                               child: Column( |                               child: Column( | ||||||
|                                 crossAxisAlignment: CrossAxisAlignment.stretch, |                                 crossAxisAlignment: CrossAxisAlignment.stretch, | ||||||
|                                 children: [ |                                 children: [ | ||||||
|                                   publisherBasisWidget(data).padding(bottom: 8), |                                   _PublisherBasisWidget( | ||||||
|                                   publisherBadgesWidget(data), |                                     data: data, | ||||||
|                                   publisherVerificationWidget(data), |                                     subStatus: subStatus, | ||||||
|                                   publisherBioWidget(data), |                                     subscribing: subscribing, | ||||||
|  |                                     subscribe: subscribe, | ||||||
|  |                                     unsubscribe: unsubscribe, | ||||||
|  |                                   ).padding(bottom: 8), | ||||||
|  |                                   _PublisherBadgesWidget( | ||||||
|  |                                     data: data, | ||||||
|  |                                     badges: badges, | ||||||
|  |                                   ), | ||||||
|  |                                   _PublisherVerificationWidget(data: data), | ||||||
|  |                                   _PublisherBioWidget(data: data), | ||||||
|                                 ], |                                 ], | ||||||
|                               ), |                               ), | ||||||
|                             ), |                             ), | ||||||
| @@ -432,15 +497,32 @@ class PublisherProfileScreen extends HookConsumerWidget { | |||||||
|                           ), |                           ), | ||||||
|                         ), |                         ), | ||||||
|                         SliverToBoxAdapter( |                         SliverToBoxAdapter( | ||||||
|                           child: publisherBasisWidget(data).padding(bottom: 8), |                           child: _PublisherBasisWidget( | ||||||
|  |                             data: data, | ||||||
|  |                             subStatus: subStatus, | ||||||
|  |                             subscribing: subscribing, | ||||||
|  |                             subscribe: subscribe, | ||||||
|  |                             unsubscribe: unsubscribe, | ||||||
|  |                           ).padding(bottom: 8), | ||||||
|                         ), |                         ), | ||||||
|                         SliverToBoxAdapter(child: publisherBadgesWidget(data)), |  | ||||||
|                         SliverToBoxAdapter( |                         SliverToBoxAdapter( | ||||||
|                           child: publisherVerificationWidget(data), |                           child: _PublisherBadgesWidget( | ||||||
|  |                             data: data, | ||||||
|  |                             badges: badges, | ||||||
|  |                           ), | ||||||
|  |                         ), | ||||||
|  |                         SliverToBoxAdapter( | ||||||
|  |                           child: _PublisherVerificationWidget(data: data), | ||||||
|  |                         ), | ||||||
|  |                         SliverToBoxAdapter( | ||||||
|  |                           child: _PublisherBioWidget(data: data), | ||||||
|                         ), |                         ), | ||||||
|                         SliverToBoxAdapter(child: publisherBioWidget(data)), |  | ||||||
|                         SliverPostList(pubName: name, pinned: true), |                         SliverPostList(pubName: name, pinned: true), | ||||||
|                         SliverToBoxAdapter(child: publisherCategoryTabWidget()), |                         SliverToBoxAdapter( | ||||||
|  |                           child: _PublisherCategoryTabWidget( | ||||||
|  |                             categoryTabController: categoryTabController, | ||||||
|  |                           ), | ||||||
|  |                         ), | ||||||
|                         SliverPostList( |                         SliverPostList( | ||||||
|                           key: ValueKey(categoryTab.value), |                           key: ValueKey(categoryTab.value), | ||||||
|                           pubName: name, |                           pubName: name, | ||||||
|   | |||||||
| @@ -668,7 +668,13 @@ class _RealmMemberListSheet extends HookConsumerWidget { | |||||||
|                   title: Row( |                   title: Row( | ||||||
|                     spacing: 6, |                     spacing: 6, | ||||||
|                     children: [ |                     children: [ | ||||||
|                       Flexible(child: Text(member.account!.nick)), |                       Flexible( | ||||||
|  |                         child: Text( | ||||||
|  |                           member.account!.nick, | ||||||
|  |                           maxLines: 1, | ||||||
|  |                           overflow: TextOverflow.ellipsis, | ||||||
|  |                         ), | ||||||
|  |                       ), | ||||||
|                       if (member.status != null) |                       if (member.status != null) | ||||||
|                         AccountStatusLabel(status: member.status!), |                         AccountStatusLabel(status: member.status!), | ||||||
|                       if (member.joinedAt == null) |                       if (member.joinedAt == null) | ||||||
|   | |||||||
| @@ -23,6 +23,7 @@ import 'package:material_symbols_icons/symbols.dart'; | |||||||
| import 'package:riverpod_annotation/riverpod_annotation.dart'; | import 'package:riverpod_annotation/riverpod_annotation.dart'; | ||||||
| import 'package:styled_widget/styled_widget.dart'; | import 'package:styled_widget/styled_widget.dart'; | ||||||
| import 'package:island/widgets/realm/realm_list_tile.dart'; | import 'package:island/widgets/realm/realm_list_tile.dart'; | ||||||
|  | import 'package:island/widgets/extended_refresh_indicator.dart'; | ||||||
|  |  | ||||||
| part 'realms.g.dart'; | part 'realms.g.dart'; | ||||||
|  |  | ||||||
| @@ -90,7 +91,7 @@ class RealmListScreen extends HookConsumerWidget { | |||||||
|         }, |         }, | ||||||
|       ), |       ), | ||||||
|       floatingActionButtonLocation: TabbedFabLocation(context), |       floatingActionButtonLocation: TabbedFabLocation(context), | ||||||
|       body: RefreshIndicator( |       body: ExtendedRefreshIndicator( | ||||||
|         child: realms.when( |         child: realms.when( | ||||||
|           data: |           data: | ||||||
|               (value) => Column( |               (value) => Column( | ||||||
|   | |||||||
| @@ -219,6 +219,33 @@ class SettingsScreen extends HookConsumerWidget { | |||||||
|           }, |           }, | ||||||
|         ), |         ), | ||||||
|  |  | ||||||
|  |       // Background image enabled | ||||||
|  |       if (!kIsWeb && docBasepath.value != null) | ||||||
|  |         FutureBuilder<bool>( | ||||||
|  |           future: | ||||||
|  |               File('${docBasepath.value}/$kAppBackgroundImagePath').exists(), | ||||||
|  |           builder: (context, snapshot) { | ||||||
|  |             if (!snapshot.hasData || !snapshot.data!) { | ||||||
|  |               return const SizedBox.shrink(); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             return ListTile( | ||||||
|  |               minLeadingWidth: 48, | ||||||
|  |               title: Text('settingsBackgroundImageEnable').tr(), | ||||||
|  |               contentPadding: const EdgeInsets.only(left: 24, right: 17), | ||||||
|  |               leading: const Icon(Symbols.image), | ||||||
|  |               trailing: Switch( | ||||||
|  |                 value: settings.showBackgroundImage, | ||||||
|  |                 onChanged: (value) { | ||||||
|  |                   ref | ||||||
|  |                       .read(appSettingsNotifierProvider.notifier) | ||||||
|  |                       .setShowBackgroundImage(value); | ||||||
|  |                 }, | ||||||
|  |               ), | ||||||
|  |             ); | ||||||
|  |           }, | ||||||
|  |         ), | ||||||
|  |  | ||||||
|       // Clear background image option |       // Clear background image option | ||||||
|       if (!kIsWeb && docBasepath.value != null) |       if (!kIsWeb && docBasepath.value != null) | ||||||
|         FutureBuilder<bool>( |         FutureBuilder<bool>( | ||||||
| @@ -423,66 +450,25 @@ class SettingsScreen extends HookConsumerWidget { | |||||||
|           }, |           }, | ||||||
|         ), |         ), | ||||||
|       ), |       ), | ||||||
|  |       ListTile( | ||||||
|  |         minLeadingWidth: 48, | ||||||
|  |         title: Text('settingsDataSavingMode').tr(), | ||||||
|  |         contentPadding: const EdgeInsets.only(left: 24, right: 17), | ||||||
|  |         leading: const Icon(Symbols.data_saver_on_rounded), | ||||||
|  |         trailing: Switch( | ||||||
|  |           value: settings.dataSavingMode, | ||||||
|  |           onChanged: (value) { | ||||||
|  |             ref | ||||||
|  |                 .read(appSettingsNotifierProvider.notifier) | ||||||
|  |                 .setDataSavingMode(value); | ||||||
|  |           }, | ||||||
|  |         ), | ||||||
|  |       ), | ||||||
|     ]; |     ]; | ||||||
|  |  | ||||||
|     // Desktop-specific settings |     // Desktop-specific settings | ||||||
|     final desktopSettings = |     // But nothing for now | ||||||
|         !isDesktop |     final desktopSettings = !isDesktop ? <Widget>[] : <Widget>[]; | ||||||
|             ? <Widget>[] |  | ||||||
|             : <Widget>[ |  | ||||||
|               ListTile( |  | ||||||
|                 minLeadingWidth: 48, |  | ||||||
|                 title: Text('settingsKeyboardShortcuts').tr(), |  | ||||||
|                 contentPadding: const EdgeInsets.only(left: 24, right: 17), |  | ||||||
|                 leading: const Icon(Symbols.keyboard), |  | ||||||
|                 onTap: () { |  | ||||||
|                   showDialog( |  | ||||||
|                     context: context, |  | ||||||
|                     builder: |  | ||||||
|                         (context) => AlertDialog( |  | ||||||
|                           title: Text('settingsKeyboardShortcuts').tr(), |  | ||||||
|                           content: SingleChildScrollView( |  | ||||||
|                             child: Column( |  | ||||||
|                               mainAxisSize: MainAxisSize.min, |  | ||||||
|                               crossAxisAlignment: CrossAxisAlignment.start, |  | ||||||
|                               children: [ |  | ||||||
|                                 _ShortcutRow( |  | ||||||
|                                   shortcut: 'Ctrl+F', |  | ||||||
|                                   description: |  | ||||||
|                                       'settingsKeyboardShortcutSearch'.tr(), |  | ||||||
|                                 ), |  | ||||||
|                                 _ShortcutRow( |  | ||||||
|                                   shortcut: 'Ctrl+,', |  | ||||||
|                                   description: |  | ||||||
|                                       'settingsKeyboardShortcutSettings'.tr(), |  | ||||||
|                                 ), |  | ||||||
|                                 _ShortcutRow( |  | ||||||
|                                   shortcut: 'Ctrl+N', |  | ||||||
|                                   description: |  | ||||||
|                                       'settingsKeyboardShortcutNewMessage'.tr(), |  | ||||||
|                                 ), |  | ||||||
|                                 _ShortcutRow( |  | ||||||
|                                   shortcut: 'Esc', |  | ||||||
|                                   description: |  | ||||||
|                                       'settingsKeyboardShortcutCloseDialog' |  | ||||||
|                                           .tr(), |  | ||||||
|                                 ), |  | ||||||
|                                 // Add more shortcuts as needed |  | ||||||
|                               ], |  | ||||||
|                             ), |  | ||||||
|                           ), |  | ||||||
|                           actions: [ |  | ||||||
|                             TextButton( |  | ||||||
|                               onPressed: () => Navigator.of(context).pop(), |  | ||||||
|                               child: Text('close').tr(), |  | ||||||
|                             ), |  | ||||||
|                           ], |  | ||||||
|                         ), |  | ||||||
|                   ); |  | ||||||
|                 }, |  | ||||||
|                 trailing: const Icon(Symbols.chevron_right), |  | ||||||
|               ), |  | ||||||
|             ]; |  | ||||||
|  |  | ||||||
|     // Create a responsive layout based on screen width |     // Create a responsive layout based on screen width | ||||||
|     Widget buildSettingsList() { |     Widget buildSettingsList() { | ||||||
| @@ -553,34 +539,7 @@ class SettingsScreen extends HookConsumerWidget { | |||||||
|  |  | ||||||
|     return AppScaffold( |     return AppScaffold( | ||||||
|       isNoBackground: false, |       isNoBackground: false, | ||||||
|       appBar: AppBar( |       appBar: AppBar(title: Text('settings').tr()), | ||||||
|         title: Text('settings').tr(), |  | ||||||
|         actions: |  | ||||||
|             isDesktop |  | ||||||
|                 ? [ |  | ||||||
|                   IconButton( |  | ||||||
|                     icon: const Icon(Symbols.help_outline), |  | ||||||
|                     onPressed: () { |  | ||||||
|                       // Show help dialog |  | ||||||
|                       showDialog( |  | ||||||
|                         context: context, |  | ||||||
|                         builder: |  | ||||||
|                             (context) => AlertDialog( |  | ||||||
|                               title: Text('settingsHelp').tr(), |  | ||||||
|                               content: Text('settingsHelpContent').tr(), |  | ||||||
|                               actions: [ |  | ||||||
|                                 TextButton( |  | ||||||
|                                   onPressed: () => Navigator.of(context).pop(), |  | ||||||
|                                   child: Text('close').tr(), |  | ||||||
|                                 ), |  | ||||||
|                               ], |  | ||||||
|                             ), |  | ||||||
|                       ); |  | ||||||
|                     }, |  | ||||||
|                   ), |  | ||||||
|                 ] |  | ||||||
|                 : null, |  | ||||||
|       ), |  | ||||||
|       body: Focus( |       body: Focus( | ||||||
|         autofocus: true, |         autofocus: true, | ||||||
|         onKeyEvent: (node, event) { |         onKeyEvent: (node, event) { | ||||||
| @@ -630,35 +589,3 @@ class _SettingsSection extends StatelessWidget { | |||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| // Helper widget for displaying keyboard shortcuts |  | ||||||
| class _ShortcutRow extends StatelessWidget { |  | ||||||
|   final String shortcut; |  | ||||||
|   final String description; |  | ||||||
|  |  | ||||||
|   const _ShortcutRow({required this.shortcut, required this.description}); |  | ||||||
|  |  | ||||||
|   @override |  | ||||||
|   Widget build(BuildContext context) { |  | ||||||
|     return Padding( |  | ||||||
|       padding: const EdgeInsets.symmetric(vertical: 8.0), |  | ||||||
|       child: Row( |  | ||||||
|         children: [ |  | ||||||
|           Container( |  | ||||||
|             padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4), |  | ||||||
|             decoration: BoxDecoration( |  | ||||||
|               color: Theme.of(context).colorScheme.surfaceVariant, |  | ||||||
|               borderRadius: BorderRadius.circular(4), |  | ||||||
|               border: Border.all( |  | ||||||
|                 color: Theme.of(context).colorScheme.outline.withOpacity(0.5), |  | ||||||
|               ), |  | ||||||
|             ), |  | ||||||
|             child: Text(shortcut, style: TextStyle(fontFamily: 'monospace')), |  | ||||||
|           ), |  | ||||||
|           SizedBox(width: 16), |  | ||||||
|           Text(description), |  | ||||||
|         ], |  | ||||||
|       ), |  | ||||||
|     ); |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|   | |||||||
| @@ -77,7 +77,7 @@ class MarketplaceStickersScreen extends HookConsumerWidget { | |||||||
|         searchController.clear(); |         searchController.clear(); | ||||||
|       } |       } | ||||||
|       return null; |       return null; | ||||||
|     }, [query.value]); |     }, [query]); | ||||||
|  |  | ||||||
|     // Clean up timer on dispose |     // Clean up timer on dispose | ||||||
|     useEffect(() { |     useEffect(() { | ||||||
|   | |||||||
							
								
								
									
										62
									
								
								lib/screens/tray_manager.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								lib/screens/tray_manager.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,62 @@ | |||||||
|  | import 'dart:io'; | ||||||
|  |  | ||||||
|  | import 'package:bitsdojo_window/bitsdojo_window.dart'; | ||||||
|  | import 'package:flutter/foundation.dart'; | ||||||
|  | import 'package:tray_manager/tray_manager.dart'; | ||||||
|  |  | ||||||
|  | class TrayService { | ||||||
|  |   TrayService._(); | ||||||
|  |  | ||||||
|  |   static final TrayService _instance = TrayService._(); | ||||||
|  |  | ||||||
|  |   static TrayService get instance => _instance; | ||||||
|  |  | ||||||
|  |   bool _checkPlatformAvalability() { | ||||||
|  |     if (kIsWeb) return false; | ||||||
|  |     if (Platform.isAndroid || Platform.isIOS) return false; | ||||||
|  |     return true; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   Future<void> initialize(TrayListener listener) async { | ||||||
|  |     if (!_checkPlatformAvalability()) return; | ||||||
|  |  | ||||||
|  |     await trayManager.setIcon( | ||||||
|  |       Platform.isWindows | ||||||
|  |           ? 'assets/icons/icon.ico' | ||||||
|  |           : 'assets/icons/icon-outline.svg', | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  |     final menu = Menu( | ||||||
|  |       items: [ | ||||||
|  |         MenuItem(key: 'show_window', label: 'Show Window'), | ||||||
|  |         MenuItem.separator(), | ||||||
|  |         MenuItem(key: 'exit_app', label: 'Exit App'), | ||||||
|  |       ], | ||||||
|  |     ); | ||||||
|  |     await trayManager.setContextMenu(menu); | ||||||
|  |  | ||||||
|  |     trayManager.addListener(listener); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   Future<void> dispose(TrayListener listener) async { | ||||||
|  |     if (!_checkPlatformAvalability()) return; | ||||||
|  |  | ||||||
|  |     trayManager.removeListener(listener); | ||||||
|  |     await trayManager.destroy(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   void handleAction(MenuItem item) { | ||||||
|  |     switch (item.key) { | ||||||
|  |       case 'show_window': | ||||||
|  |         if (appWindow.isVisible) { | ||||||
|  |           appWindow.restore(); | ||||||
|  |         } else { | ||||||
|  |           appWindow.show(); | ||||||
|  |         } | ||||||
|  |         break; | ||||||
|  |       case 'exit_app': | ||||||
|  |         appWindow.close(); | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -6,6 +6,7 @@ import 'package:dio/dio.dart'; | |||||||
| import 'package:firebase_messaging/firebase_messaging.dart'; | import 'package:firebase_messaging/firebase_messaging.dart'; | ||||||
| import 'package:flutter/foundation.dart'; | import 'package:flutter/foundation.dart'; | ||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:flutter_local_notifications/flutter_local_notifications.dart'; | ||||||
| import 'package:flutter_riverpod/flutter_riverpod.dart'; | import 'package:flutter_riverpod/flutter_riverpod.dart'; | ||||||
| import 'package:go_router/go_router.dart'; | import 'package:go_router/go_router.dart'; | ||||||
| import 'package:island/main.dart'; | import 'package:island/main.dart'; | ||||||
| @@ -16,54 +17,159 @@ import 'package:island/widgets/app_notification.dart'; | |||||||
| import 'package:top_snackbar_flutter/top_snack_bar.dart'; | import 'package:top_snackbar_flutter/top_snack_bar.dart'; | ||||||
| import 'package:url_launcher/url_launcher_string.dart'; | import 'package:url_launcher/url_launcher_string.dart'; | ||||||
|  |  | ||||||
|  | final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = | ||||||
|  |     FlutterLocalNotificationsPlugin(); | ||||||
|  |  | ||||||
|  | AppLifecycleState _appLifecycleState = AppLifecycleState.resumed; | ||||||
|  |  | ||||||
|  | void _onAppLifecycleChanged(AppLifecycleState state) { | ||||||
|  |   _appLifecycleState = state; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | Future<void> initializeLocalNotifications() async { | ||||||
|  |   const AndroidInitializationSettings initializationSettingsAndroid = | ||||||
|  |       AndroidInitializationSettings('@mipmap/ic_launcher'); | ||||||
|  |  | ||||||
|  |   const DarwinInitializationSettings initializationSettingsIOS = | ||||||
|  |       DarwinInitializationSettings(); | ||||||
|  |  | ||||||
|  |   const DarwinInitializationSettings initializationSettingsMacOS = | ||||||
|  |       DarwinInitializationSettings(); | ||||||
|  |  | ||||||
|  |   const LinuxInitializationSettings initializationSettingsLinux = | ||||||
|  |       LinuxInitializationSettings(defaultActionName: 'Open notification'); | ||||||
|  |  | ||||||
|  |   const WindowsInitializationSettings initializationSettingsWindows = | ||||||
|  |       WindowsInitializationSettings( | ||||||
|  |         appName: 'Island', | ||||||
|  |         appUserModelId: 'dev.solsynth.solian', | ||||||
|  |         guid: 'dev.solsynth.solian', | ||||||
|  |       ); | ||||||
|  |  | ||||||
|  |   const InitializationSettings initializationSettings = InitializationSettings( | ||||||
|  |     android: initializationSettingsAndroid, | ||||||
|  |     iOS: initializationSettingsIOS, | ||||||
|  |     macOS: initializationSettingsMacOS, | ||||||
|  |     linux: initializationSettingsLinux, | ||||||
|  |     windows: initializationSettingsWindows, | ||||||
|  |   ); | ||||||
|  |  | ||||||
|  |   await flutterLocalNotificationsPlugin.initialize( | ||||||
|  |     initializationSettings, | ||||||
|  |     onDidReceiveNotificationResponse: (NotificationResponse response) async { | ||||||
|  |       final payload = response.payload; | ||||||
|  |       if (payload != null) { | ||||||
|  |         if (payload.startsWith('/')) { | ||||||
|  |           // In-app routes | ||||||
|  |           rootNavigatorKey.currentContext?.push(payload); | ||||||
|  |         } else { | ||||||
|  |           // External URLs | ||||||
|  |           launchUrlString(payload); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |   ); | ||||||
|  |  | ||||||
|  |   WidgetsBinding.instance.addObserver( | ||||||
|  |     LifecycleEventHandler(onAppLifecycleChanged: _onAppLifecycleChanged), | ||||||
|  |   ); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class LifecycleEventHandler extends WidgetsBindingObserver { | ||||||
|  |   final void Function(AppLifecycleState) onAppLifecycleChanged; | ||||||
|  |  | ||||||
|  |   LifecycleEventHandler({required this.onAppLifecycleChanged}); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   void didChangeAppLifecycleState(AppLifecycleState state) { | ||||||
|  |     onAppLifecycleChanged(state); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
| StreamSubscription<WebSocketPacket> setupNotificationListener( | StreamSubscription<WebSocketPacket> setupNotificationListener( | ||||||
|   BuildContext context, |   BuildContext context, | ||||||
|   WidgetRef ref, |   WidgetRef ref, | ||||||
| ) { | ) { | ||||||
|   final ws = ref.watch(websocketProvider); |   final ws = ref.watch(websocketProvider); | ||||||
|   return ws.dataStream.listen((pkt) { |   return ws.dataStream.listen((pkt) async { | ||||||
|     if (pkt.type == "notifications.new") { |     if (pkt.type == "notifications.new") { | ||||||
|       final notification = SnNotification.fromJson(pkt.data!); |       final notification = SnNotification.fromJson(pkt.data!); | ||||||
|       showTopSnackBar( |       if (_appLifecycleState == AppLifecycleState.resumed) { | ||||||
|         globalOverlay.currentState!, |         // App is focused, show in-app notification | ||||||
|         Center( |         log( | ||||||
|           child: ConstrainedBox( |           '[Notification] Showing in-app notification: ${notification.title}', | ||||||
|             constraints: const BoxConstraints(maxWidth: 480), |         ); | ||||||
|             child: NotificationCard(notification: notification), |         showTopSnackBar( | ||||||
|  |           globalOverlay.currentState!, | ||||||
|  |           Center( | ||||||
|  |             child: ConstrainedBox( | ||||||
|  |               constraints: const BoxConstraints(maxWidth: 480), | ||||||
|  |               child: NotificationCard(notification: notification), | ||||||
|  |             ), | ||||||
|           ), |           ), | ||||||
|         ), |           onTap: () { | ||||||
|         onTap: () { |             if (notification.meta['action_uri'] != null) { | ||||||
|           if (notification.meta['action_uri'] != null) { |               var uri = notification.meta['action_uri'] as String; | ||||||
|             var uri = notification.meta['action_uri'] as String; |               if (uri.startsWith('/')) { | ||||||
|             if (uri.startsWith('/')) { |                 // In-app routes | ||||||
|               // In-app routes |                 rootNavigatorKey.currentContext?.push( | ||||||
|               rootNavigatorKey.currentContext?.push( |                   notification.meta['action_uri'], | ||||||
|                 notification.meta['action_uri'], |                 ); | ||||||
|               ); |               } else { | ||||||
|             } else { |                 // External URLs | ||||||
|               // External URLs |                 launchUrlString(uri); | ||||||
|               launchUrlString(uri); |               } | ||||||
|             } |             } | ||||||
|           } |           }, | ||||||
|         }, |           onDismissed: () {}, | ||||||
|         onDismissed: () {}, |           dismissType: DismissType.onSwipe, | ||||||
|         dismissType: DismissType.onSwipe, |           displayDuration: const Duration(seconds: 5), | ||||||
|         displayDuration: const Duration(seconds: 5), |           snackBarPosition: SnackBarPosition.top, | ||||||
|         snackBarPosition: SnackBarPosition.top, |           padding: EdgeInsets.only( | ||||||
|         padding: EdgeInsets.only( |             left: 16, | ||||||
|           left: 16, |             right: 16, | ||||||
|           right: 16, |             top: | ||||||
|           top: |                 (!kIsWeb && | ||||||
|               (!kIsWeb && |                         (Platform.isMacOS || | ||||||
|                       (Platform.isMacOS || |                             Platform.isWindows || | ||||||
|                           Platform.isWindows || |                             Platform.isLinux)) | ||||||
|                           Platform.isLinux)) |                     ? 28 | ||||||
|                   ? 28 |                     // ignore: use_build_context_synchronously | ||||||
|                   // ignore: use_build_context_synchronously |                     : MediaQuery.of(context).padding.top + 16, | ||||||
|                   : MediaQuery.of(context).padding.top + 16, |             bottom: 16, | ||||||
|           bottom: 16, |           ), | ||||||
|         ), |         ); | ||||||
|       ); |       } else { | ||||||
|  |         // App is in background, show system notification (only on supported platforms) | ||||||
|  |         if (!kIsWeb && !Platform.isIOS) { | ||||||
|  |           log( | ||||||
|  |             '[Notification] Showing system notification: ${notification.title}', | ||||||
|  |           ); | ||||||
|  |           const AndroidNotificationDetails androidNotificationDetails = | ||||||
|  |               AndroidNotificationDetails( | ||||||
|  |                 'channel_id', | ||||||
|  |                 'channel_name', | ||||||
|  |                 channelDescription: 'channel_description', | ||||||
|  |                 importance: Importance.max, | ||||||
|  |                 priority: Priority.high, | ||||||
|  |                 ticker: 'ticker', | ||||||
|  |               ); | ||||||
|  |           const NotificationDetails notificationDetails = NotificationDetails( | ||||||
|  |             android: androidNotificationDetails, | ||||||
|  |           ); | ||||||
|  |           await flutterLocalNotificationsPlugin.show( | ||||||
|  |             0, | ||||||
|  |             notification.title, | ||||||
|  |             notification.content, | ||||||
|  |             notificationDetails, | ||||||
|  |             payload: notification.meta['action_uri'] as String?, | ||||||
|  |           ); | ||||||
|  |         } else { | ||||||
|  |           log( | ||||||
|  |             '[Notification] Skipping system notification for unsupported platform: ${notification.title}', | ||||||
|  |           ); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|     } |     } | ||||||
|   }); |   }); | ||||||
| } | } | ||||||
| @@ -72,7 +178,7 @@ Future<void> subscribePushNotification( | |||||||
|   Dio apiClient, { |   Dio apiClient, { | ||||||
|   bool detailedErrors = false, |   bool detailedErrors = false, | ||||||
| }) async { | }) async { | ||||||
|   if (Platform.isLinux) { |   if (!kIsWeb && Platform.isLinux) { | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|   await FirebaseMessaging.instance.requestPermission( |   await FirebaseMessaging.instance.requestPermission( | ||||||
|   | |||||||
| @@ -1,5 +1,11 @@ | |||||||
| import 'package:flutter_udid/flutter_udid.dart'; | import 'package:flutter_udid/flutter_udid.dart'; | ||||||
|  |  | ||||||
|  | String? _cachedUdid; | ||||||
|  |  | ||||||
| Future<String> getUdid() async { | Future<String> getUdid() async { | ||||||
|   return await FlutterUdid.consistentUdid; |   if (_cachedUdid != null) { | ||||||
|  |     return _cachedUdid!; | ||||||
|  |   } | ||||||
|  |   _cachedUdid = await FlutterUdid.consistentUdid; | ||||||
|  |   return _cachedUdid!; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ import 'dart:async'; | |||||||
| import 'dart:developer'; | import 'dart:developer'; | ||||||
| import 'dart:io'; | import 'dart:io'; | ||||||
|  |  | ||||||
|  | import 'package:archive/archive.dart'; | ||||||
| import 'package:dio/dio.dart'; | import 'package:dio/dio.dart'; | ||||||
| import 'package:flutter/foundation.dart'; | import 'package:flutter/foundation.dart'; | ||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
| @@ -10,6 +11,9 @@ import 'package:flutter_app_update/update_model.dart'; | |||||||
| import 'package:island/widgets/content/markdown.dart'; | import 'package:island/widgets/content/markdown.dart'; | ||||||
| import 'package:material_symbols_icons/symbols.dart'; | import 'package:material_symbols_icons/symbols.dart'; | ||||||
| import 'package:package_info_plus/package_info_plus.dart'; | import 'package:package_info_plus/package_info_plus.dart'; | ||||||
|  | import 'package:path_provider/path_provider.dart'; | ||||||
|  | import 'package:path/path.dart' as path; | ||||||
|  | import 'package:process_run/process_run.dart'; | ||||||
| import 'package:collection/collection.dart'; // Added for firstWhereOrNull | import 'package:collection/collection.dart'; // Added for firstWhereOrNull | ||||||
| import 'package:styled_widget/styled_widget.dart'; | import 'package:styled_widget/styled_widget.dart'; | ||||||
| import 'package:url_launcher/url_launcher.dart'; | import 'package:url_launcher/url_launcher.dart'; | ||||||
| @@ -180,9 +184,13 @@ class UpdateService { | |||||||
|       useRootNavigator: true, |       useRootNavigator: true, | ||||||
|       builder: (ctx) { |       builder: (ctx) { | ||||||
|         String? androidUpdateUrl; |         String? androidUpdateUrl; | ||||||
|  |         String? windowsUpdateUrl; | ||||||
|         if (Platform.isAndroid) { |         if (Platform.isAndroid) { | ||||||
|           androidUpdateUrl = _getAndroidUpdateUrl(release.assets); |           androidUpdateUrl = _getAndroidUpdateUrl(release.assets); | ||||||
|         } |         } | ||||||
|  |         if (Platform.isWindows) { | ||||||
|  |           windowsUpdateUrl = _getWindowsUpdateUrl(); | ||||||
|  |         } | ||||||
|         return _UpdateSheet( |         return _UpdateSheet( | ||||||
|           release: release, |           release: release, | ||||||
|           onOpen: () async { |           onOpen: () async { | ||||||
| @@ -192,6 +200,7 @@ class UpdateService { | |||||||
|             } |             } | ||||||
|           }, |           }, | ||||||
|           androidUpdateUrl: androidUpdateUrl, |           androidUpdateUrl: androidUpdateUrl, | ||||||
|  |           windowsUpdateUrl: windowsUpdateUrl, | ||||||
|           useProxy: useProxy, // Pass the useProxy flag |           useProxy: useProxy, // Pass the useProxy flag | ||||||
|         ); |         ); | ||||||
|       }, |       }, | ||||||
| @@ -211,15 +220,270 @@ class UpdateService { | |||||||
|  |  | ||||||
|     // Prioritize arm64, then armeabi, then x86_64 |     // Prioritize arm64, then armeabi, then x86_64 | ||||||
|     if (arm64 != null) { |     if (arm64 != null) { | ||||||
|       return arm64.browserDownloadUrl; |       return 'https://fs.solsynth.dev/d/official/solian/${arm64.name}'; | ||||||
|     } else if (armeabi != null) { |     } else if (armeabi != null) { | ||||||
|       return armeabi.browserDownloadUrl; |       return 'https://fs.solsynth.dev/d/official/solian/${armeabi.name}'; | ||||||
|     } else if (x86_64 != null) { |     } else if (x86_64 != null) { | ||||||
|       return x86_64.browserDownloadUrl; |       return 'https://fs.solsynth.dev/d/official/solian/${x86_64.name}'; | ||||||
|     } |     } | ||||||
|     return null; |     return null; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   String _getWindowsUpdateUrl() { | ||||||
|  |     return 'https://fs.solsynth.dev/d/official/solian/build-output-windows-installer.zip'; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   /// Downloads the Windows installer ZIP file | ||||||
|  |   Future<String?> _downloadWindowsInstaller(String url) async { | ||||||
|  |     try { | ||||||
|  |       log('[Update] Starting Windows installer download from: $url'); | ||||||
|  |  | ||||||
|  |       final tempDir = await getTemporaryDirectory(); | ||||||
|  |       final fileName = | ||||||
|  |           'solian-installer-${DateTime.now().millisecondsSinceEpoch}.zip'; | ||||||
|  |       final filePath = path.join(tempDir.path, fileName); | ||||||
|  |  | ||||||
|  |       final response = await _dio.download( | ||||||
|  |         url, | ||||||
|  |         filePath, | ||||||
|  |         onReceiveProgress: (received, total) { | ||||||
|  |           if (total != -1) { | ||||||
|  |             log( | ||||||
|  |               '[Update] Download progress: ${(received / total * 100).toStringAsFixed(1)}%', | ||||||
|  |             ); | ||||||
|  |           } | ||||||
|  |         }, | ||||||
|  |       ); | ||||||
|  |  | ||||||
|  |       if (response.statusCode == 200) { | ||||||
|  |         log('[Update] Windows installer downloaded successfully to: $filePath'); | ||||||
|  |         return filePath; | ||||||
|  |       } else { | ||||||
|  |         log( | ||||||
|  |           '[Update] Failed to download Windows installer. Status: ${response.statusCode}', | ||||||
|  |         ); | ||||||
|  |         return null; | ||||||
|  |       } | ||||||
|  |     } catch (e) { | ||||||
|  |       log('[Update] Error downloading Windows installer: $e'); | ||||||
|  |       return null; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   /// Extracts the ZIP file to a temporary directory | ||||||
|  |   Future<String?> _extractWindowsInstaller(String zipPath) async { | ||||||
|  |     try { | ||||||
|  |       log('[Update] Extracting Windows installer from: $zipPath'); | ||||||
|  |  | ||||||
|  |       final tempDir = await getTemporaryDirectory(); | ||||||
|  |       final extractDir = path.join( | ||||||
|  |         tempDir.path, | ||||||
|  |         'solian-installer-${DateTime.now().millisecondsSinceEpoch}', | ||||||
|  |       ); | ||||||
|  |  | ||||||
|  |       final zipFile = File(zipPath); | ||||||
|  |       final bytes = await zipFile.readAsBytes(); | ||||||
|  |       final archive = ZipDecoder().decodeBytes(bytes); | ||||||
|  |  | ||||||
|  |       for (final file in archive) { | ||||||
|  |         final filename = file.name; | ||||||
|  |         if (file.isFile) { | ||||||
|  |           final data = file.content as List<int>; | ||||||
|  |           final filePath = path.join(extractDir, filename); | ||||||
|  |           await Directory(path.dirname(filePath)).create(recursive: true); | ||||||
|  |           await File(filePath).writeAsBytes(data); | ||||||
|  |         } else { | ||||||
|  |           final dirPath = path.join(extractDir, filename); | ||||||
|  |           await Directory(dirPath).create(recursive: true); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       log('[Update] Windows installer extracted successfully to: $extractDir'); | ||||||
|  |       return extractDir; | ||||||
|  |     } catch (e) { | ||||||
|  |       log('[Update] Error extracting Windows installer: $e'); | ||||||
|  |       return null; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   /// Runs the setup.exe file | ||||||
|  |   Future<bool> _runWindowsInstaller(String extractDir) async { | ||||||
|  |     try { | ||||||
|  |       log('[Update] Running Windows installer from: $extractDir'); | ||||||
|  |  | ||||||
|  |       final setupExePath = path.join(extractDir, 'setup.exe'); | ||||||
|  |  | ||||||
|  |       if (!await File(setupExePath).exists()) { | ||||||
|  |         log('[Update] setup.exe not found in extracted directory'); | ||||||
|  |         return false; | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       final shell = Shell(); | ||||||
|  |       final results = await shell.run(setupExePath); | ||||||
|  |       final result = results.first; | ||||||
|  |  | ||||||
|  |       if (result.exitCode == 0) { | ||||||
|  |         log('[Update] Windows installer completed successfully'); | ||||||
|  |         return true; | ||||||
|  |       } else { | ||||||
|  |         log( | ||||||
|  |           '[Update] Windows installer failed with exit code: ${result.exitCode}', | ||||||
|  |         ); | ||||||
|  |         log('[Update] Installer output: ${result.stdout}'); | ||||||
|  |         log('[Update] Installer errors: ${result.stderr}'); | ||||||
|  |         return false; | ||||||
|  |       } | ||||||
|  |     } catch (e) { | ||||||
|  |       log('[Update] Error running Windows installer: $e'); | ||||||
|  |       return false; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   /// Performs automatic Windows update: download, extract, and install | ||||||
|  |   Future<void> _performAutomaticWindowsUpdate( | ||||||
|  |     BuildContext context, | ||||||
|  |     String url, | ||||||
|  |   ) async { | ||||||
|  |     if (!context.mounted) return; | ||||||
|  |  | ||||||
|  |     // Show progress dialog | ||||||
|  |     showDialog( | ||||||
|  |       context: context, | ||||||
|  |       barrierDismissible: false, | ||||||
|  |       builder: | ||||||
|  |           (context) => const AlertDialog( | ||||||
|  |             title: Text('Installing Update'), | ||||||
|  |             content: Column( | ||||||
|  |               mainAxisSize: MainAxisSize.min, | ||||||
|  |               children: [ | ||||||
|  |                 CircularProgressIndicator(), | ||||||
|  |                 SizedBox(height: 16), | ||||||
|  |                 Text('Downloading installer...'), | ||||||
|  |               ], | ||||||
|  |             ), | ||||||
|  |           ), | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  |     try { | ||||||
|  |       // Step 1: Download | ||||||
|  |       if (!context.mounted) return; | ||||||
|  |       Navigator.of(context).pop(); // Close progress dialog | ||||||
|  |       showDialog( | ||||||
|  |         context: context, | ||||||
|  |         barrierDismissible: false, | ||||||
|  |         builder: | ||||||
|  |             (context) => const AlertDialog( | ||||||
|  |               title: Text('Installing Update'), | ||||||
|  |               content: Column( | ||||||
|  |                 mainAxisSize: MainAxisSize.min, | ||||||
|  |                 children: [ | ||||||
|  |                   CircularProgressIndicator(), | ||||||
|  |                   SizedBox(height: 16), | ||||||
|  |                   Text('Extracting installer...'), | ||||||
|  |                 ], | ||||||
|  |               ), | ||||||
|  |             ), | ||||||
|  |       ); | ||||||
|  |  | ||||||
|  |       final zipPath = await _downloadWindowsInstaller(url); | ||||||
|  |       if (zipPath == null) { | ||||||
|  |         if (!context.mounted) return; | ||||||
|  |         Navigator.of(context).pop(); | ||||||
|  |         _showErrorDialog(context, 'Failed to download installer'); | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       // Step 2: Extract | ||||||
|  |       if (!context.mounted) return; | ||||||
|  |       Navigator.of(context).pop(); // Close progress dialog | ||||||
|  |       showDialog( | ||||||
|  |         context: context, | ||||||
|  |         barrierDismissible: false, | ||||||
|  |         builder: | ||||||
|  |             (context) => const AlertDialog( | ||||||
|  |               title: Text('Installing Update'), | ||||||
|  |               content: Column( | ||||||
|  |                 mainAxisSize: MainAxisSize.min, | ||||||
|  |                 children: [ | ||||||
|  |                   CircularProgressIndicator(), | ||||||
|  |                   SizedBox(height: 16), | ||||||
|  |                   Text('Running installer...'), | ||||||
|  |                 ], | ||||||
|  |               ), | ||||||
|  |             ), | ||||||
|  |       ); | ||||||
|  |  | ||||||
|  |       final extractDir = await _extractWindowsInstaller(zipPath); | ||||||
|  |       if (extractDir == null) { | ||||||
|  |         if (!context.mounted) return; | ||||||
|  |         Navigator.of(context).pop(); | ||||||
|  |         _showErrorDialog(context, 'Failed to extract installer'); | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       // Step 3: Run installer | ||||||
|  |       if (!context.mounted) return; | ||||||
|  |       Navigator.of(context).pop(); // Close progress dialog | ||||||
|  |  | ||||||
|  |       final success = await _runWindowsInstaller(extractDir); | ||||||
|  |       if (!context.mounted) return; | ||||||
|  |  | ||||||
|  |       if (success) { | ||||||
|  |         showDialog( | ||||||
|  |           context: context, | ||||||
|  |           builder: | ||||||
|  |               (context) => AlertDialog( | ||||||
|  |                 title: const Text('Update Complete'), | ||||||
|  |                 content: const Text( | ||||||
|  |                   'The application has been updated successfully. Please restart the application.', | ||||||
|  |                 ), | ||||||
|  |                 actions: [ | ||||||
|  |                   TextButton( | ||||||
|  |                     onPressed: () { | ||||||
|  |                       Navigator.of(context).pop(); | ||||||
|  |                       // Close the update sheet | ||||||
|  |                       Navigator.of(context).pop(); | ||||||
|  |                     }, | ||||||
|  |                     child: const Text('OK'), | ||||||
|  |                   ), | ||||||
|  |                 ], | ||||||
|  |               ), | ||||||
|  |         ); | ||||||
|  |       } else { | ||||||
|  |         _showErrorDialog(context, 'Failed to run installer'); | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       // Cleanup | ||||||
|  |       try { | ||||||
|  |         await File(zipPath).delete(); | ||||||
|  |         await Directory(extractDir).delete(recursive: true); | ||||||
|  |       } catch (e) { | ||||||
|  |         log('[Update] Error cleaning up temporary files: $e'); | ||||||
|  |       } | ||||||
|  |     } catch (e) { | ||||||
|  |       if (!context.mounted) return; | ||||||
|  |       Navigator.of(context).pop(); // Close any open dialogs | ||||||
|  |       _showErrorDialog(context, 'Update failed: $e'); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   void _showErrorDialog(BuildContext context, String message) { | ||||||
|  |     showDialog( | ||||||
|  |       context: context, | ||||||
|  |       builder: | ||||||
|  |           (context) => AlertDialog( | ||||||
|  |             title: const Text('Update Failed'), | ||||||
|  |             content: Text(message), | ||||||
|  |             actions: [ | ||||||
|  |               TextButton( | ||||||
|  |                 onPressed: () => Navigator.of(context).pop(), | ||||||
|  |                 child: const Text('OK'), | ||||||
|  |               ), | ||||||
|  |             ], | ||||||
|  |           ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|   /// Fetch the latest release info from GitHub. |   /// Fetch the latest release info from GitHub. | ||||||
|   /// Public so other screens (e.g., About) can manually trigger update checks. |   /// Public so other screens (e.g., About) can manually trigger update checks. | ||||||
|   Future<GithubReleaseInfo?> fetchLatestRelease() async { |   Future<GithubReleaseInfo?> fetchLatestRelease() async { | ||||||
| @@ -277,10 +541,12 @@ class _UpdateSheet extends StatefulWidget { | |||||||
|     required this.release, |     required this.release, | ||||||
|     required this.onOpen, |     required this.onOpen, | ||||||
|     this.androidUpdateUrl, |     this.androidUpdateUrl, | ||||||
|  |     this.windowsUpdateUrl, | ||||||
|     this.useProxy = false, |     this.useProxy = false, | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
|   final String? androidUpdateUrl; |   final String? androidUpdateUrl; | ||||||
|  |   final String? windowsUpdateUrl; | ||||||
|   final bool useProxy; |   final bool useProxy; | ||||||
|   final GithubReleaseInfo release; |   final GithubReleaseInfo release; | ||||||
|   final VoidCallback onOpen; |   final VoidCallback onOpen; | ||||||
| @@ -299,8 +565,11 @@ class _UpdateSheetState extends State<_UpdateSheet> { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   Future<void> _installUpdate(String url) async { |   Future<void> _installUpdate(String url) async { | ||||||
|     final downloadUrl = |     String downloadUrl = url; | ||||||
|         _useProxy ? 'https://ghfast.top/${Uri.encodeComponent(url)}' : url; |     if (_useProxy) { | ||||||
|  |       final fileName = url.split('/').last; | ||||||
|  |       downloadUrl = 'https://fs.solsynth.dev/d/rainyun02/solian/$fileName'; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     UpdateModel model = UpdateModel( |     UpdateModel model = UpdateModel( | ||||||
|       downloadUrl, |       downloadUrl, | ||||||
| @@ -350,7 +619,7 @@ class _UpdateSheetState extends State<_UpdateSheet> { | |||||||
|             ), |             ), | ||||||
|             if (!kIsWeb && Platform.isAndroid) |             if (!kIsWeb && Platform.isAndroid) | ||||||
|               SwitchListTile( |               SwitchListTile( | ||||||
|                 title: const Text('Use GitHub Proxy for Download'), |                 title: const Text('Use secondary source for download'), | ||||||
|                 value: _useProxy, |                 value: _useProxy, | ||||||
|                 onChanged: (value) { |                 onChanged: (value) { | ||||||
|                   setState(() { |                   setState(() { | ||||||
| @@ -376,6 +645,25 @@ class _UpdateSheetState extends State<_UpdateSheet> { | |||||||
|                           label: const Text('Install update'), |                           label: const Text('Install update'), | ||||||
|                         ), |                         ), | ||||||
|                       ), |                       ), | ||||||
|  |                     if (!kIsWeb && | ||||||
|  |                         Platform.isWindows && | ||||||
|  |                         widget.windowsUpdateUrl != null) | ||||||
|  |                       Expanded( | ||||||
|  |                         child: FilledButton.icon( | ||||||
|  |                           onPressed: () { | ||||||
|  |                             // Access the UpdateService instance to call the automatic update method | ||||||
|  |                             final updateService = UpdateService( | ||||||
|  |                               useProxy: widget.useProxy, | ||||||
|  |                             ); | ||||||
|  |                             updateService._performAutomaticWindowsUpdate( | ||||||
|  |                               context, | ||||||
|  |                               widget.windowsUpdateUrl!, | ||||||
|  |                             ); | ||||||
|  |                           }, | ||||||
|  |                           icon: const Icon(Symbols.update), | ||||||
|  |                           label: const Text('Install update'), | ||||||
|  |                         ), | ||||||
|  |                       ), | ||||||
|                     Expanded( |                     Expanded( | ||||||
|                       child: FilledButton.icon( |                       child: FilledButton.icon( | ||||||
|                         onPressed: widget.onOpen, |                         onPressed: widget.onOpen, | ||||||
|   | |||||||
							
								
								
									
										9
									
								
								lib/utils/format.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								lib/utils/format.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | |||||||
|  | String formatFileSize(int bytes) { | ||||||
|  |   if (bytes <= 0) return '0 B'; | ||||||
|  |   if (bytes < 1024) return '$bytes B'; | ||||||
|  |   if (bytes < 1024 * 1024) return '${(bytes / 1024).toStringAsFixed(2)} KB'; | ||||||
|  |   if (bytes < 1024 * 1024 * 1024) { | ||||||
|  |     return '${(bytes / (1024 * 1024)).toStringAsFixed(2)} MB'; | ||||||
|  |   } | ||||||
|  |   return '${(bytes / (1024 * 1024 * 1024)).toStringAsFixed(2)} GB'; | ||||||
|  | } | ||||||
							
								
								
									
										62
									
								
								lib/utils/share_utils.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								lib/utils/share_utils.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,62 @@ | |||||||
|  | import 'dart:io'; | ||||||
|  |  | ||||||
|  | import 'package:flutter/foundation.dart'; | ||||||
|  | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
|  | import 'package:island/models/post.dart'; | ||||||
|  | import 'package:island/pods/config.dart'; | ||||||
|  | import 'package:island/widgets/alert.dart'; | ||||||
|  | import 'package:island/widgets/post/post_item_screenshot.dart'; | ||||||
|  | import 'package:path_provider/path_provider.dart' show getTemporaryDirectory; | ||||||
|  | import 'package:screenshot/screenshot.dart'; | ||||||
|  | import 'package:share_plus/share_plus.dart'; | ||||||
|  |  | ||||||
|  | /// Shares a post as a screenshot image | ||||||
|  | Future<void> sharePostAsScreenshot( | ||||||
|  |   BuildContext context, | ||||||
|  |   WidgetRef ref, | ||||||
|  |   SnPost post, | ||||||
|  | ) async { | ||||||
|  |   if (kIsWeb) return; | ||||||
|  |  | ||||||
|  |   final screenshotController = ScreenshotController(); | ||||||
|  |  | ||||||
|  |   showLoadingModal(context); | ||||||
|  |   await screenshotController | ||||||
|  |       .captureFromWidget( | ||||||
|  |         ProviderScope( | ||||||
|  |           overrides: [ | ||||||
|  |             sharedPreferencesProvider.overrideWithValue( | ||||||
|  |               ref.watch(sharedPreferencesProvider), | ||||||
|  |             ), | ||||||
|  |           ], | ||||||
|  |           child: Directionality( | ||||||
|  |             textDirection: TextDirection.ltr, | ||||||
|  |             child: SizedBox( | ||||||
|  |               width: 520, | ||||||
|  |               child: PostItemScreenshot(item: post, isFullPost: true), | ||||||
|  |             ), | ||||||
|  |           ), | ||||||
|  |         ), | ||||||
|  |         context: context, | ||||||
|  |         pixelRatio: MediaQuery.of(context).devicePixelRatio, | ||||||
|  |         delay: const Duration(seconds: 1), | ||||||
|  |       ) | ||||||
|  |       .then((Uint8List? image) async { | ||||||
|  |         if (image == null) return; | ||||||
|  |         final directory = await getTemporaryDirectory(); | ||||||
|  |         final imagePath = await File('${directory.path}/image.png').create(); | ||||||
|  |         await imagePath.writeAsBytes(image); | ||||||
|  |  | ||||||
|  |         if (!context.mounted) return; | ||||||
|  |         hideLoadingModal(context); | ||||||
|  |         final box = context.findRenderObject() as RenderBox?; | ||||||
|  |         await Share.shareXFiles([ | ||||||
|  |           XFile(imagePath.path), | ||||||
|  |         ], sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size); | ||||||
|  |       }) | ||||||
|  |       .catchError((err) { | ||||||
|  |         if (context.mounted) hideLoadingModal(context); | ||||||
|  |         showErrorAlert(err); | ||||||
|  |       }); | ||||||
|  | } | ||||||
| @@ -12,6 +12,7 @@ import 'package:island/widgets/content/sheet.dart'; | |||||||
| import 'package:island/widgets/response.dart'; | import 'package:island/widgets/response.dart'; | ||||||
| import 'package:riverpod_annotation/riverpod_annotation.dart'; | import 'package:riverpod_annotation/riverpod_annotation.dart'; | ||||||
| import 'package:styled_widget/styled_widget.dart'; | import 'package:styled_widget/styled_widget.dart'; | ||||||
|  | import 'package:island/widgets/extended_refresh_indicator.dart'; | ||||||
|  |  | ||||||
| part 'account_devices.g.dart'; | part 'account_devices.g.dart'; | ||||||
|  |  | ||||||
| @@ -177,7 +178,7 @@ class AccountSessionSheet extends HookConsumerWidget { | |||||||
|       titleText: 'authSessions'.tr(), |       titleText: 'authSessions'.tr(), | ||||||
|       child: authDevices.when( |       child: authDevices.when( | ||||||
|         data: |         data: | ||||||
|             (data) => RefreshIndicator( |             (data) => ExtendedRefreshIndicator( | ||||||
|               onRefresh: |               onRefresh: | ||||||
|                   () => Future.sync(() => ref.invalidate(authDevicesProvider)), |                   () => Future.sync(() => ref.invalidate(authDevicesProvider)), | ||||||
|               child: ListView.builder( |               child: ListView.builder( | ||||||
|   | |||||||
| @@ -37,7 +37,14 @@ class AccountName extends StatelessWidget { | |||||||
|       mainAxisSize: MainAxisSize.min, |       mainAxisSize: MainAxisSize.min, | ||||||
|       spacing: 4, |       spacing: 4, | ||||||
|       children: [ |       children: [ | ||||||
|         Flexible(child: Text(account.nick, style: nameStyle)), |         Flexible( | ||||||
|  |           child: Text( | ||||||
|  |             account.nick, | ||||||
|  |             style: nameStyle, | ||||||
|  |             maxLines: 1, | ||||||
|  |             overflow: TextOverflow.ellipsis, | ||||||
|  |           ), | ||||||
|  |         ), | ||||||
|         if (account.perkSubscription != null) |         if (account.perkSubscription != null) | ||||||
|           StellarMembershipMark(membership: account.perkSubscription!), |           StellarMembershipMark(membership: account.perkSubscription!), | ||||||
|         if (account.profile.verification != null) |         if (account.profile.verification != null) | ||||||
| @@ -162,7 +169,7 @@ class VerificationStatusCard extends StatelessWidget { | |||||||
|           size: 32, |           size: 32, | ||||||
|           color: kVerificationMarkColors[mark.type], |           color: kVerificationMarkColors[mark.type], | ||||||
|           fill: 1, |           fill: 1, | ||||||
|         ), |         ).alignment(Alignment.centerLeft), | ||||||
|         const Gap(8), |         const Gap(8), | ||||||
|         Text(mark.title ?? 'No title').bold(), |         Text(mark.title ?? 'No title').bold(), | ||||||
|         Text(mark.description ?? 'descriptionNone'.tr()), |         Text(mark.description ?? 'descriptionNone'.tr()), | ||||||
|   | |||||||
| @@ -111,26 +111,39 @@ class AccountProfileCard extends HookConsumerWidget { | |||||||
|                           ], |                           ], | ||||||
|                         ), |                         ), | ||||||
|                       if (data.profile.timeZone.isNotEmpty && !kIsWeb) |                       if (data.profile.timeZone.isNotEmpty && !kIsWeb) | ||||||
|                         Row( |                         () { | ||||||
|                           spacing: 6, |                           try { | ||||||
|                           children: [ |                             final tzInfo = getTzInfo(data.profile.timeZone); | ||||||
|                             Icon( |                             return Row( | ||||||
|                               Symbols.alarm, |                               spacing: 6, | ||||||
|                               size: 17, |                               children: [ | ||||||
|                               fill: 1, |                                 Icon( | ||||||
|                             ).padding(right: 2), |                                   Symbols.alarm, | ||||||
|                             Text( |                                   size: 17, | ||||||
|                               getTzInfo( |                                   fill: 1, | ||||||
|                                 data.profile.timeZone, |                                 ).padding(right: 2), | ||||||
|                               ).$2.formatCustomGlobal('HH:mm'), |                                 Text( | ||||||
|                             ).fontSize(12), |                                   tzInfo.$2.formatCustomGlobal('HH:mm'), | ||||||
|                             Text( |                                 ).fontSize(12), | ||||||
|                               getTzInfo( |                                 Text( | ||||||
|                                 data.profile.timeZone, |                                   tzInfo.$1.formatOffsetLocal(), | ||||||
|                               ).$1.formatOffsetLocal(), |                                 ).fontSize(12), | ||||||
|                             ).fontSize(12), |                               ], | ||||||
|                           ], |                             ).padding(top: 2); | ||||||
|                         ).padding(top: 2), |                           } catch (e) { | ||||||
|  |                             return Row( | ||||||
|  |                               spacing: 6, | ||||||
|  |                               children: [ | ||||||
|  |                                 Icon( | ||||||
|  |                                   Symbols.alarm, | ||||||
|  |                                   size: 17, | ||||||
|  |                                   fill: 1, | ||||||
|  |                                 ).padding(right: 2), | ||||||
|  |                                 Text('timezoneNotFound'.tr()).fontSize(12), | ||||||
|  |                               ], | ||||||
|  |                             ).padding(top: 2); | ||||||
|  |                           } | ||||||
|  |                         }(), | ||||||
|                       if (data.badges.isNotEmpty) |                       if (data.badges.isNotEmpty) | ||||||
|                         BadgeList(badges: data.badges).padding(top: 12), |                         BadgeList(badges: data.badges).padding(top: 12), | ||||||
|                       LevelingProgressCard( |                       LevelingProgressCard( | ||||||
|   | |||||||
| @@ -46,6 +46,10 @@ class EventDetailsWidget extends StatelessWidget { | |||||||
|                       size: 12, |                       size: 12, | ||||||
|                       fill: 1, |                       fill: 1, | ||||||
|                     ).padding(top: 4, right: 4), |                     ).padding(top: 4, right: 4), | ||||||
|  |                     Icon( | ||||||
|  |                       tip.isPositive ? Symbols.thumb_up : Symbols.thumb_down, | ||||||
|  |                       size: 14, | ||||||
|  |                     ).padding(top: 2.5), | ||||||
|                     Expanded( |                     Expanded( | ||||||
|                       child: Column( |                       child: Column( | ||||||
|                         crossAxisAlignment: CrossAxisAlignment.start, |                         crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|   | |||||||
| @@ -60,7 +60,9 @@ class AccountStatusCreationWidget extends HookConsumerWidget { | |||||||
|                         spacing: 4, |                         spacing: 4, | ||||||
|                         children: [ |                         children: [ | ||||||
|                           Icon(Symbols.keyboard_arrow_up), |                           Icon(Symbols.keyboard_arrow_up), | ||||||
|                           Text('statusCreateHint').tr(), |                           Expanded( | ||||||
|  |                             child: Text('statusCreateHint', maxLines: 1).tr(), | ||||||
|  |                           ), | ||||||
|                         ], |                         ], | ||||||
|                       ), |                       ), | ||||||
|                     ).opacity(0.85), |                     ).opacity(0.85), | ||||||
|   | |||||||
| @@ -17,8 +17,8 @@ class NotificationCard extends HookConsumerWidget { | |||||||
|     return Card( |     return Card( | ||||||
|       elevation: 4, |       elevation: 4, | ||||||
|       margin: const EdgeInsets.only(bottom: 8), |       margin: const EdgeInsets.only(bottom: 8), | ||||||
|       shape: RoundedRectangleBorder( |       shape: const RoundedRectangleBorder( | ||||||
|         borderRadius: BorderRadius.vertical(bottom: Radius.circular(8)), |         borderRadius: BorderRadius.all(Radius.circular(8)), | ||||||
|       ), |       ), | ||||||
|       child: Column( |       child: Column( | ||||||
|         crossAxisAlignment: CrossAxisAlignment.start, |         crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|   | |||||||
| @@ -1,8 +1,10 @@ | |||||||
| import 'dart:io'; | import 'dart:io'; | ||||||
|  | import 'dart:ui'; | ||||||
| import 'package:bitsdojo_window/bitsdojo_window.dart'; | import 'package:bitsdojo_window/bitsdojo_window.dart'; | ||||||
| import 'package:easy_localization/easy_localization.dart'; | import 'package:easy_localization/easy_localization.dart'; | ||||||
| import 'package:flutter/foundation.dart'; | import 'package:flutter/foundation.dart'; | ||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:flutter/services.dart'; | ||||||
| import 'package:go_router/go_router.dart'; | import 'package:go_router/go_router.dart'; | ||||||
| import 'package:flutter_hooks/flutter_hooks.dart'; | import 'package:flutter_hooks/flutter_hooks.dart'; | ||||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
| @@ -14,6 +16,15 @@ import 'package:material_symbols_icons/material_symbols_icons.dart'; | |||||||
| import 'package:path_provider/path_provider.dart'; | import 'package:path_provider/path_provider.dart'; | ||||||
| import 'package:styled_widget/styled_widget.dart'; | import 'package:styled_widget/styled_widget.dart'; | ||||||
|  |  | ||||||
|  | class AppScrollBehavior extends MaterialScrollBehavior { | ||||||
|  |   @override | ||||||
|  |   Set<PointerDeviceKind> get dragDevices => { | ||||||
|  |     PointerDeviceKind.touch, // default | ||||||
|  |     PointerDeviceKind.trackpad, // default | ||||||
|  |     PointerDeviceKind.mouse, // add mouse dragging | ||||||
|  |   }; | ||||||
|  | } | ||||||
|  |  | ||||||
| class WindowScaffold extends HookConsumerWidget { | class WindowScaffold extends HookConsumerWidget { | ||||||
|   final Widget child; |   final Widget child; | ||||||
|   const WindowScaffold({super.key, required this.child}); |   const WindowScaffold({super.key, required this.child}); | ||||||
| @@ -153,7 +164,7 @@ class _WindowSizeObserver extends WidgetsBindingObserver { | |||||||
|  |  | ||||||
| final rootScaffoldKey = GlobalKey<ScaffoldState>(); | final rootScaffoldKey = GlobalKey<ScaffoldState>(); | ||||||
|  |  | ||||||
| class AppScaffold extends StatelessWidget { | class AppScaffold extends HookConsumerWidget { | ||||||
|   final Widget? body; |   final Widget? body; | ||||||
|   final PreferredSizeWidget? bottomNavigationBar; |   final PreferredSizeWidget? bottomNavigationBar; | ||||||
|   final PreferredSizeWidget? bottomSheet; |   final PreferredSizeWidget? bottomSheet; | ||||||
| @@ -186,7 +197,14 @@ class AppScaffold extends StatelessWidget { | |||||||
|   }); |   }); | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|  |     final focusNode = useFocusNode(); | ||||||
|  |  | ||||||
|  |     useEffect(() { | ||||||
|  |       focusNode.requestFocus(); | ||||||
|  |       return null; | ||||||
|  |     }, []); | ||||||
|  |  | ||||||
|     final appBarHeight = appBar?.preferredSize.height ?? 0; |     final appBarHeight = appBar?.preferredSize.height ?? 0; | ||||||
|     final safeTop = MediaQuery.of(context).padding.top; |     final safeTop = MediaQuery.of(context).padding.top; | ||||||
|  |  | ||||||
| @@ -201,29 +219,59 @@ class AppScaffold extends StatelessWidget { | |||||||
|       ], |       ], | ||||||
|     ); |     ); | ||||||
|  |  | ||||||
|     return Scaffold( |     return Shortcuts( | ||||||
|       extendBody: extendBody ?? true, |       shortcuts: <LogicalKeySet, Intent>{ | ||||||
|       extendBodyBehindAppBar: true, |         LogicalKeySet(LogicalKeyboardKey.escape): const PopIntent(), | ||||||
|       backgroundColor: |       }, | ||||||
|           noBackground |       child: Actions( | ||||||
|               ? Colors.transparent |         actions: <Type, Action<Intent>>{PopIntent: PopAction(context)}, | ||||||
|               : Theme.of(context).scaffoldBackgroundColor, |         child: Focus( | ||||||
|       body: |           focusNode: focusNode, | ||||||
|           noBackground ? content : AppBackground(isRoot: true, child: content), |           child: Scaffold( | ||||||
|       appBar: appBar, |             extendBody: extendBody ?? true, | ||||||
|       bottomNavigationBar: bottomNavigationBar, |             extendBodyBehindAppBar: true, | ||||||
|       bottomSheet: bottomSheet, |             backgroundColor: | ||||||
|       drawer: drawer, |                 noBackground | ||||||
|       endDrawer: endDrawer, |                     ? Colors.transparent | ||||||
|       floatingActionButton: floatingActionButton, |                     : Theme.of(context).scaffoldBackgroundColor, | ||||||
|       floatingActionButtonAnimator: floatingActionButtonAnimator, |             body: | ||||||
|       floatingActionButtonLocation: floatingActionButtonLocation, |                 noBackground | ||||||
|       onDrawerChanged: onDrawerChanged, |                     ? content | ||||||
|       onEndDrawerChanged: onEndDrawerChanged, |                     : AppBackground(isRoot: true, child: content), | ||||||
|  |             appBar: appBar, | ||||||
|  |             bottomNavigationBar: bottomNavigationBar, | ||||||
|  |             bottomSheet: bottomSheet, | ||||||
|  |             drawer: drawer, | ||||||
|  |             endDrawer: endDrawer, | ||||||
|  |             floatingActionButton: floatingActionButton, | ||||||
|  |             floatingActionButtonAnimator: floatingActionButtonAnimator, | ||||||
|  |             floatingActionButtonLocation: floatingActionButtonLocation, | ||||||
|  |             onDrawerChanged: onDrawerChanged, | ||||||
|  |             onEndDrawerChanged: onEndDrawerChanged, | ||||||
|  |           ), | ||||||
|  |         ), | ||||||
|  |       ), | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | class PopIntent extends Intent { | ||||||
|  |   const PopIntent(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class PopAction extends Action<PopIntent> { | ||||||
|  |   final BuildContext context; | ||||||
|  |  | ||||||
|  |   PopAction(this.context); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   void invoke(PopIntent intent) { | ||||||
|  |     if (context.canPop()) { | ||||||
|  |       context.pop(); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
| class PageBackButton extends StatelessWidget { | class PageBackButton extends StatelessWidget { | ||||||
|   final Color? color; |   final Color? color; | ||||||
|   final List<Shadow>? shadows; |   final List<Shadow>? shadows; | ||||||
| @@ -271,11 +319,12 @@ class AppBackground extends ConsumerWidget { | |||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context, WidgetRef ref) { |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|     final imageFileAsync = ref.watch(backgroundImageFileProvider); |     final imageFileAsync = ref.watch(backgroundImageFileProvider); | ||||||
|  |     final settings = ref.watch(appSettingsNotifierProvider); | ||||||
|  |  | ||||||
|     if (isRoot || !isWideScreen(context)) { |     if (isRoot || !isWideScreen(context)) { | ||||||
|       return imageFileAsync.when( |       return imageFileAsync.when( | ||||||
|         data: (file) { |         data: (file) { | ||||||
|           if (file != null) { |           if (file != null && settings.showBackgroundImage) { | ||||||
|             return Container( |             return Container( | ||||||
|               color: Theme.of(context).colorScheme.surface, |               color: Theme.of(context).colorScheme.surface, | ||||||
|               child: Container( |               child: Container( | ||||||
|   | |||||||
| @@ -1,15 +1,18 @@ | |||||||
| import 'dart:async'; | import 'dart:async'; | ||||||
|  | import 'package:bitsdojo_window/bitsdojo_window.dart'; | ||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
| import 'package:flutter_hooks/flutter_hooks.dart'; | import 'package:flutter_hooks/flutter_hooks.dart'; | ||||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
| import 'package:island/pods/websocket.dart'; | import 'package:island/pods/websocket.dart'; | ||||||
|  | import 'package:island/screens/tray_manager.dart'; | ||||||
| import 'package:island/services/notify.dart'; | import 'package:island/services/notify.dart'; | ||||||
| import 'package:island/services/sharing_intent.dart'; | import 'package:island/services/sharing_intent.dart'; | ||||||
| import 'package:island/services/update_service.dart'; | import 'package:island/services/update_service.dart'; | ||||||
| import 'package:island/widgets/content/network_status_sheet.dart'; | import 'package:island/widgets/content/network_status_sheet.dart'; | ||||||
| import 'package:island/widgets/tour/tour.dart'; | import 'package:island/widgets/tour/tour.dart'; | ||||||
|  | import 'package:tray_manager/tray_manager.dart'; | ||||||
|  |  | ||||||
| class AppWrapper extends HookConsumerWidget { | class AppWrapper extends HookConsumerWidget with TrayListener { | ||||||
|   final Widget child; |   final Widget child; | ||||||
|   const AppWrapper({super.key, required this.child}); |   const AppWrapper({super.key, required this.child}); | ||||||
|  |  | ||||||
| @@ -20,10 +23,16 @@ class AppWrapper extends HookConsumerWidget { | |||||||
|       Future(() { |       Future(() { | ||||||
|         if (context.mounted) ntySubs = setupNotificationListener(context, ref); |         if (context.mounted) ntySubs = setupNotificationListener(context, ref); | ||||||
|       }); |       }); | ||||||
|  |  | ||||||
|       final sharingService = SharingIntentService(); |       final sharingService = SharingIntentService(); | ||||||
|       sharingService.initialize(context); |       sharingService.initialize(context); | ||||||
|  |  | ||||||
|       UpdateService().checkForUpdates(context); |       UpdateService().checkForUpdates(context); | ||||||
|  |  | ||||||
|  |       TrayService.instance.initialize(this); | ||||||
|  |  | ||||||
|       return () { |       return () { | ||||||
|  |         TrayService.instance.dispose(this); | ||||||
|         sharingService.dispose(); |         sharingService.dispose(); | ||||||
|         ntySubs?.cancel(); |         ntySubs?.cancel(); | ||||||
|       }; |       }; | ||||||
| @@ -52,4 +61,31 @@ class AppWrapper extends HookConsumerWidget { | |||||||
|  |  | ||||||
|     return TourTriggerWidget(key: UniqueKey(), child: child); |     return TourTriggerWidget(key: UniqueKey(), child: child); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   void _trayIconPrimaryAction() { | ||||||
|  |     if (appWindow.isVisible) { | ||||||
|  |       appWindow.restore(); | ||||||
|  |     } else { | ||||||
|  |       appWindow.show(); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   void _trayIconSecondaryAction() { | ||||||
|  |     trayManager.popUpContextMenu(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   void onTrayIconMouseUp() { | ||||||
|  |     _trayIconPrimaryAction(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   void onTrayIconRightMouseDown() { | ||||||
|  |     _trayIconSecondaryAction(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   void onTrayMenuItemClick(MenuItem menuItem) { | ||||||
|  |     TrayService.instance.handleAction(menuItem); | ||||||
|  |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -3,6 +3,7 @@ import 'dart:convert'; | |||||||
| import 'package:dio/dio.dart'; | import 'package:dio/dio.dart'; | ||||||
| import 'package:easy_localization/easy_localization.dart'; | import 'package:easy_localization/easy_localization.dart'; | ||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:flutter_hooks/flutter_hooks.dart'; | ||||||
| import 'package:go_router/go_router.dart'; | import 'package:go_router/go_router.dart'; | ||||||
| import 'package:gap/gap.dart'; | import 'package:gap/gap.dart'; | ||||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
| @@ -14,6 +15,7 @@ import 'package:island/widgets/alert.dart'; | |||||||
| import 'package:island/widgets/content/cloud_files.dart'; | import 'package:island/widgets/content/cloud_files.dart'; | ||||||
| import 'package:material_symbols_icons/symbols.dart'; | import 'package:material_symbols_icons/symbols.dart'; | ||||||
| import 'package:riverpod_annotation/riverpod_annotation.dart'; | import 'package:riverpod_annotation/riverpod_annotation.dart'; | ||||||
|  | import 'package:slide_countdown/slide_countdown.dart'; | ||||||
| import 'package:styled_widget/styled_widget.dart'; | import 'package:styled_widget/styled_widget.dart'; | ||||||
|  |  | ||||||
| part 'check_in.g.dart'; | part 'check_in.g.dart'; | ||||||
| @@ -34,6 +36,17 @@ Future<SnCheckInResult?> checkInResultToday(Ref ref) async { | |||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @riverpod | ||||||
|  | Future<SnNotableDay?> nextNotableDay(Ref ref) async { | ||||||
|  |   final client = ref.watch(apiClientProvider); | ||||||
|  |   try { | ||||||
|  |     final resp = await client.get('/id/notable/me/next'); | ||||||
|  |     return SnNotableDay.fromJson(resp.data); | ||||||
|  |   } catch (err) { | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
| class CheckInWidget extends HookConsumerWidget { | class CheckInWidget extends HookConsumerWidget { | ||||||
|   final EdgeInsets? margin; |   final EdgeInsets? margin; | ||||||
|   final VoidCallback? onChecked; |   final VoidCallback? onChecked; | ||||||
| @@ -42,6 +55,22 @@ class CheckInWidget extends HookConsumerWidget { | |||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context, WidgetRef ref) { |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|     final todayResult = ref.watch(checkInResultTodayProvider); |     final todayResult = ref.watch(checkInResultTodayProvider); | ||||||
|  |     final nextNotableDay = ref.watch(nextNotableDayProvider); | ||||||
|  |  | ||||||
|  |     final userinfo = ref.watch(userInfoProvider); | ||||||
|  |     final isAdult = useMemoized(() { | ||||||
|  |       final birthday = userinfo.value?.profile.birthday; | ||||||
|  |       if (birthday == null) return false; | ||||||
|  |       final now = DateTime.now(); | ||||||
|  |       final age = | ||||||
|  |           now.year - | ||||||
|  |           birthday.year - | ||||||
|  |           ((now.month < birthday.month || | ||||||
|  |                   (now.month == birthday.month && now.day < birthday.day)) | ||||||
|  |               ? 1 | ||||||
|  |               : 0); | ||||||
|  |       return age >= 18; | ||||||
|  |     }, [userinfo]); | ||||||
|  |  | ||||||
|     Future<void> checkIn({String? captchatTk}) async { |     Future<void> checkIn({String? captchatTk}) async { | ||||||
|       final client = ref.read(apiClientProvider); |       final client = ref.read(apiClientProvider); | ||||||
| @@ -72,67 +101,127 @@ class CheckInWidget extends HookConsumerWidget { | |||||||
|       margin: |       margin: | ||||||
|           margin ?? EdgeInsets.only(left: 16, right: 16, top: 16, bottom: 8), |           margin ?? EdgeInsets.only(left: 16, right: 16, top: 16, bottom: 8), | ||||||
|       child: Row( |       child: Row( | ||||||
|         crossAxisAlignment: CrossAxisAlignment.center, |  | ||||||
|         spacing: 16, |  | ||||||
|         children: [ |         children: [ | ||||||
|           ClipRRect( |  | ||||||
|             borderRadius: BorderRadius.circular(8), |  | ||||||
|             child: Container( |  | ||||||
|               color: Theme.of(context).colorScheme.secondaryContainer, |  | ||||||
|               width: 56, |  | ||||||
|               height: 56, |  | ||||||
|               child: |  | ||||||
|                   Column( |  | ||||||
|                     mainAxisSize: MainAxisSize.min, |  | ||||||
|                     crossAxisAlignment: CrossAxisAlignment.center, |  | ||||||
|                     children: [ |  | ||||||
|                       Text(DateFormat('EEE').format(DateTime.now())) |  | ||||||
|                           .fontSize(16) |  | ||||||
|                           .bold() |  | ||||||
|                           .textColor( |  | ||||||
|                             Theme.of(context).colorScheme.onSecondaryContainer, |  | ||||||
|                           ), |  | ||||||
|                       Text(DateFormat('MM/dd').format(DateTime.now())) |  | ||||||
|                           .fontSize(12) |  | ||||||
|                           .textColor( |  | ||||||
|                             Theme.of(context).colorScheme.onSecondaryContainer, |  | ||||||
|                           ), |  | ||||||
|                     ], |  | ||||||
|                   ).center(), |  | ||||||
|             ), |  | ||||||
|           ), |  | ||||||
|           Expanded( |           Expanded( | ||||||
|             child: AnimatedSwitcher( |             child: Column( | ||||||
|               duration: const Duration(milliseconds: 300), |               crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|               child: todayResult.when( |               children: [ | ||||||
|                 data: (result) { |                 Row( | ||||||
|                   if (result == null) return _CheckInNoneWidget(); |                   spacing: 6, | ||||||
|                   return Column( |                   mainAxisSize: MainAxisSize.min, | ||||||
|                     crossAxisAlignment: CrossAxisAlignment.stretch, |                   crossAxisAlignment: CrossAxisAlignment.center, | ||||||
|                     children: [ |                   children: [ | ||||||
|                       Text( |                     Icon( | ||||||
|                         'checkInResultLevel${result.level}', |                       switch (DateTime.now().weekday) { | ||||||
|                       ).tr().fontSize(15).bold(), |                         6 || 7 => Symbols.weekend, | ||||||
|                       Text( |                         _ => isAdult ? Symbols.work : Symbols.school, | ||||||
|                         result.tips |                       }, | ||||||
|                             .map( |                       fill: 1, | ||||||
|                               (e) => '${e.isPositive ? '宜' : '忌'} ${e.title}', |                       size: 16, | ||||||
|                             ) |                     ).padding(right: 2), | ||||||
|                             .join('  ·  '), |                     Text(DateFormat('EEE').format(DateTime.now())) | ||||||
|                       ).fontSize(11), |                         .fontSize(16) | ||||||
|                     ], |                         .bold() | ||||||
|                   ); |                         .textColor( | ||||||
|                 }, |                           Theme.of(context).colorScheme.onSecondaryContainer, | ||||||
|                 loading: () => _CheckInNoneWidget(), |                         ), | ||||||
|                 error: |                     Text(DateFormat('MM/dd').format(DateTime.now())) | ||||||
|                     (err, stack) => Column( |                         .fontSize(16) | ||||||
|                       crossAxisAlignment: CrossAxisAlignment.start, |                         .textColor( | ||||||
|                       children: [ |                           Theme.of(context).colorScheme.onSecondaryContainer, | ||||||
|                         Text('error').tr().fontSize(15).bold(), |                         ), | ||||||
|                         Text(err.toString()).fontSize(11), |                     Expanded( | ||||||
|                       ], |                       child: AnimatedSwitcher( | ||||||
|  |                         duration: const Duration(milliseconds: 300), | ||||||
|  |                         child: todayResult.when( | ||||||
|  |                           data: (result) { | ||||||
|  |                             return Text( | ||||||
|  |                               result == null | ||||||
|  |                                   ? 'checkInNone' | ||||||
|  |                                   : 'checkInResultLevel${result.level}', | ||||||
|  |                               textAlign: TextAlign.start, | ||||||
|  |                             ).tr().fontSize(15).bold(); | ||||||
|  |                           }, | ||||||
|  |                           loading: | ||||||
|  |                               () => | ||||||
|  |                                   Text('checkInNone').tr().fontSize(15).bold(), | ||||||
|  |                           error: | ||||||
|  |                               (err, stack) => | ||||||
|  |                                   Text('error').tr().fontSize(15).bold(), | ||||||
|  |                         ), | ||||||
|  |                       ).alignment(Alignment.centerLeft), | ||||||
|                     ), |                     ), | ||||||
|               ), |                   ], | ||||||
|  |                 ), | ||||||
|  |                 Row( | ||||||
|  |                   spacing: 5, | ||||||
|  |                   children: [ | ||||||
|  |                     Text('notableDayNext') | ||||||
|  |                         .tr(args: [nextNotableDay.value?.localName ?? 'idk']) | ||||||
|  |                         .fontSize(12), | ||||||
|  |                     if (nextNotableDay.value != null) | ||||||
|  |                       SlideCountdown( | ||||||
|  |                         decoration: const BoxDecoration(), | ||||||
|  |                         style: const TextStyle(fontSize: 12), | ||||||
|  |                         separatorStyle: const TextStyle(fontSize: 12), | ||||||
|  |                         padding: EdgeInsets.zero, | ||||||
|  |                         duration: nextNotableDay.value?.date.difference( | ||||||
|  |                           DateTime.now(), | ||||||
|  |                         ), | ||||||
|  |                       ), | ||||||
|  |                   ], | ||||||
|  |                 ), | ||||||
|  |                 const Gap(6), | ||||||
|  |                 AnimatedSwitcher( | ||||||
|  |                   duration: const Duration(milliseconds: 300), | ||||||
|  |                   child: todayResult.when( | ||||||
|  |                     data: (result) { | ||||||
|  |                       if (result == null) { | ||||||
|  |                         return Text('checkInNoneHint').tr().fontSize(11); | ||||||
|  |                       } | ||||||
|  |                       return Wrap( | ||||||
|  |                         alignment: WrapAlignment.start, | ||||||
|  |                         runAlignment: WrapAlignment.start, | ||||||
|  |                         children: | ||||||
|  |                             result.tips | ||||||
|  |                                 .map((e) { | ||||||
|  |                                   return Row( | ||||||
|  |                                     mainAxisSize: MainAxisSize.min, | ||||||
|  |                                     children: [ | ||||||
|  |                                       Icon( | ||||||
|  |                                         e.isPositive | ||||||
|  |                                             ? Symbols.thumb_up | ||||||
|  |                                             : Symbols.thumb_down, | ||||||
|  |                                         size: 12, | ||||||
|  |                                       ), | ||||||
|  |                                       const Gap(4), | ||||||
|  |                                       Text(e.title).fontSize(11), | ||||||
|  |                                     ], | ||||||
|  |                                   ); | ||||||
|  |                                 }) | ||||||
|  |                                 .toList() | ||||||
|  |                                 .expand( | ||||||
|  |                                   (widget) => [ | ||||||
|  |                                     widget, | ||||||
|  |                                     Text('  ·  ').fontSize(11), | ||||||
|  |                                   ], | ||||||
|  |                                 ) | ||||||
|  |                                 .toList() | ||||||
|  |                               ..removeLast(), | ||||||
|  |                       ); | ||||||
|  |                     }, | ||||||
|  |                     loading: () => Text('checkInNoneHint').tr().fontSize(11), | ||||||
|  |                     error: | ||||||
|  |                         (err, stack) => Column( | ||||||
|  |                           crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|  |                           children: [ | ||||||
|  |                             Text('error').tr().fontSize(15).bold(), | ||||||
|  |                             Text(err.toString()).fontSize(11), | ||||||
|  |                           ], | ||||||
|  |                         ), | ||||||
|  |                   ), | ||||||
|  |                 ).alignment(Alignment.centerLeft), | ||||||
|  |               ], | ||||||
|             ), |             ), | ||||||
|           ), |           ), | ||||||
|           IconButton.outlined( |           IconButton.outlined( | ||||||
| @@ -167,21 +256,6 @@ class CheckInWidget extends HookConsumerWidget { | |||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| class _CheckInNoneWidget extends StatelessWidget { |  | ||||||
|   const _CheckInNoneWidget(); |  | ||||||
|  |  | ||||||
|   @override |  | ||||||
|   Widget build(BuildContext context) { |  | ||||||
|     return Column( |  | ||||||
|       crossAxisAlignment: CrossAxisAlignment.stretch, |  | ||||||
|       children: [ |  | ||||||
|         Text('checkInNone').tr().fontSize(15).bold(), |  | ||||||
|         Text('checkInNoneHint').tr().fontSize(11), |  | ||||||
|       ], |  | ||||||
|     ); |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| class CheckInActivityWidget extends StatelessWidget { | class CheckInActivityWidget extends StatelessWidget { | ||||||
|   final SnActivity item; |   final SnActivity item; | ||||||
|   const CheckInActivityWidget({super.key, required this.item}); |   const CheckInActivityWidget({super.key, required this.item}); | ||||||
|   | |||||||
| @@ -26,5 +26,24 @@ final checkInResultTodayProvider = | |||||||
| @Deprecated('Will be removed in 3.0. Use Ref instead') | @Deprecated('Will be removed in 3.0. Use Ref instead') | ||||||
| // ignore: unused_element | // ignore: unused_element | ||||||
| typedef CheckInResultTodayRef = AutoDisposeFutureProviderRef<SnCheckInResult?>; | typedef CheckInResultTodayRef = AutoDisposeFutureProviderRef<SnCheckInResult?>; | ||||||
|  | String _$nextNotableDayHash() => r'698370bec4be28774d332412c5a701f914064c90'; | ||||||
|  |  | ||||||
|  | /// See also [nextNotableDay]. | ||||||
|  | @ProviderFor(nextNotableDay) | ||||||
|  | final nextNotableDayProvider = | ||||||
|  |     AutoDisposeFutureProvider<SnNotableDay?>.internal( | ||||||
|  |       nextNotableDay, | ||||||
|  |       name: r'nextNotableDayProvider', | ||||||
|  |       debugGetCreateSourceHash: | ||||||
|  |           const bool.fromEnvironment('dart.vm.product') | ||||||
|  |               ? null | ||||||
|  |               : _$nextNotableDayHash, | ||||||
|  |       dependencies: null, | ||||||
|  |       allTransitiveDependencies: null, | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  | @Deprecated('Will be removed in 3.0. Use Ref instead') | ||||||
|  | // ignore: unused_element | ||||||
|  | typedef NextNotableDayRef = AutoDisposeFutureProviderRef<SnNotableDay?>; | ||||||
| // ignore_for_file: type=lint | // ignore_for_file: type=lint | ||||||
| // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package | // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package | ||||||
|   | |||||||
| @@ -6,17 +6,21 @@ import 'package:flutter_platform_alert/flutter_platform_alert.dart'; | |||||||
|  |  | ||||||
| String _parseRemoteError(DioException err) { | String _parseRemoteError(DioException err) { | ||||||
|   log('${err.requestOptions.method} ${err.requestOptions.uri} ${err.message}'); |   log('${err.requestOptions.method} ${err.requestOptions.uri} ${err.message}'); | ||||||
|   if (err.response?.data is String) return err.response?.data; |   String? message; | ||||||
|   if (err.response?.data?['errors'] != null) { |   if (err.response?.data is String) { | ||||||
|  |     message = err.response?.data; | ||||||
|  |   } else if (err.response?.data?['errors'] != null) { | ||||||
|     final errors = err.response?.data['errors'] as Map<String, dynamic>; |     final errors = err.response?.data['errors'] as Map<String, dynamic>; | ||||||
|     return errors.values |     message = errors.values | ||||||
|         .map( |         .map( | ||||||
|           (ele) => |           (ele) => | ||||||
|               (ele as List<dynamic>).map((ele) => ele.toString()).join('\n'), |               (ele as List<dynamic>).map((ele) => ele.toString()).join('\n'), | ||||||
|         ) |         ) | ||||||
|         .join('\n'); |         .join('\n'); | ||||||
|   } |   } | ||||||
|   return err.message ?? err.toString(); |   if (message == null || message.isEmpty) message = err.response?.statusMessage; | ||||||
|  |   message ??= err.message; | ||||||
|  |   return message ?? err.toString(); | ||||||
| } | } | ||||||
|  |  | ||||||
| void showErrorAlert(dynamic err) async { | void showErrorAlert(dynamic err) async { | ||||||
|   | |||||||
| @@ -10,6 +10,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; | |||||||
| import 'package:island/models/file.dart'; | import 'package:island/models/file.dart'; | ||||||
| import 'package:island/pods/network.dart'; | import 'package:island/pods/network.dart'; | ||||||
| import 'package:island/services/file.dart'; | import 'package:island/services/file.dart'; | ||||||
|  | import 'package:island/utils/format.dart'; | ||||||
| import 'package:island/widgets/alert.dart'; | import 'package:island/widgets/alert.dart'; | ||||||
| import 'package:island/widgets/content/cloud_files.dart'; | import 'package:island/widgets/content/cloud_files.dart'; | ||||||
| import 'package:island/widgets/content/sheet.dart'; | import 'package:island/widgets/content/sheet.dart'; | ||||||
| @@ -284,6 +285,13 @@ class AttachmentPreview extends HookConsumerWidget { | |||||||
|                   Builder( |                   Builder( | ||||||
|                     key: ValueKey(item.hashCode), |                     key: ValueKey(item.hashCode), | ||||||
|                     builder: (context) { |                     builder: (context) { | ||||||
|  |                       final fallbackIcon = switch (item.type) { | ||||||
|  |                         UniversalFileType.video => Symbols.video_file, | ||||||
|  |                         UniversalFileType.audio => Symbols.audio_file, | ||||||
|  |                         UniversalFileType.image => Symbols.image, | ||||||
|  |                         _ => Symbols.insert_drive_file, | ||||||
|  |                       }; | ||||||
|  |  | ||||||
|                       if (item.isOnCloud) { |                       if (item.isOnCloud) { | ||||||
|                         return CloudFileWidget(item: item.data); |                         return CloudFileWidget(item: item.data); | ||||||
|                       } else if (item.data is XFile) { |                       } else if (item.data is XFile) { | ||||||
| @@ -309,9 +317,23 @@ class AttachmentPreview extends HookConsumerWidget { | |||||||
|                                 : Image.file(File(file.path)); |                                 : Image.file(File(file.path)); | ||||||
|                           default: |                           default: | ||||||
|                             return Column( |                             return Column( | ||||||
|  |                               mainAxisAlignment: MainAxisAlignment.center, | ||||||
|                               children: [ |                               children: [ | ||||||
|                                 const Icon(Symbols.document_scanner), |                                 Icon(fallbackIcon), | ||||||
|                                 Text(file.name), |                                 const Gap(6), | ||||||
|  |                                 Text(file.name, textAlign: TextAlign.center), | ||||||
|  |                                 FutureBuilder( | ||||||
|  |                                   future: file.length(), | ||||||
|  |                                   builder: (context, snapshot) { | ||||||
|  |                                     if (snapshot.hasData) { | ||||||
|  |                                       final size = snapshot.data as int; | ||||||
|  |                                       return Text( | ||||||
|  |                                         formatFileSize(size), | ||||||
|  |                                       ).fontSize(11); | ||||||
|  |                                     } | ||||||
|  |                                     return const SizedBox.shrink(); | ||||||
|  |                                   }, | ||||||
|  |                                 ), | ||||||
|                               ], |                               ], | ||||||
|                             ); |                             ); | ||||||
|                         } |                         } | ||||||
| @@ -321,7 +343,14 @@ class AttachmentPreview extends HookConsumerWidget { | |||||||
|                             return Image.memory(item.data); |                             return Image.memory(item.data); | ||||||
|                           default: |                           default: | ||||||
|                             return Column( |                             return Column( | ||||||
|                               children: [const Icon(Symbols.document_scanner)], |                               mainAxisAlignment: MainAxisAlignment.center, | ||||||
|  |                               children: [ | ||||||
|  |                                 Icon(fallbackIcon), | ||||||
|  |                                 const Gap(6), | ||||||
|  |                                 Text( | ||||||
|  |                                   formatFileSize(item.data.length), | ||||||
|  |                                 ).fontSize(11), | ||||||
|  |                               ], | ||||||
|                             ); |                             ); | ||||||
|                         } |                         } | ||||||
|                       } |                       } | ||||||
|   | |||||||
| @@ -5,11 +5,11 @@ import 'dart:ui'; | |||||||
|  |  | ||||||
| import 'package:dismissible_page/dismissible_page.dart'; | import 'package:dismissible_page/dismissible_page.dart'; | ||||||
| import 'package:easy_localization/easy_localization.dart'; | import 'package:easy_localization/easy_localization.dart'; | ||||||
|  | import 'package:flutter_blurhash/flutter_blurhash.dart'; | ||||||
| import 'package:file_saver/file_saver.dart'; | import 'package:file_saver/file_saver.dart'; | ||||||
| import 'package:flutter/foundation.dart'; | import 'package:flutter/foundation.dart'; | ||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
| import 'package:flutter/services.dart'; | import 'package:flutter/services.dart'; | ||||||
| import 'package:flutter_blurhash/flutter_blurhash.dart'; |  | ||||||
| import 'package:flutter_hooks/flutter_hooks.dart'; | import 'package:flutter_hooks/flutter_hooks.dart'; | ||||||
| import 'package:gap/gap.dart'; | import 'package:gap/gap.dart'; | ||||||
| import 'package:gal/gal.dart'; | import 'package:gal/gal.dart'; | ||||||
| @@ -17,6 +17,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; | |||||||
| import 'package:island/models/file.dart'; | import 'package:island/models/file.dart'; | ||||||
| import 'package:island/pods/config.dart'; | import 'package:island/pods/config.dart'; | ||||||
| import 'package:island/pods/network.dart'; | import 'package:island/pods/network.dart'; | ||||||
|  | import 'package:island/utils/format.dart'; | ||||||
| import 'package:island/widgets/alert.dart'; | import 'package:island/widgets/alert.dart'; | ||||||
| import 'package:island/widgets/content/cloud_files.dart'; | import 'package:island/widgets/content/cloud_files.dart'; | ||||||
| import 'package:island/widgets/content/sensitive.dart'; | import 'package:island/widgets/content/sensitive.dart'; | ||||||
| @@ -359,16 +360,6 @@ class CloudFileZoomIn extends HookConsumerWidget { | |||||||
|       } |       } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     String formatFileSize(int bytes) { |  | ||||||
|       if (bytes <= 0) return '0 B'; |  | ||||||
|       if (bytes < 1024) return '$bytes B'; |  | ||||||
|       if (bytes < 1024 * 1024) return '${(bytes / 1024).toStringAsFixed(2)} KB'; |  | ||||||
|       if (bytes < 1024 * 1024 * 1024) { |  | ||||||
|         return '${(bytes / (1024 * 1024)).toStringAsFixed(2)} MB'; |  | ||||||
|       } |  | ||||||
|       return '${(bytes / (1024 * 1024 * 1024)).toStringAsFixed(2)} GB'; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void showInfoSheet() { |     void showInfoSheet() { | ||||||
|       final theme = Theme.of(context); |       final theme = Theme.of(context); | ||||||
|       final exifData = item.fileMeta?['exif'] as Map<String, dynamic>? ?? {}; |       final exifData = item.fileMeta?['exif'] as Map<String, dynamic>? ?? {}; | ||||||
| @@ -813,164 +804,213 @@ class _CloudFileListEntry extends HookConsumerWidget { | |||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context, WidgetRef ref) { |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|  |     final dataSaving = ref.watch( | ||||||
|  |       appSettingsNotifierProvider.select((s) => s.dataSavingMode), | ||||||
|  |     ); | ||||||
|     final showMature = useState(false); |     final showMature = useState(false); | ||||||
|  |     final showDataSaving = useState(!dataSaving); | ||||||
|  |     final lockedByDS = dataSaving && !showDataSaving.value; | ||||||
|  |     final lockedByMature = file.sensitiveMarks.isNotEmpty && !showMature.value; | ||||||
|  |     final meta = file.fileMeta is Map ? file.fileMeta as Map : const {}; | ||||||
|  |     final hasRatio = | ||||||
|  |         meta.containsKey('ratio') && | ||||||
|  |         (meta['ratio'] is num && (meta['ratio'] as num) != 0); | ||||||
|  |     final ratio = | ||||||
|  |         (meta['ratio'] is num && (meta['ratio'] as num) != 0) | ||||||
|  |             ? (meta['ratio'] as num).toDouble() | ||||||
|  |             : 1.0; | ||||||
|  |  | ||||||
|     var content = Stack( |     final fit = hasRatio ? BoxFit.cover : BoxFit.contain; | ||||||
|       fit: StackFit.expand, |  | ||||||
|       children: [ |     Widget bg = const SizedBox.shrink(); | ||||||
|         if (isImage) |     if (isImage) { | ||||||
|           Positioned.fill( |       if (meta['blur'] is String) { | ||||||
|             child: |         bg = BlurHash(hash: meta['blur'] as String); | ||||||
|                 file.fileMeta?['blur'] is String |       } else if (!lockedByDS && !lockedByMature) { | ||||||
|                     ? BlurHash(hash: file.fileMeta?['blur']) |         bg = ImageFiltered( | ||||||
|                     : ImageFiltered( |           imageFilter: ImageFilter.blur(sigmaX: 10, sigmaY: 10), | ||||||
|                       imageFilter: ImageFilter.blur(sigmaX: 10, sigmaY: 10), |           child: CloudFileWidget( | ||||||
|                       child: CloudFileWidget(item: file, noBlurhash: true), |             fit: fit, | ||||||
|                     ), |  | ||||||
|           ), |  | ||||||
|         if (isImage) |  | ||||||
|           CloudFileWidget( |  | ||||||
|             item: file, |             item: file, | ||||||
|             heroTag: heroTag, |  | ||||||
|             noBlurhash: true, |             noBlurhash: true, | ||||||
|             fit: BoxFit.contain, |             useInternalGate: false, | ||||||
|           ) |           ), | ||||||
|         else |         ); | ||||||
|           CloudFileWidget(item: file, heroTag: heroTag, fit: BoxFit.contain), |       } else { | ||||||
|       ], |         bg = const ColoredBox(color: Colors.black26); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     final bool fullyUnlocked = !lockedByDS && !lockedByMature; | ||||||
|  |     Widget fg = | ||||||
|  |         fullyUnlocked | ||||||
|  |             ? (isImage | ||||||
|  |                 ? CloudFileWidget( | ||||||
|  |                   item: file, | ||||||
|  |                   heroTag: heroTag, | ||||||
|  |                   noBlurhash: true, | ||||||
|  |                   fit: fit, | ||||||
|  |                   useInternalGate: false, | ||||||
|  |                 ) | ||||||
|  |                 : CloudFileWidget( | ||||||
|  |                   item: file, | ||||||
|  |                   heroTag: heroTag, | ||||||
|  |                   fit: fit, | ||||||
|  |                   useInternalGate: false, | ||||||
|  |                 )) | ||||||
|  |             : AspectRatio(aspectRatio: ratio, child: const SizedBox.shrink()); | ||||||
|  |  | ||||||
|  |     Widget overlays; | ||||||
|  |     if (lockedByDS) { | ||||||
|  |       overlays = _DataSavingOverlay(); | ||||||
|  |     } else if (file.sensitiveMarks.isNotEmpty) { | ||||||
|  |       overlays = _SensitiveOverlay( | ||||||
|  |         file: file, | ||||||
|  |         isRevealed: showMature.value, | ||||||
|  |         onHide: () => showMature.value = false, | ||||||
|  |       ); | ||||||
|  |     } else { | ||||||
|  |       overlays = const SizedBox.shrink(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     final content = Stack( | ||||||
|  |       fit: StackFit.expand, | ||||||
|  |       children: [if (isImage) Positioned.fill(child: bg), fg, overlays], | ||||||
|     ); |     ); | ||||||
|  |  | ||||||
|     if (file.sensitiveMarks.isNotEmpty) { |     return InkWell( | ||||||
|       // Show a blurred overlay only when not revealed yet, with a smooth transition |       borderRadius: const BorderRadius.all(Radius.circular(16)), | ||||||
|       content = Stack( |       onTap: () { | ||||||
|         children: [ |         if (lockedByDS) { | ||||||
|           content, |           showDataSaving.value = true; | ||||||
|           // Toggle blur overlay with animation |         } else if (lockedByMature) { | ||||||
|           Positioned.fill( |           showMature.value = true; | ||||||
|             child: AnimatedSwitcher( |         } else { | ||||||
|               duration: const Duration(milliseconds: 250), |           onTap?.call(); | ||||||
|               switchInCurve: Curves.easeOut, |         } | ||||||
|               switchOutCurve: Curves.easeIn, |       }, | ||||||
|               layoutBuilder: |       child: content, | ||||||
|                   (currentChild, previousChildren) => Stack( |     ); | ||||||
|                     fit: StackFit.expand, |   } | ||||||
|                     children: [ | } | ||||||
|                       ...previousChildren, |  | ||||||
|                       if (currentChild != null) currentChild, | class _SensitiveOverlay extends StatelessWidget { | ||||||
|                     ], |   final SnCloudFile file; | ||||||
|                   ), |   final VoidCallback? onHide; | ||||||
|               child: |   final bool isRevealed; | ||||||
|                   showMature.value |  | ||||||
|                       ? const SizedBox.shrink(key: ValueKey('revealed')) |   const _SensitiveOverlay({ | ||||||
|                       : ColoredBox( |     required this.file, | ||||||
|                         key: const ValueKey('blurred'), |     this.onHide, | ||||||
|                         color: Colors.transparent, |     this.isRevealed = false, | ||||||
|                         child: BackdropFilter( |   }); | ||||||
|                           filter: ImageFilter.blur(sigmaX: 64, sigmaY: 64), |  | ||||||
|                           child: Stack( |   @override | ||||||
|                             fit: StackFit.expand, |   Widget build(BuildContext context) { | ||||||
|                             children: [ |     if (isRevealed) { | ||||||
|                               const ColoredBox(color: Colors.transparent), |       return Positioned( | ||||||
|                               Center( |         top: 3, | ||||||
|                                 child: Container( |         left: 4, | ||||||
|                                   margin: const EdgeInsets.all(12), |         child: IconButton( | ||||||
|                                   padding: const EdgeInsets.symmetric( |           iconSize: 16, | ||||||
|                                     horizontal: 12, |           constraints: const BoxConstraints(), | ||||||
|                                     vertical: 8, |           icon: const Icon( | ||||||
|                                   ), |             Icons.visibility_off, | ||||||
|                                   decoration: BoxDecoration( |             color: Colors.white, | ||||||
|                                     color: Colors.black54, |             shadows: [ | ||||||
|                                     borderRadius: BorderRadius.circular(12), |               Shadow( | ||||||
|                                   ), |                 color: Colors.black, | ||||||
|                                   child: ConstrainedBox( |                 blurRadius: 5.0, | ||||||
|                                     constraints: const BoxConstraints( |                 offset: Offset(1.0, 1.0), | ||||||
|                                       maxWidth: 280, |               ), | ||||||
|                                     ), |             ], | ||||||
|                                     child: Column( |           ), | ||||||
|                                       mainAxisSize: MainAxisSize.min, |           tooltip: 'Blur content', | ||||||
|                                       children: [ |           onPressed: onHide, | ||||||
|                                         const Icon( |         ), | ||||||
|                                           Icons.warning, |       ); | ||||||
|                                           color: Colors.white, |     } | ||||||
|                                           fill: 1, |  | ||||||
|                                           size: 24, |     return BackdropFilter( | ||||||
|                                         ), |       filter: ImageFilter.blur(sigmaX: 64, sigmaY: 64), | ||||||
|                                         const Gap(4), |       child: Container( | ||||||
|                                         Text( |         color: Colors.transparent, | ||||||
|                                           file.sensitiveMarks |         child: Center( | ||||||
|                                               .map( |           child: _OverlayCard( | ||||||
|                                                 (e) => |             icon: Icons.warning, | ||||||
|                                                     SensitiveCategory |             title: file.sensitiveMarks | ||||||
|                                                         .values[e] |                 .map((e) => SensitiveCategory.values[e].i18nKey.tr()) | ||||||
|                                                         .i18nKey |                 .join(' · '), | ||||||
|                                                         .tr(), |             subtitle: 'Sensitive Content', | ||||||
|                                               ) |             hint: 'Tap to Reveal', | ||||||
|                                               .join(' · '), |           ), | ||||||
|                                           style: const TextStyle( |         ), | ||||||
|                                             color: Colors.white, |       ), | ||||||
|                                             fontWeight: FontWeight.w600, |     ); | ||||||
|                                           ), |   } | ||||||
|                                           textAlign: TextAlign.center, | } | ||||||
|                                         ), |  | ||||||
|                                         Text( | class _DataSavingOverlay extends StatelessWidget { | ||||||
|                                           'Sensitive Content', |   @override | ||||||
|                                           style: TextStyle( |   Widget build(BuildContext context) { | ||||||
|                                             color: Colors.white, |     return ColoredBox( | ||||||
|                                             fontSize: 13, |       color: Colors.black38, | ||||||
|                                           ), |       child: Center( | ||||||
|                                         ), |         child: _OverlayCard( | ||||||
|                                         const Gap(4), |           icon: Symbols.image, | ||||||
|                                         Text( |           title: 'Data Saving Mode', | ||||||
|                                           'Tap to Reveal', |           subtitle: '', | ||||||
|                                           style: TextStyle( |           hint: 'Tap to Load', | ||||||
|                                             color: Colors.white, |         ), | ||||||
|                                             fontSize: 11, |       ), | ||||||
|                                           ), |     ); | ||||||
|                                         ), |   } | ||||||
|                                       ], | } | ||||||
|                                     ), |  | ||||||
|                                   ).padding(horizontal: 24, vertical: 16), | class _OverlayCard extends StatelessWidget { | ||||||
|                                 ), |   final IconData icon; | ||||||
|                               ), |   final String title; | ||||||
|                             ], |   final String subtitle; | ||||||
|                           ), |   final String hint; | ||||||
|                         ), |  | ||||||
|                       ), |   const _OverlayCard({ | ||||||
|             ), |     required this.icon, | ||||||
|           ), |     required this.title, | ||||||
|           // When revealed (no blur), show a small control at top-left to re-enable blur |     required this.subtitle, | ||||||
|           if (showMature.value) |     required this.hint, | ||||||
|             Positioned( |   }); | ||||||
|               top: 3, |  | ||||||
|               left: 4, |   @override | ||||||
|               child: IconButton( |   Widget build(BuildContext context) { | ||||||
|                 iconSize: 16, |     return Container( | ||||||
|                 constraints: const BoxConstraints(), |       margin: const EdgeInsets.all(12), | ||||||
|                 icon: const Icon(Icons.visibility_off, color: Colors.white), |       padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), | ||||||
|                 tooltip: 'Blur content', |       decoration: BoxDecoration( | ||||||
|                 onPressed: () { |         color: Colors.black54, | ||||||
|                   showMature.value = false; |         borderRadius: BorderRadius.circular(12), | ||||||
|                 }, |       ), | ||||||
|               ), |       constraints: const BoxConstraints(maxWidth: 280), | ||||||
|             ), |       child: Column( | ||||||
|         ], |         mainAxisSize: MainAxisSize.min, | ||||||
|       ); |         children: [ | ||||||
|     } |           Icon(icon, color: Colors.white, size: 24), | ||||||
|  |           const Gap(4), | ||||||
|     if (onTap != null) { |           Text( | ||||||
|       return InkWell( |             title, | ||||||
|         borderRadius: const BorderRadius.all(Radius.circular(16)), |             style: const TextStyle( | ||||||
|         onTap: () { |               color: Colors.white, | ||||||
|           if (!showMature.value) { |               fontWeight: FontWeight.w600, | ||||||
|             showMature.value = true; |             ), | ||||||
|           } else { |             textAlign: TextAlign.center, | ||||||
|             onTap?.call(); |           ), | ||||||
|           } |           Text( | ||||||
|         }, |             subtitle, | ||||||
|         child: content, |             style: const TextStyle(color: Colors.white, fontSize: 13), | ||||||
|       ); |           ), | ||||||
|     } |           const Gap(4), | ||||||
|  |           Text(hint, style: const TextStyle(color: Colors.white, fontSize: 11)), | ||||||
|     return content; |         ], | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,15 +1,20 @@ | |||||||
| import 'dart:math' as math; | import 'dart:math' as math; | ||||||
|  |  | ||||||
| import 'package:cached_network_image/cached_network_image.dart'; | import 'package:cached_network_image/cached_network_image.dart'; | ||||||
|  | import 'package:easy_localization/easy_localization.dart'; | ||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
| import 'package:flutter_hooks/flutter_hooks.dart'; | import 'package:flutter_hooks/flutter_hooks.dart'; | ||||||
|  | import 'package:gap/gap.dart'; | ||||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
| import 'package:island/models/file.dart'; | import 'package:island/models/file.dart'; | ||||||
| import 'package:island/pods/config.dart'; | import 'package:island/pods/config.dart'; | ||||||
| import 'package:island/services/time.dart'; | import 'package:island/services/time.dart'; | ||||||
|  | import 'package:island/utils/format.dart'; | ||||||
| import 'package:island/widgets/content/audio.dart'; | import 'package:island/widgets/content/audio.dart'; | ||||||
| import 'package:material_symbols_icons/symbols.dart'; | import 'package:material_symbols_icons/symbols.dart'; | ||||||
| import 'package:styled_widget/styled_widget.dart'; | import 'package:styled_widget/styled_widget.dart'; | ||||||
|  | import 'package:url_launcher/url_launcher_string.dart'; | ||||||
|  | import 'package:island/widgets/data_saving_gate.dart'; | ||||||
|  |  | ||||||
| import 'image.dart'; | import 'image.dart'; | ||||||
| import 'video.dart'; | import 'video.dart'; | ||||||
| @@ -19,48 +24,97 @@ class CloudFileWidget extends HookConsumerWidget { | |||||||
|   final BoxFit fit; |   final BoxFit fit; | ||||||
|   final String? heroTag; |   final String? heroTag; | ||||||
|   final bool noBlurhash; |   final bool noBlurhash; | ||||||
|  |   final bool useInternalGate; | ||||||
|   const CloudFileWidget({ |   const CloudFileWidget({ | ||||||
|     super.key, |     super.key, | ||||||
|     required this.item, |     required this.item, | ||||||
|     this.fit = BoxFit.cover, |     this.fit = BoxFit.cover, | ||||||
|     this.heroTag, |     this.heroTag, | ||||||
|     this.noBlurhash = false, |     this.noBlurhash = false, | ||||||
|  |     this.useInternalGate = true, | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context, WidgetRef ref) { |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|  |     final dataSaving = ref.watch( | ||||||
|  |       appSettingsNotifierProvider.select((s) => s.dataSavingMode), | ||||||
|  |     ); | ||||||
|     final serverUrl = ref.watch(serverUrlProvider); |     final serverUrl = ref.watch(serverUrlProvider); | ||||||
|     final uri = '$serverUrl/drive/files/${item.id}'; |     final uri = '$serverUrl/drive/files/${item.id}'; | ||||||
|  |  | ||||||
|     var ratio = |     final unlocked = useState(false); | ||||||
|         item.fileMeta?['ratio'] is num |  | ||||||
|             ? item.fileMeta!['ratio'].toDouble() |     final meta = item.fileMeta is Map ? (item.fileMeta as Map) : const {}; | ||||||
|             : 1.0; |     final blurHash = noBlurhash ? null : (meta['blur'] as String?); | ||||||
|  |     var ratio = meta['ratio'] is num ? (meta['ratio'] as num).toDouble() : 1.0; | ||||||
|     if (ratio == 0) ratio = 1.0; |     if (ratio == 0) ratio = 1.0; | ||||||
|  |  | ||||||
|  |     Widget cloudImage() => UniversalImage(uri: uri, blurHash: blurHash, fit: fit); | ||||||
|  |     Widget cloudVideo() => CloudVideoWidget(item: item); | ||||||
|  |  | ||||||
|  |     Widget dataPlaceHolder(IconData icon) => _DataSavingPlaceholder( | ||||||
|  |           icon: icon, | ||||||
|  |           onTap: () { | ||||||
|  |             unlocked.value = true; | ||||||
|  |           }, | ||||||
|  |         ); | ||||||
|  |  | ||||||
|     var content = switch (item.mimeType?.split('/').firstOrNull) { |     var content = switch (item.mimeType?.split('/').firstOrNull) { | ||||||
|       "image" => AspectRatio( |       'image' => AspectRatio( | ||||||
|         aspectRatio: ratio, |           aspectRatio: ratio, | ||||||
|         child: UniversalImage( |           child: (useInternalGate && dataSaving && !unlocked.value) ? dataPlaceHolder(Symbols.image) : cloudImage(), | ||||||
|           uri: uri, |  | ||||||
|           blurHash: |  | ||||||
|               noBlurhash |  | ||||||
|                   ? null |  | ||||||
|                   : (item.fileMeta is String ? item.fileMeta!['blur'] : null), |  | ||||||
|         ), |         ), | ||||||
|       ), |       'video' => AspectRatio( | ||||||
|       "video" => AspectRatio( |           aspectRatio: ratio, | ||||||
|         aspectRatio: ratio, |           child: (useInternalGate && dataSaving && !unlocked.value) ? dataPlaceHolder(Symbols.play_arrow) : cloudVideo(), | ||||||
|         child: CloudVideoWidget(item: item), |         ), | ||||||
|       ), |       'audio' => Center( | ||||||
|       "audio" => Center( |           child: ConstrainedBox( | ||||||
|         child: ConstrainedBox( |             constraints: BoxConstraints( | ||||||
|           constraints: BoxConstraints( |               maxWidth: math.min(360, MediaQuery.of(context).size.width * 0.8), | ||||||
|             maxWidth: math.min(360, MediaQuery.of(context).size.width * 0.8), |             ), | ||||||
|  |             child: UniversalAudio(uri: uri, filename: item.name), | ||||||
|           ), |           ), | ||||||
|           child: UniversalAudio(uri: uri, filename: item.name), |  | ||||||
|         ), |         ), | ||||||
|       ), |       _ => Column( | ||||||
|       _ => Text('Unable render for ${item.mimeType}'), |           mainAxisSize: MainAxisSize.min, | ||||||
|  |           mainAxisAlignment: MainAxisAlignment.center, | ||||||
|  |           children: [ | ||||||
|  |             Icon( | ||||||
|  |               Symbols.insert_drive_file, | ||||||
|  |               size: 48, | ||||||
|  |               color: Theme.of(context).colorScheme.onSurfaceVariant, | ||||||
|  |             ), | ||||||
|  |             const Gap(8), | ||||||
|  |             Text( | ||||||
|  |               item.name, | ||||||
|  |               maxLines: 1, | ||||||
|  |               overflow: TextOverflow.ellipsis, | ||||||
|  |               style: TextStyle( | ||||||
|  |                 fontSize: 14, | ||||||
|  |                 color: Theme.of(context).colorScheme.onSurfaceVariant, | ||||||
|  |               ), | ||||||
|  |             ), | ||||||
|  |             Text( | ||||||
|  |               formatFileSize(item.size), | ||||||
|  |               style: TextStyle( | ||||||
|  |                 fontSize: 12, | ||||||
|  |                 color: Theme.of(context).colorScheme.onSurfaceVariant, | ||||||
|  |               ), | ||||||
|  |             ), | ||||||
|  |             const Gap(8), | ||||||
|  |             TextButton.icon( | ||||||
|  |               onPressed: () { | ||||||
|  |                 launchUrlString( | ||||||
|  |                   'https://fs.solian.app/files/${item.id}', | ||||||
|  |                   mode: LaunchMode.externalApplication, | ||||||
|  |                 ); | ||||||
|  |               }, | ||||||
|  |               icon: const Icon(Symbols.launch), | ||||||
|  |               label: Text('openInBrowser').tr(), | ||||||
|  |             ), | ||||||
|  |           ], | ||||||
|  |         ).padding(all: 8), | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     if (heroTag != null) { |     if (heroTag != null) { | ||||||
| @@ -71,6 +125,35 @@ class CloudFileWidget extends HookConsumerWidget { | |||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | class _DataSavingPlaceholder extends StatelessWidget { | ||||||
|  |   final IconData icon; | ||||||
|  |   final VoidCallback onTap; | ||||||
|  |   const _DataSavingPlaceholder({required this.icon, required this.onTap}); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context) { | ||||||
|  |     return GestureDetector( | ||||||
|  |       onTap: onTap, | ||||||
|  |       child: Container( | ||||||
|  |         color: Colors.black26, | ||||||
|  |         alignment: Alignment.center, | ||||||
|  |         child: Column( | ||||||
|  |           mainAxisSize: MainAxisSize.min, | ||||||
|  |           children: [ | ||||||
|  |             Icon(icon, size: 36, | ||||||
|  |               color: Theme.of(context).colorScheme.onSurfaceVariant), | ||||||
|  |             const Gap(8), | ||||||
|  |             Text( | ||||||
|  |               'dataSavingHint'.tr(), | ||||||
|  |               style: Theme.of(context).textTheme.bodySmall, | ||||||
|  |               textAlign: TextAlign.center, | ||||||
|  |             ), | ||||||
|  |           ], | ||||||
|  |         ), | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
| class CloudVideoWidget extends HookConsumerWidget { | class CloudVideoWidget extends HookConsumerWidget { | ||||||
|   final SnCloudFile item; |   final SnCloudFile item; | ||||||
|   const CloudVideoWidget({super.key, required this.item}); |   const CloudVideoWidget({super.key, required this.item}); | ||||||
| @@ -269,32 +352,35 @@ class ProfilePictureWidget extends ConsumerWidget { | |||||||
|     this.fallbackColor, |     this.fallbackColor, | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
|   @override | @override | ||||||
|   Widget build(BuildContext context, WidgetRef ref) { |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|     final serverUrl = ref.watch(serverUrlProvider); |     final serverUrl = ref.watch(serverUrlProvider); | ||||||
|     final uri = '$serverUrl/drive/files/${file?.id ?? fileId}'; |     final String? id = file?.id ?? fileId; | ||||||
|  |  | ||||||
|  |     final fallback = Icon( | ||||||
|  |       fallbackIcon ?? Symbols.account_circle, | ||||||
|  |       size: radius, | ||||||
|  |       color: fallbackColor ?? Theme.of(context).colorScheme.onPrimaryContainer, | ||||||
|  |     ).center(); | ||||||
|  |  | ||||||
|     return ClipRRect( |     return ClipRRect( | ||||||
|       borderRadius: |       borderRadius: borderRadius == null | ||||||
|           borderRadius == null |           ? BorderRadius.all(Radius.circular(radius)) | ||||||
|               ? BorderRadius.all(Radius.circular(radius)) |           : BorderRadius.all(Radius.circular(borderRadius!)), | ||||||
|               : BorderRadius.all(Radius.circular(borderRadius!)), |  | ||||||
|       child: Container( |       child: Container( | ||||||
|         width: radius * 2, |         width: radius * 2, | ||||||
|         height: radius * 2, |         height: radius * 2, | ||||||
|         color: Theme.of(context).colorScheme.primaryContainer, |         color: Theme.of(context).colorScheme.primaryContainer, | ||||||
|         child: |         child: id == null | ||||||
|             file != null |             ? fallback | ||||||
|                 ? CloudFileWidget(item: file!, fit: BoxFit.cover) |             : DataSavingGate( | ||||||
|                 : fileId == null |                 bypass: true, | ||||||
|                 ? Icon( |                 placeholder: fallback, | ||||||
|                   fallbackIcon ?? Symbols.account_circle, |                 content: () => UniversalImage( | ||||||
|                   size: radius, |                   uri: '$serverUrl/drive/files/$id', | ||||||
|                   color: |                   fit: BoxFit.cover, | ||||||
|                       fallbackColor ?? |                 ), | ||||||
|                       Theme.of(context).colorScheme.onPrimaryContainer, |               ), | ||||||
|                 ).center() |  | ||||||
|                 : UniversalImage(uri: uri, fit: BoxFit.cover), |  | ||||||
|       ), |       ), | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -52,12 +52,10 @@ class UniversalImage extends StatelessWidget { | |||||||
|             }, |             }, | ||||||
|             errorWidget: (context, url, error) { |             errorWidget: (context, url, error) { | ||||||
|               return Image.asset( |               return Image.asset( | ||||||
|                 'assets/images/media-offline.png', |                 'assets/images/media-offline.jpg', | ||||||
|                 fit: BoxFit.cover, |                 fit: BoxFit.cover, | ||||||
|  |                 key: Key('image-broke-$uri'), | ||||||
|               ); |               ); | ||||||
|               // return const Center( |  | ||||||
|               //   child: Icon(Icons.broken_image, color: Colors.white, size: 16), |  | ||||||
|               // ); |  | ||||||
|             }, |             }, | ||||||
|           ), |           ), | ||||||
|         ], |         ], | ||||||
|   | |||||||
							
								
								
									
										27
									
								
								lib/widgets/data_saving_gate.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								lib/widgets/data_saving_gate.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | |||||||
|  | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
|  | import 'package:island/pods/config.dart'; | ||||||
|  |  | ||||||
|  |  | ||||||
|  | typedef WidgetBuilder0 = Widget Function(); | ||||||
|  |  | ||||||
|  | class DataSavingGate extends ConsumerWidget { | ||||||
|  |   final bool bypass; | ||||||
|  |   final WidgetBuilder0 content; | ||||||
|  |   final Widget placeholder; | ||||||
|  |  | ||||||
|  |   const DataSavingGate({ | ||||||
|  |     super.key, | ||||||
|  |     required this.bypass, | ||||||
|  |     required this.content, | ||||||
|  |     required this.placeholder, | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|  |     final dataSaving = | ||||||
|  |         ref.watch(appSettingsNotifierProvider.select((s) => s.dataSavingMode)); | ||||||
|  |     if (bypass || !dataSaving) return content(); | ||||||
|  |     return placeholder; | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										67
									
								
								lib/widgets/extended_refresh_indicator.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								lib/widgets/extended_refresh_indicator.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,67 @@ | |||||||
|  | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:flutter/services.dart'; | ||||||
|  |  | ||||||
|  | class RefreshIntent extends Intent { | ||||||
|  |   const RefreshIntent(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class ExtendedRefreshIndicator extends StatefulWidget { | ||||||
|  |   final Widget child; | ||||||
|  |   final RefreshCallback onRefresh; | ||||||
|  |  | ||||||
|  |   const ExtendedRefreshIndicator({ | ||||||
|  |     super.key, | ||||||
|  |     required this.child, | ||||||
|  |     required this.onRefresh, | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   State<ExtendedRefreshIndicator> createState() => | ||||||
|  |       _ExtendedRefreshIndicatorState(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class _ExtendedRefreshIndicatorState extends State<ExtendedRefreshIndicator> { | ||||||
|  |   late final FocusNode _focusNode; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   void initState() { | ||||||
|  |     super.initState(); | ||||||
|  |     _focusNode = FocusNode(); | ||||||
|  |     WidgetsBinding.instance.addPostFrameCallback((_) { | ||||||
|  |       _focusNode.requestFocus(); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   void dispose() { | ||||||
|  |     _focusNode.dispose(); | ||||||
|  |     super.dispose(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context) { | ||||||
|  |     return Shortcuts( | ||||||
|  |       shortcuts: <LogicalKeySet, Intent>{ | ||||||
|  |         LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyR): | ||||||
|  |             const RefreshIntent(), | ||||||
|  |         LogicalKeySet(LogicalKeyboardKey.meta, LogicalKeyboardKey.keyR): | ||||||
|  |             const RefreshIntent(), | ||||||
|  |         LogicalKeySet(LogicalKeyboardKey.f5): const RefreshIntent(), | ||||||
|  |       }, | ||||||
|  |       child: Actions( | ||||||
|  |         actions: <Type, Action<Intent>>{ | ||||||
|  |           RefreshIntent: CallbackAction<RefreshIntent>( | ||||||
|  |             onInvoke: (RefreshIntent intent) => widget.onRefresh(), | ||||||
|  |           ), | ||||||
|  |         }, | ||||||
|  |         child: Focus( | ||||||
|  |           focusNode: _focusNode, | ||||||
|  |           child: RefreshIndicator( | ||||||
|  |             onRefresh: widget.onRefresh, | ||||||
|  |             child: widget.child, | ||||||
|  |           ), | ||||||
|  |         ), | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -44,13 +44,13 @@ class PollStatsWidget extends StatelessWidget { | |||||||
|         // yes/no: map {true: count, false: count} |         // yes/no: map {true: count, false: count} | ||||||
|         if (raw is Map) { |         if (raw is Map) { | ||||||
|           final int yes = |           final int yes = | ||||||
|               (raw[true] is int) |               (raw['true'] is int) | ||||||
|                   ? raw[true] as int |                   ? raw['true'] as int | ||||||
|                   : int.tryParse('${raw[true]}') ?? 0; |                   : int.tryParse('${raw['true']}') ?? 0; | ||||||
|           final int no = |           final int no = | ||||||
|               (raw[false] is int) |               (raw['false'] is int) | ||||||
|                   ? raw[false] as int |                   ? raw['false'] as int | ||||||
|                   : int.tryParse('${raw[false]}') ?? 0; |                   : int.tryParse('${raw['false']}') ?? 0; | ||||||
|           final total = (yes + no).clamp(0, 1 << 31); |           final total = (yes + no).clamp(0, 1 << 31); | ||||||
|           final yesPct = total == 0 ? 0.0 : yes / total; |           final yesPct = total == 0 ? 0.0 : yes / total; | ||||||
|           final noPct = total == 0 ? 0.0 : no / total; |           final noPct = total == 0 ? 0.0 : no / total; | ||||||
|   | |||||||
| @@ -17,6 +17,7 @@ class PollSubmit extends ConsumerStatefulWidget { | |||||||
|     this.onCancel, |     this.onCancel, | ||||||
|     this.showProgress = true, |     this.showProgress = true, | ||||||
|     this.isReadonly = false, |     this.isReadonly = false, | ||||||
|  |     this.isInitiallyExpanded = false, | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
|   final SnPollWithStats poll; |   final SnPollWithStats poll; | ||||||
| @@ -36,6 +37,9 @@ class PollSubmit extends ConsumerStatefulWidget { | |||||||
|  |  | ||||||
|   final bool isReadonly; |   final bool isReadonly; | ||||||
|  |  | ||||||
|  |   /// Whether the poll should start expanded instead of collapsed. | ||||||
|  |   final bool isInitiallyExpanded; | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   ConsumerState<PollSubmit> createState() => _PollSubmitState(); |   ConsumerState<PollSubmit> createState() => _PollSubmitState(); | ||||||
| } | } | ||||||
| @@ -45,6 +49,7 @@ class _PollSubmitState extends ConsumerState<PollSubmit> { | |||||||
|   int _index = 0; |   int _index = 0; | ||||||
|   bool _submitting = false; |   bool _submitting = false; | ||||||
|   bool _isModifying = false; // New state to track if user is modifying answers |   bool _isModifying = false; // New state to track if user is modifying answers | ||||||
|  |   bool _isCollapsed = true; // New state to track collapse/expand | ||||||
|  |  | ||||||
|   /// Collected answers, keyed by questionId |   /// Collected answers, keyed by questionId | ||||||
|   late Map<String, dynamic> _answers; |   late Map<String, dynamic> _answers; | ||||||
| @@ -65,6 +70,8 @@ class _PollSubmitState extends ConsumerState<PollSubmit> { | |||||||
|     _questions = [...widget.poll.questions] |     _questions = [...widget.poll.questions] | ||||||
|       ..sort((a, b) => a.order.compareTo(b.order)); |       ..sort((a, b) => a.order.compareTo(b.order)); | ||||||
|     _answers = Map<String, dynamic>.from(widget.initialAnswers ?? {}); |     _answers = Map<String, dynamic>.from(widget.initialAnswers ?? {}); | ||||||
|  |     // Set initial collapse state based on the parameter | ||||||
|  |     _isCollapsed = !widget.isInitiallyExpanded; | ||||||
|     if (!widget.isReadonly) { |     if (!widget.isReadonly) { | ||||||
|       _loadCurrentIntoLocalState(); |       _loadCurrentIntoLocalState(); | ||||||
|       // If initial answers are provided, set _isModifying to false initially |       // If initial answers are provided, set _isModifying to false initially | ||||||
| @@ -405,6 +412,7 @@ class _PollSubmitState extends ConsumerState<PollSubmit> { | |||||||
|               }); |               }); | ||||||
|             }, |             }, | ||||||
|             multiSelectionEnabled: false, |             multiSelectionEnabled: false, | ||||||
|  |             emptySelectionAllowed: true, | ||||||
|           ), |           ), | ||||||
|         ), |         ), | ||||||
|       ], |       ], | ||||||
| @@ -652,39 +660,179 @@ class _PollSubmitState extends ConsumerState<PollSubmit> { | |||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   Widget _buildCollapsedView(BuildContext context) { | ||||||
|  |     return Column( | ||||||
|  |       crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|  |       children: [ | ||||||
|  |         Row( | ||||||
|  |           children: [ | ||||||
|  |             Expanded( | ||||||
|  |               child: Column( | ||||||
|  |                 crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|  |                 children: [ | ||||||
|  |                   if (widget.poll.title != null) | ||||||
|  |                     Text( | ||||||
|  |                       widget.poll.title!, | ||||||
|  |                       style: Theme.of(context).textTheme.titleMedium?.copyWith( | ||||||
|  |                         fontWeight: FontWeight.w600, | ||||||
|  |                       ), | ||||||
|  |                       maxLines: 1, | ||||||
|  |                       overflow: TextOverflow.ellipsis, | ||||||
|  |                     ), | ||||||
|  |                   if (widget.poll.description != null) | ||||||
|  |                     Padding( | ||||||
|  |                       padding: const EdgeInsets.only(top: 2), | ||||||
|  |                       child: Text( | ||||||
|  |                         widget.poll.description!, | ||||||
|  |                         style: Theme.of(context).textTheme.bodySmall?.copyWith( | ||||||
|  |                           color: Theme.of( | ||||||
|  |                             context, | ||||||
|  |                           ).textTheme.bodySmall?.color?.withOpacity(0.7), | ||||||
|  |                         ), | ||||||
|  |                         maxLines: 2, | ||||||
|  |                         overflow: TextOverflow.ellipsis, | ||||||
|  |                       ), | ||||||
|  |                     ) | ||||||
|  |                   else | ||||||
|  |                     Padding( | ||||||
|  |                       padding: const EdgeInsets.only(top: 2), | ||||||
|  |                       child: Text( | ||||||
|  |                         '${_questions.length} question${_questions.length == 1 ? '' : 's'}', | ||||||
|  |                         style: Theme.of(context).textTheme.bodySmall?.copyWith( | ||||||
|  |                           color: Theme.of( | ||||||
|  |                             context, | ||||||
|  |                           ).textTheme.bodySmall?.color?.withOpacity(0.7), | ||||||
|  |                         ), | ||||||
|  |                       ), | ||||||
|  |                     ), | ||||||
|  |                 ], | ||||||
|  |               ), | ||||||
|  |             ), | ||||||
|  |             IconButton( | ||||||
|  |               icon: Icon( | ||||||
|  |                 _isCollapsed ? Icons.expand_more : Icons.expand_less, | ||||||
|  |                 size: 20, | ||||||
|  |               ), | ||||||
|  |               onPressed: () { | ||||||
|  |                 setState(() { | ||||||
|  |                   _isCollapsed = !_isCollapsed; | ||||||
|  |                 }); | ||||||
|  |               }, | ||||||
|  |               visualDensity: VisualDensity.compact, | ||||||
|  |               tooltip: _isCollapsed ? 'expandPoll'.tr() : 'collapsePoll'.tr(), | ||||||
|  |             ), | ||||||
|  |           ], | ||||||
|  |         ), | ||||||
|  |       ], | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
|     if (_questions.isEmpty) { |     if (_questions.isEmpty) { | ||||||
|       return const SizedBox.shrink(); |       return const SizedBox.shrink(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     // If collapsed, show collapsed view for all states | ||||||
|  |     if (_isCollapsed) { | ||||||
|  |       return _buildCollapsedView(context); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     // If poll is already submitted and not in readonly mode, and not in modification mode, show submitted view |     // If poll is already submitted and not in readonly mode, and not in modification mode, show submitted view | ||||||
|     if (widget.initialAnswers != null && !widget.isReadonly && !_isModifying) { |     if (widget.initialAnswers != null && !widget.isReadonly && !_isModifying) { | ||||||
|       return Column( |       return Column( | ||||||
|         crossAxisAlignment: CrossAxisAlignment.stretch, |         crossAxisAlignment: CrossAxisAlignment.stretch, | ||||||
|         children: [_buildSubmittedView(context), _buildNavBar(context)], |         children: [ | ||||||
|  |           _buildCollapsedView(context), | ||||||
|  |           const SizedBox(height: 8), | ||||||
|  |           AnimatedSwitcher( | ||||||
|  |             duration: const Duration(milliseconds: 300), | ||||||
|  |             transitionBuilder: (child, anim) { | ||||||
|  |               final offset = Tween<Offset>( | ||||||
|  |                 begin: const Offset(0, -0.1), | ||||||
|  |                 end: Offset.zero, | ||||||
|  |               ).animate(CurvedAnimation(parent: anim, curve: Curves.easeOut)); | ||||||
|  |               final fade = CurvedAnimation(parent: anim, curve: Curves.easeOut); | ||||||
|  |               return FadeTransition( | ||||||
|  |                 opacity: fade, | ||||||
|  |                 child: SlideTransition(position: offset, child: child), | ||||||
|  |               ); | ||||||
|  |             }, | ||||||
|  |             child: Column( | ||||||
|  |               key: const ValueKey('submitted_expanded'), | ||||||
|  |               crossAxisAlignment: CrossAxisAlignment.stretch, | ||||||
|  |               children: [_buildSubmittedView(context), _buildNavBar(context)], | ||||||
|  |             ), | ||||||
|  |           ), | ||||||
|  |         ], | ||||||
|       ); |       ); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // If poll is in readonly mode, show readonly view |     // If poll is in readonly mode, show readonly view | ||||||
|     if (widget.isReadonly) { |     if (widget.isReadonly) { | ||||||
|       return _buildReadonlyView(context); |       return Column( | ||||||
|  |         crossAxisAlignment: CrossAxisAlignment.stretch, | ||||||
|  |         children: [ | ||||||
|  |           _buildCollapsedView(context), | ||||||
|  |           const SizedBox(height: 8), | ||||||
|  |           AnimatedSwitcher( | ||||||
|  |             duration: const Duration(milliseconds: 300), | ||||||
|  |             transitionBuilder: (child, anim) { | ||||||
|  |               final offset = Tween<Offset>( | ||||||
|  |                 begin: const Offset(0, -0.1), | ||||||
|  |                 end: Offset.zero, | ||||||
|  |               ).animate(CurvedAnimation(parent: anim, curve: Curves.easeOut)); | ||||||
|  |               final fade = CurvedAnimation(parent: anim, curve: Curves.easeOut); | ||||||
|  |               return FadeTransition( | ||||||
|  |                 opacity: fade, | ||||||
|  |                 child: SlideTransition(position: offset, child: child), | ||||||
|  |               ); | ||||||
|  |             }, | ||||||
|  |             child: _buildReadonlyView(context), | ||||||
|  |           ), | ||||||
|  |         ], | ||||||
|  |       ); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     return Column( |     return Column( | ||||||
|       crossAxisAlignment: CrossAxisAlignment.stretch, |       crossAxisAlignment: CrossAxisAlignment.stretch, | ||||||
|       children: [ |       children: [ | ||||||
|         _buildHeader(context), |         _buildCollapsedView(context), | ||||||
|         const SizedBox(height: 12), |         const SizedBox(height: 8), | ||||||
|         _AnimatedStep( |         AnimatedSwitcher( | ||||||
|           key: ValueKey(_current.id), |           duration: const Duration(milliseconds: 300), | ||||||
|  |           transitionBuilder: (child, anim) { | ||||||
|  |             final offset = Tween<Offset>( | ||||||
|  |               begin: const Offset(0, -0.1), | ||||||
|  |               end: Offset.zero, | ||||||
|  |             ).animate(CurvedAnimation(parent: anim, curve: Curves.easeOut)); | ||||||
|  |             final fade = CurvedAnimation(parent: anim, curve: Curves.easeOut); | ||||||
|  |             return FadeTransition( | ||||||
|  |               opacity: fade, | ||||||
|  |               child: SlideTransition(position: offset, child: child), | ||||||
|  |             ); | ||||||
|  |           }, | ||||||
|           child: Column( |           child: Column( | ||||||
|  |             key: const ValueKey('normal_expanded'), | ||||||
|             crossAxisAlignment: CrossAxisAlignment.stretch, |             crossAxisAlignment: CrossAxisAlignment.stretch, | ||||||
|             children: [_buildBody(context), _buildStats(context, _current)], |             children: [ | ||||||
|  |               _buildHeader(context), | ||||||
|  |               const SizedBox(height: 12), | ||||||
|  |               _AnimatedStep( | ||||||
|  |                 key: ValueKey(_current.id), | ||||||
|  |                 child: Column( | ||||||
|  |                   crossAxisAlignment: CrossAxisAlignment.stretch, | ||||||
|  |                   children: [ | ||||||
|  |                     _buildBody(context), | ||||||
|  |                     _buildStats(context, _current), | ||||||
|  |                   ], | ||||||
|  |                 ), | ||||||
|  |               ), | ||||||
|  |               const SizedBox(height: 16), | ||||||
|  |               _buildNavBar(context), | ||||||
|  |             ], | ||||||
|           ), |           ), | ||||||
|         ), |         ), | ||||||
|         const SizedBox(height: 16), |  | ||||||
|         _buildNavBar(context), |  | ||||||
|       ], |       ], | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
|   | |||||||
							
								
								
									
										287
									
								
								lib/widgets/post/compose_embed_sheet.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										287
									
								
								lib/widgets/post/compose_embed_sheet.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,287 @@ | |||||||
|  | import 'package:dropdown_button2/dropdown_button2.dart'; | ||||||
|  | import 'package:easy_localization/easy_localization.dart'; | ||||||
|  | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:flutter/services.dart'; | ||||||
|  | import 'package:flutter_hooks/flutter_hooks.dart'; | ||||||
|  | import 'package:gap/gap.dart'; | ||||||
|  | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
|  | import 'package:island/models/post.dart'; | ||||||
|  | import 'package:island/widgets/content/sheet.dart'; | ||||||
|  | import 'package:island/widgets/post/compose_shared.dart'; | ||||||
|  | import 'package:material_symbols_icons/symbols.dart'; | ||||||
|  | import 'package:styled_widget/styled_widget.dart'; | ||||||
|  |  | ||||||
|  | class ComposeEmbedSheet extends HookConsumerWidget { | ||||||
|  |   final ComposeState state; | ||||||
|  |  | ||||||
|  |   const ComposeEmbedSheet({super.key, required this.state}); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|  |     final theme = Theme.of(context); | ||||||
|  |     final colorScheme = theme.colorScheme; | ||||||
|  |  | ||||||
|  |     // Listen to embed view changes | ||||||
|  |     final currentEmbedView = useValueListenable(state.embedView); | ||||||
|  |  | ||||||
|  |     // Form state | ||||||
|  |     final uriController = useTextEditingController(); | ||||||
|  |     final aspectRatioController = useTextEditingController(); | ||||||
|  |     final selectedRenderer = useState<PostEmbedViewRenderer>( | ||||||
|  |       PostEmbedViewRenderer.webView, | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  |     void clearForm() { | ||||||
|  |       uriController.clear(); | ||||||
|  |       aspectRatioController.clear(); | ||||||
|  |       selectedRenderer.value = PostEmbedViewRenderer.webView; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // Populate form when embed view changes | ||||||
|  |     useEffect(() { | ||||||
|  |       if (currentEmbedView != null) { | ||||||
|  |         uriController.text = currentEmbedView.uri; | ||||||
|  |         aspectRatioController.text = | ||||||
|  |             currentEmbedView.aspectRatio?.toString() ?? ''; | ||||||
|  |         selectedRenderer.value = currentEmbedView.renderer; | ||||||
|  |       } else { | ||||||
|  |         clearForm(); | ||||||
|  |       } | ||||||
|  |       return null; | ||||||
|  |     }, [currentEmbedView]); | ||||||
|  |  | ||||||
|  |     void saveEmbedView() { | ||||||
|  |       final uri = uriController.text.trim(); | ||||||
|  |       if (uri.isEmpty) { | ||||||
|  |         ScaffoldMessenger.of( | ||||||
|  |           context, | ||||||
|  |         ).showSnackBar(SnackBar(content: Text('embedUriRequired'.tr()))); | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       final aspectRatio = | ||||||
|  |           aspectRatioController.text.trim().isNotEmpty | ||||||
|  |               ? double.tryParse(aspectRatioController.text.trim()) | ||||||
|  |               : null; | ||||||
|  |  | ||||||
|  |       final embedView = SnPostEmbedView( | ||||||
|  |         uri: uri, | ||||||
|  |         aspectRatio: aspectRatio, | ||||||
|  |         renderer: selectedRenderer.value, | ||||||
|  |       ); | ||||||
|  |  | ||||||
|  |       if (currentEmbedView != null) { | ||||||
|  |         ComposeLogic.updateEmbedView(state, embedView); | ||||||
|  |       } else { | ||||||
|  |         ComposeLogic.setEmbedView(state, embedView); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return SheetScaffold( | ||||||
|  |       titleText: 'embedView'.tr(), | ||||||
|  |       heightFactor: 0.7, | ||||||
|  |       child: Column( | ||||||
|  |         children: [ | ||||||
|  |           // Header with save button when editing | ||||||
|  |           if (currentEmbedView != null) | ||||||
|  |             Container( | ||||||
|  |               padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 8), | ||||||
|  |               color: Theme.of(context).colorScheme.surfaceContainerHigh, | ||||||
|  |               child: Row( | ||||||
|  |                 children: [ | ||||||
|  |                   Expanded( | ||||||
|  |                     child: Text( | ||||||
|  |                       'editEmbed'.tr(), | ||||||
|  |                       style: theme.textTheme.titleMedium, | ||||||
|  |                     ), | ||||||
|  |                   ), | ||||||
|  |                   TextButton( | ||||||
|  |                     onPressed: saveEmbedView, | ||||||
|  |                     child: Text('save'.tr()), | ||||||
|  |                   ), | ||||||
|  |                 ], | ||||||
|  |               ), | ||||||
|  |             ), | ||||||
|  |  | ||||||
|  |           // Content area | ||||||
|  |           Expanded( | ||||||
|  |             child: SingleChildScrollView( | ||||||
|  |               padding: const EdgeInsets.all(16), | ||||||
|  |               child: Column( | ||||||
|  |                 crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|  |                 children: [ | ||||||
|  |                   // Form fields | ||||||
|  |                   TextField( | ||||||
|  |                     controller: uriController, | ||||||
|  |                     decoration: InputDecoration( | ||||||
|  |                       labelText: 'embedUri'.tr(), | ||||||
|  |                       hintText: 'https://example.com', | ||||||
|  |                       border: OutlineInputBorder( | ||||||
|  |                         borderRadius: BorderRadius.circular(8), | ||||||
|  |                       ), | ||||||
|  |                     ), | ||||||
|  |                     keyboardType: TextInputType.url, | ||||||
|  |                   ), | ||||||
|  |                   const Gap(16), | ||||||
|  |                   TextField( | ||||||
|  |                     controller: aspectRatioController, | ||||||
|  |                     decoration: InputDecoration( | ||||||
|  |                       labelText: 'aspectRatio'.tr(), | ||||||
|  |                       hintText: '16/9 = 1.777', | ||||||
|  |                       border: OutlineInputBorder( | ||||||
|  |                         borderRadius: BorderRadius.circular(8), | ||||||
|  |                       ), | ||||||
|  |                     ), | ||||||
|  |                     keyboardType: TextInputType.numberWithOptions( | ||||||
|  |                       decimal: true, | ||||||
|  |                     ), | ||||||
|  |                     inputFormatters: [ | ||||||
|  |                       FilteringTextInputFormatter.allow(RegExp(r'^\d*\.?\d*$')), | ||||||
|  |                     ], | ||||||
|  |                   ), | ||||||
|  |                   const Gap(16), | ||||||
|  |                   DropdownButtonFormField2<PostEmbedViewRenderer>( | ||||||
|  |                     value: selectedRenderer.value, | ||||||
|  |                     decoration: InputDecoration( | ||||||
|  |                       labelText: 'renderer'.tr(), | ||||||
|  |                       border: OutlineInputBorder( | ||||||
|  |                         borderRadius: BorderRadius.circular(8), | ||||||
|  |                       ), | ||||||
|  |                     ), | ||||||
|  |                     items: | ||||||
|  |                         PostEmbedViewRenderer.values.map((renderer) { | ||||||
|  |                           return DropdownMenuItem( | ||||||
|  |                             value: renderer, | ||||||
|  |                             child: Text(renderer.name).tr(), | ||||||
|  |                           ); | ||||||
|  |                         }).toList(), | ||||||
|  |                     onChanged: (value) { | ||||||
|  |                       if (value != null) { | ||||||
|  |                         selectedRenderer.value = value; | ||||||
|  |                       } | ||||||
|  |                     }, | ||||||
|  |                   ), | ||||||
|  |  | ||||||
|  |                   // Current embed view display (when exists) | ||||||
|  |                   if (currentEmbedView != null) ...[ | ||||||
|  |                     const Gap(32), | ||||||
|  |                     Text( | ||||||
|  |                       'currentEmbed'.tr(), | ||||||
|  |                       style: theme.textTheme.titleMedium, | ||||||
|  |                     ).padding(horizontal: 4), | ||||||
|  |                     const Gap(8), | ||||||
|  |                     Card( | ||||||
|  |                       margin: EdgeInsets.zero, | ||||||
|  |                       color: Theme.of(context).colorScheme.surfaceContainerHigh, | ||||||
|  |                       child: Padding( | ||||||
|  |                         padding: const EdgeInsets.only( | ||||||
|  |                           left: 16, | ||||||
|  |                           right: 16, | ||||||
|  |                           bottom: 12, | ||||||
|  |                           top: 4, | ||||||
|  |                         ), | ||||||
|  |                         child: Column( | ||||||
|  |                           crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|  |                           children: [ | ||||||
|  |                             Row( | ||||||
|  |                               children: [ | ||||||
|  |                                 Icon( | ||||||
|  |                                   currentEmbedView.renderer == | ||||||
|  |                                           PostEmbedViewRenderer.webView | ||||||
|  |                                       ? Symbols.web | ||||||
|  |                                       : Symbols.web, | ||||||
|  |                                   color: colorScheme.primary, | ||||||
|  |                                 ), | ||||||
|  |                                 const Gap(12), | ||||||
|  |                                 Expanded( | ||||||
|  |                                   child: Text( | ||||||
|  |                                     currentEmbedView.uri, | ||||||
|  |                                     style: theme.textTheme.bodyMedium, | ||||||
|  |                                     maxLines: 1, | ||||||
|  |                                     overflow: TextOverflow.ellipsis, | ||||||
|  |                                   ), | ||||||
|  |                                 ), | ||||||
|  |                                 IconButton( | ||||||
|  |                                   icon: const Icon(Symbols.delete), | ||||||
|  |                                   onPressed: () { | ||||||
|  |                                     showDialog( | ||||||
|  |                                       context: context, | ||||||
|  |                                       builder: | ||||||
|  |                                           (dialogContext) => AlertDialog( | ||||||
|  |                                             title: Text('deleteEmbed').tr(), | ||||||
|  |                                             content: | ||||||
|  |                                                 Text('deleteEmbedConfirm').tr(), | ||||||
|  |                                             actions: [ | ||||||
|  |                                               TextButton( | ||||||
|  |                                                 onPressed: | ||||||
|  |                                                     () => | ||||||
|  |                                                         Navigator.of( | ||||||
|  |                                                           dialogContext, | ||||||
|  |                                                         ).pop(), | ||||||
|  |                                                 child: Text('cancel'.tr()), | ||||||
|  |                                               ), | ||||||
|  |                                               TextButton( | ||||||
|  |                                                 onPressed: () { | ||||||
|  |                                                   ComposeLogic.deleteEmbedView( | ||||||
|  |                                                     state, | ||||||
|  |                                                   ); | ||||||
|  |                                                   clearForm(); | ||||||
|  |                                                   Navigator.of( | ||||||
|  |                                                     dialogContext, | ||||||
|  |                                                   ).pop(); | ||||||
|  |                                                 }, | ||||||
|  |                                                 style: TextButton.styleFrom( | ||||||
|  |                                                   foregroundColor: | ||||||
|  |                                                       colorScheme.error, | ||||||
|  |                                                 ), | ||||||
|  |                                                 child: Text('delete').tr(), | ||||||
|  |                                               ), | ||||||
|  |                                             ], | ||||||
|  |                                           ), | ||||||
|  |                                     ); | ||||||
|  |                                   }, | ||||||
|  |                                   tooltip: 'delete'.tr(), | ||||||
|  |                                   color: colorScheme.error, | ||||||
|  |                                 ), | ||||||
|  |                               ], | ||||||
|  |                             ), | ||||||
|  |                             const Gap(12), | ||||||
|  |                             Text( | ||||||
|  |                               'aspectRatio'.tr(), | ||||||
|  |                               style: theme.textTheme.labelMedium?.copyWith( | ||||||
|  |                                 color: colorScheme.onSurfaceVariant, | ||||||
|  |                               ), | ||||||
|  |                             ), | ||||||
|  |                             const Gap(4), | ||||||
|  |                             Text( | ||||||
|  |                               currentEmbedView.aspectRatio != null | ||||||
|  |                                   ? currentEmbedView.aspectRatio! | ||||||
|  |                                       .toStringAsFixed(2) | ||||||
|  |                                   : 'notSet'.tr(), | ||||||
|  |                               style: theme.textTheme.bodyMedium, | ||||||
|  |                             ), | ||||||
|  |                           ], | ||||||
|  |                         ), | ||||||
|  |                       ), | ||||||
|  |                     ), | ||||||
|  |                   ] else ...[ | ||||||
|  |                     // Save button for new embed | ||||||
|  |                     const Gap(16), | ||||||
|  |                     SizedBox( | ||||||
|  |                       width: double.infinity, | ||||||
|  |                       child: FilledButton.icon( | ||||||
|  |                         onPressed: saveEmbedView, | ||||||
|  |                         icon: const Icon(Symbols.add), | ||||||
|  |                         label: Text('addEmbed'.tr()), | ||||||
|  |                       ), | ||||||
|  |                     ), | ||||||
|  |                   ], | ||||||
|  |                 ], | ||||||
|  |               ), | ||||||
|  |             ), | ||||||
|  |           ), | ||||||
|  |         ], | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -1,6 +1,7 @@ | |||||||
| import 'package:collection/collection.dart'; | import 'package:collection/collection.dart'; | ||||||
| import 'package:dio/dio.dart'; | import 'package:dio/dio.dart'; | ||||||
| import 'package:easy_localization/easy_localization.dart'; | import 'package:easy_localization/easy_localization.dart'; | ||||||
|  | import 'package:file_picker/file_picker.dart'; | ||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
| import 'package:flutter/services.dart'; | import 'package:flutter/services.dart'; | ||||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
| @@ -36,6 +37,7 @@ class ComposeState { | |||||||
|   final ValueNotifier<List<SnPostCategory>> categories; |   final ValueNotifier<List<SnPostCategory>> categories; | ||||||
|   StringTagController tagsController; |   StringTagController tagsController; | ||||||
|   final ValueNotifier<SnRealm?> realm; |   final ValueNotifier<SnRealm?> realm; | ||||||
|  |   final ValueNotifier<SnPostEmbedView?> embedView; | ||||||
|   final String draftId; |   final String draftId; | ||||||
|   int postType; |   int postType; | ||||||
|   // Linked poll id for this compose session (nullable) |   // Linked poll id for this compose session (nullable) | ||||||
| @@ -55,6 +57,7 @@ class ComposeState { | |||||||
|     required this.tagsController, |     required this.tagsController, | ||||||
|     required this.categories, |     required this.categories, | ||||||
|     required this.realm, |     required this.realm, | ||||||
|  |     required this.embedView, | ||||||
|     required this.draftId, |     required this.draftId, | ||||||
|     this.postType = 0, |     this.postType = 0, | ||||||
|     String? pollId, |     String? pollId, | ||||||
| @@ -119,6 +122,7 @@ class ComposeLogic { | |||||||
|         originalPost?.categories ?? [], |         originalPost?.categories ?? [], | ||||||
|       ), |       ), | ||||||
|       realm: ValueNotifier(originalPost?.realm), |       realm: ValueNotifier(originalPost?.realm), | ||||||
|  |       embedView: ValueNotifier<SnPostEmbedView?>(originalPost?.embedView), | ||||||
|       draftId: id, |       draftId: id, | ||||||
|       postType: postType, |       postType: postType, | ||||||
|       // initialize without poll by default |       // initialize without poll by default | ||||||
| @@ -150,6 +154,7 @@ class ComposeLogic { | |||||||
|       tagsController: tagsController, |       tagsController: tagsController, | ||||||
|       categories: ValueNotifier<List<SnPostCategory>>([]), |       categories: ValueNotifier<List<SnPostCategory>>([]), | ||||||
|       realm: ValueNotifier(null), |       realm: ValueNotifier(null), | ||||||
|  |       embedView: ValueNotifier<SnPostEmbedView?>(draft.embedView), | ||||||
|       draftId: draft.id, |       draftId: draft.id, | ||||||
|       postType: postType, |       postType: postType, | ||||||
|       pollId: null, |       pollId: null, | ||||||
| @@ -252,6 +257,7 @@ class ComposeLogic { | |||||||
|         tags: [], |         tags: [], | ||||||
|         categories: [], |         categories: [], | ||||||
|         collections: [], |         collections: [], | ||||||
|  |         embedView: state.embedView.value, | ||||||
|         createdAt: DateTime.now(), |         createdAt: DateTime.now(), | ||||||
|         updatedAt: DateTime.now(), |         updatedAt: DateTime.now(), | ||||||
|         deletedAt: null, |         deletedAt: null, | ||||||
| @@ -328,6 +334,7 @@ class ComposeLogic { | |||||||
|         tags: [], |         tags: [], | ||||||
|         categories: [], |         categories: [], | ||||||
|         collections: [], |         collections: [], | ||||||
|  |         embedView: state.embedView.value, | ||||||
|         createdAt: DateTime.now(), |         createdAt: DateTime.now(), | ||||||
|         updatedAt: DateTime.now(), |         updatedAt: DateTime.now(), | ||||||
|         deletedAt: null, |         deletedAt: null, | ||||||
| @@ -388,26 +395,32 @@ class ComposeLogic { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   static Future<void> pickPhotoMedia(WidgetRef ref, ComposeState state) async { |   static Future<void> pickPhotoMedia(WidgetRef ref, ComposeState state) async { | ||||||
|     final result = await ref |     final result = await FilePicker.platform.pickFiles( | ||||||
|         .watch(imagePickerProvider) |       type: FileType.image, | ||||||
|         .pickMultiImage(requestFullMetadata: true); |       allowMultiple: true, | ||||||
|     if (result.isEmpty) return; |       allowCompression: false, | ||||||
|  |     ); | ||||||
|  |     if (result == null || result.count == 0) return; | ||||||
|     state.attachments.value = [ |     state.attachments.value = [ | ||||||
|       ...state.attachments.value, |       ...state.attachments.value, | ||||||
|       ...result.map( |       ...result.files.map( | ||||||
|         (e) => UniversalFile(data: e, type: UniversalFileType.image), |         (e) => UniversalFile(data: e.xFile, type: UniversalFileType.image), | ||||||
|       ), |       ), | ||||||
|     ]; |     ]; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   static Future<void> pickVideoMedia(WidgetRef ref, ComposeState state) async { |   static Future<void> pickVideoMedia(WidgetRef ref, ComposeState state) async { | ||||||
|     final result = await ref |     final result = await FilePicker.platform.pickFiles( | ||||||
|         .watch(imagePickerProvider) |       type: FileType.video, | ||||||
|         .pickVideo(source: ImageSource.gallery); |       allowMultiple: true, | ||||||
|     if (result == null) return; |       allowCompression: false, | ||||||
|  |     ); | ||||||
|  |     if (result == null || result.count == 0) return; | ||||||
|     state.attachments.value = [ |     state.attachments.value = [ | ||||||
|       ...state.attachments.value, |       ...state.attachments.value, | ||||||
|       UniversalFile(data: result, type: UniversalFileType.video), |       ...result.files.map( | ||||||
|  |         (e) => UniversalFile(data: e.xFile, type: UniversalFileType.video), | ||||||
|  |       ), | ||||||
|     ]; |     ]; | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -570,6 +583,18 @@ class ComposeLogic { | |||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   static void setEmbedView(ComposeState state, SnPostEmbedView embedView) { | ||||||
|  |     state.embedView.value = embedView; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   static void updateEmbedView(ComposeState state, SnPostEmbedView embedView) { | ||||||
|  |     state.embedView.value = embedView; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   static void deleteEmbedView(ComposeState state) { | ||||||
|  |     state.embedView.value = null; | ||||||
|  |   } | ||||||
|  |  | ||||||
|   static Future<void> pickPoll( |   static Future<void> pickPoll( | ||||||
|     WidgetRef ref, |     WidgetRef ref, | ||||||
|     ComposeState state, |     ComposeState state, | ||||||
| @@ -653,6 +678,8 @@ class ComposeLogic { | |||||||
|         'categories': state.categories.value.map((e) => e.slug).toList(), |         'categories': state.categories.value.map((e) => e.slug).toList(), | ||||||
|         if (state.realm.value != null) 'realm_id': state.realm.value?.id, |         if (state.realm.value != null) 'realm_id': state.realm.value?.id, | ||||||
|         if (state.pollId.value != null) 'poll_id': state.pollId.value, |         if (state.pollId.value != null) 'poll_id': state.pollId.value, | ||||||
|  |         if (state.embedView.value != null) | ||||||
|  |           'embed_view': state.embedView.value!.toJson(), | ||||||
|       }; |       }; | ||||||
|  |  | ||||||
|       // Send request |       // Send request | ||||||
| @@ -746,6 +773,7 @@ class ComposeLogic { | |||||||
|     state.tagsController.dispose(); |     state.tagsController.dispose(); | ||||||
|     state.categories.dispose(); |     state.categories.dispose(); | ||||||
|     state.realm.dispose(); |     state.realm.dispose(); | ||||||
|  |     state.embedView.dispose(); | ||||||
|     state.pollId.dispose(); |     state.pollId.dispose(); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; | |||||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
| import 'package:island/models/post.dart'; | import 'package:island/models/post.dart'; | ||||||
| import 'package:island/services/compose_storage_db.dart'; | import 'package:island/services/compose_storage_db.dart'; | ||||||
|  | import 'package:island/widgets/post/compose_embed_sheet.dart'; | ||||||
| import 'package:island/widgets/post/compose_shared.dart'; | import 'package:island/widgets/post/compose_shared.dart'; | ||||||
| import 'package:island/widgets/post/draft_manager.dart'; | import 'package:island/widgets/post/draft_manager.dart'; | ||||||
| import 'package:material_symbols_icons/symbols.dart'; | import 'package:material_symbols_icons/symbols.dart'; | ||||||
| @@ -40,6 +41,14 @@ class ComposeToolbar extends HookConsumerWidget { | |||||||
|       ComposeLogic.pickPoll(ref, state, context); |       ComposeLogic.pickPoll(ref, state, context); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     void showEmbedSheet() { | ||||||
|  |       showModalBottomSheet( | ||||||
|  |         context: context, | ||||||
|  |         isScrollControlled: true, | ||||||
|  |         builder: (context) => ComposeEmbedSheet(state: state), | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     void showDraftManager() { |     void showDraftManager() { | ||||||
|       showModalBottomSheet( |       showModalBottomSheet( | ||||||
|         context: context, |         context: context, | ||||||
| @@ -112,6 +121,25 @@ class ComposeToolbar extends HookConsumerWidget { | |||||||
|                   ); |                   ); | ||||||
|                 }, |                 }, | ||||||
|               ), |               ), | ||||||
|  |               // Embed button with visual state when embed is present | ||||||
|  |               ListenableBuilder( | ||||||
|  |                 listenable: state.embedView, | ||||||
|  |                 builder: (context, _) { | ||||||
|  |                   return IconButton( | ||||||
|  |                     onPressed: showEmbedSheet, | ||||||
|  |                     icon: const Icon(Symbols.web), | ||||||
|  |                     tooltip: 'embedView'.tr(), | ||||||
|  |                     color: colorScheme.primary, | ||||||
|  |                     style: ButtonStyle( | ||||||
|  |                       backgroundColor: WidgetStatePropertyAll( | ||||||
|  |                         state.embedView.value != null | ||||||
|  |                             ? colorScheme.primary.withOpacity(0.15) | ||||||
|  |                             : null, | ||||||
|  |                       ), | ||||||
|  |                     ), | ||||||
|  |                   ); | ||||||
|  |                 }, | ||||||
|  |               ), | ||||||
|               const Spacer(), |               const Spacer(), | ||||||
|               if (originalPost == null && state.isEmpty) |               if (originalPost == null && state.isEmpty) | ||||||
|                 IconButton( |                 IconButton( | ||||||
|   | |||||||
							
								
								
									
										233
									
								
								lib/widgets/post/embed_view_renderer.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										233
									
								
								lib/widgets/post/embed_view_renderer.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,233 @@ | |||||||
|  | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:flutter_inappwebview/flutter_inappwebview.dart'; | ||||||
|  | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
|  | import 'package:island/models/post.dart'; | ||||||
|  | import 'package:material_symbols_icons/symbols.dart'; | ||||||
|  | import 'package:url_launcher/url_launcher.dart'; | ||||||
|  |  | ||||||
|  | class EmbedViewRenderer extends HookConsumerWidget { | ||||||
|  |   final SnPostEmbedView embedView; | ||||||
|  |   final double? maxHeight; | ||||||
|  |   final BorderRadius? borderRadius; | ||||||
|  |  | ||||||
|  |   const EmbedViewRenderer({ | ||||||
|  |     super.key, | ||||||
|  |     required this.embedView, | ||||||
|  |     this.maxHeight = 400, | ||||||
|  |     this.borderRadius, | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|  |     final theme = Theme.of(context); | ||||||
|  |     final colorScheme = theme.colorScheme; | ||||||
|  |  | ||||||
|  |     return Container( | ||||||
|  |       constraints: BoxConstraints(maxHeight: maxHeight ?? 400), | ||||||
|  |       decoration: BoxDecoration( | ||||||
|  |         borderRadius: borderRadius ?? BorderRadius.circular(12), | ||||||
|  |         border: Border.all( | ||||||
|  |           color: colorScheme.outline.withOpacity(0.3), | ||||||
|  |           width: 1, | ||||||
|  |         ), | ||||||
|  |         color: colorScheme.surfaceContainerLowest, | ||||||
|  |       ), | ||||||
|  |       child: ClipRRect( | ||||||
|  |         borderRadius: borderRadius ?? BorderRadius.circular(12), | ||||||
|  |         child: Column( | ||||||
|  |           mainAxisSize: MainAxisSize.min, | ||||||
|  |           children: [ | ||||||
|  |             // Header with embed info | ||||||
|  |             Container( | ||||||
|  |               padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), | ||||||
|  |               decoration: BoxDecoration( | ||||||
|  |                 color: colorScheme.surfaceContainerHigh, | ||||||
|  |                 border: Border( | ||||||
|  |                   bottom: BorderSide( | ||||||
|  |                     color: colorScheme.outline.withOpacity(0.2), | ||||||
|  |                     width: 1, | ||||||
|  |                   ), | ||||||
|  |                 ), | ||||||
|  |               ), | ||||||
|  |               child: Row( | ||||||
|  |                 children: [ | ||||||
|  |                   Icon( | ||||||
|  |                     embedView.renderer == PostEmbedViewRenderer.webView | ||||||
|  |                         ? Symbols.web | ||||||
|  |                         : Symbols.web, | ||||||
|  |                     size: 16, | ||||||
|  |                     color: colorScheme.primary, | ||||||
|  |                   ), | ||||||
|  |                   const SizedBox(width: 8), | ||||||
|  |                   Expanded( | ||||||
|  |                     child: Text( | ||||||
|  |                       _getUriDisplay(embedView.uri), | ||||||
|  |                       style: theme.textTheme.bodySmall?.copyWith( | ||||||
|  |                         color: colorScheme.onSurfaceVariant, | ||||||
|  |                       ), | ||||||
|  |                       maxLines: 1, | ||||||
|  |                       overflow: TextOverflow.ellipsis, | ||||||
|  |                     ), | ||||||
|  |                   ), | ||||||
|  |                   IconButton( | ||||||
|  |                     icon: Icon( | ||||||
|  |                       Symbols.open_in_new, | ||||||
|  |                       size: 16, | ||||||
|  |                       color: colorScheme.onSurfaceVariant, | ||||||
|  |                     ), | ||||||
|  |                     onPressed: () async { | ||||||
|  |                       final uri = Uri.parse(embedView.uri); | ||||||
|  |                       if (await canLaunchUrl(uri)) { | ||||||
|  |                         await launchUrl( | ||||||
|  |                           uri, | ||||||
|  |                           mode: LaunchMode.externalApplication, | ||||||
|  |                         ); | ||||||
|  |                       } | ||||||
|  |                     }, | ||||||
|  |                     padding: EdgeInsets.zero, | ||||||
|  |                     constraints: const BoxConstraints(), | ||||||
|  |                     visualDensity: VisualDensity.compact, | ||||||
|  |                     tooltip: 'Open in browser', | ||||||
|  |                   ), | ||||||
|  |                 ], | ||||||
|  |               ), | ||||||
|  |             ), | ||||||
|  |  | ||||||
|  |             // WebView content | ||||||
|  |             AspectRatio( | ||||||
|  |               aspectRatio: embedView.aspectRatio ?? 1, | ||||||
|  |               child: InAppWebView( | ||||||
|  |                 initialUrlRequest: URLRequest(url: WebUri(embedView.uri)), | ||||||
|  |                 initialSettings: InAppWebViewSettings( | ||||||
|  |                   javaScriptEnabled: true, | ||||||
|  |                   mediaPlaybackRequiresUserGesture: false, | ||||||
|  |                   allowsInlineMediaPlayback: true, | ||||||
|  |                   useShouldOverrideUrlLoading: true, | ||||||
|  |                   useOnLoadResource: true, | ||||||
|  |                   supportZoom: false, | ||||||
|  |                   useWideViewPort: false, | ||||||
|  |                   loadWithOverviewMode: true, | ||||||
|  |                   builtInZoomControls: false, | ||||||
|  |                   displayZoomControls: false, | ||||||
|  |                   minimumFontSize: 12, | ||||||
|  |                   preferredContentMode: UserPreferredContentMode.MOBILE, | ||||||
|  |                   allowsBackForwardNavigationGestures: false, | ||||||
|  |                   allowsLinkPreview: false, | ||||||
|  |                   isInspectable: false, | ||||||
|  |                   applicationNameForUserAgent: 'Solian/3.0', | ||||||
|  |                   userAgent: | ||||||
|  |                       'Mozilla/5.0 (Linux; Android 10; Mobile) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.120 Mobile Safari/537.36 Solian/3.0', | ||||||
|  |                 ), | ||||||
|  |                 onWebViewCreated: (controller) { | ||||||
|  |                   // Configure webview settings | ||||||
|  |                   controller.addJavaScriptHandler( | ||||||
|  |                     handlerName: 'onHeightChanged', | ||||||
|  |                     callback: (args) { | ||||||
|  |                       // Handle dynamic height changes if needed | ||||||
|  |                     }, | ||||||
|  |                   ); | ||||||
|  |                 }, | ||||||
|  |                 onLoadStart: (controller, url) { | ||||||
|  |                   // Handle load start | ||||||
|  |                 }, | ||||||
|  |                 onLoadStop: (controller, url) async { | ||||||
|  |                   // Inject CSS to improve mobile display and remove borders | ||||||
|  |                   await controller.evaluateJavascript( | ||||||
|  |                     source: ''' | ||||||
|  |                     // Remove unwanted elements | ||||||
|  |                     var elements = document.querySelectorAll('nav, header, footer, .ads, .advertisement, .sidebar'); | ||||||
|  |                     for (var i = 0; i < elements.length; i++) { | ||||||
|  |                       elements[i].style.display = 'none'; | ||||||
|  |                     } | ||||||
|  |              | ||||||
|  |                     // Remove borders from embedded content (YouTube, Vimeo, etc.) | ||||||
|  |                     var iframes = document.querySelectorAll('iframe'); | ||||||
|  |                     for (var i = 0; i < iframes.length; i++) { | ||||||
|  |                       iframes[i].style.border = 'none'; | ||||||
|  |                       iframes[i].style.borderRadius = '0'; | ||||||
|  |                     } | ||||||
|  |              | ||||||
|  |                     // Remove borders from video elements | ||||||
|  |                     var videos = document.querySelectorAll('video'); | ||||||
|  |                     for (var i = 0; i < videos.length; i++) { | ||||||
|  |                       videos[i].style.border = 'none'; | ||||||
|  |                       videos[i].style.borderRadius = '0'; | ||||||
|  |                     } | ||||||
|  |              | ||||||
|  |                     // Remove borders from any element that might have them | ||||||
|  |                     var allElements = document.querySelectorAll('*'); | ||||||
|  |                     for (var i = 0; i < allElements.length; i++) { | ||||||
|  |                       if (allElements[i].style.border) { | ||||||
|  |                         allElements[i].style.border = 'none'; | ||||||
|  |                       } | ||||||
|  |                     } | ||||||
|  |              | ||||||
|  |                     // Improve readability | ||||||
|  |                     var body = document.body; | ||||||
|  |                     body.style.fontSize = '14px'; | ||||||
|  |                     body.style.lineHeight = '1.4'; | ||||||
|  |                     body.style.margin = '0'; | ||||||
|  |                     body.style.padding = '0'; | ||||||
|  |              | ||||||
|  |                     // Handle dynamic content | ||||||
|  |                     var observer = new MutationObserver(function(mutations) { | ||||||
|  |                       // Remove borders from newly added elements | ||||||
|  |                       var newIframes = document.querySelectorAll('iframe'); | ||||||
|  |                       for (var i = 0; i < newIframes.length; i++) { | ||||||
|  |                         if (!newIframes[i].style.border || newIframes[i].style.border !== 'none') { | ||||||
|  |                           newIframes[i].style.border = 'none'; | ||||||
|  |                           newIframes[i].style.borderRadius = '0'; | ||||||
|  |                         } | ||||||
|  |                       } | ||||||
|  |                       var newVideos = document.querySelectorAll('video'); | ||||||
|  |                       for (var i = 0; i < newVideos.length; i++) { | ||||||
|  |                         if (!newVideos[i].style.border || newVideos[i].style.border !== 'none') { | ||||||
|  |                           newVideos[i].style.border = 'none'; | ||||||
|  |                           newVideos[i].style.borderRadius = '0'; | ||||||
|  |                         } | ||||||
|  |                       } | ||||||
|  |                       window.flutter_inappwebview.callHandler('onHeightChanged', document.body.scrollHeight); | ||||||
|  |                     }); | ||||||
|  |                     observer.observe(document.body, { childList: true, subtree: true }); | ||||||
|  |                   ''', | ||||||
|  |                   ); | ||||||
|  |                 }, | ||||||
|  |                 onLoadError: (controller, url, code, message) { | ||||||
|  |                   // Handle load errors | ||||||
|  |                 }, | ||||||
|  |                 onLoadHttpError: (controller, url, statusCode, description) { | ||||||
|  |                   // Handle HTTP errors | ||||||
|  |                 }, | ||||||
|  |                 shouldOverrideUrlLoading: (controller, navigationAction) async { | ||||||
|  |                   final uri = navigationAction.request.url; | ||||||
|  |                   if (uri != null && uri.toString() != embedView.uri) { | ||||||
|  |                     // Open external links in browser | ||||||
|  |                     // You might want to use url_launcher here | ||||||
|  |                     return NavigationActionPolicy.CANCEL; | ||||||
|  |                   } | ||||||
|  |                   return NavigationActionPolicy.ALLOW; | ||||||
|  |                 }, | ||||||
|  |                 onProgressChanged: (controller, progress) { | ||||||
|  |                   // Handle progress changes if needed | ||||||
|  |                 }, | ||||||
|  |                 onConsoleMessage: (controller, consoleMessage) { | ||||||
|  |                   // Handle console messages for debugging | ||||||
|  |                   debugPrint('WebView Console: ${consoleMessage.message}'); | ||||||
|  |                 }, | ||||||
|  |               ), | ||||||
|  |             ), | ||||||
|  |           ], | ||||||
|  |         ), | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   String _getUriDisplay(String uri) { | ||||||
|  |     try { | ||||||
|  |       final parsedUri = Uri.parse(uri); | ||||||
|  |       return parsedUri.host.isNotEmpty ? parsedUri.host : uri; | ||||||
|  |     } catch (e) { | ||||||
|  |       return uri; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -1,3 +1,4 @@ | |||||||
|  | import 'package:easy_localization/easy_localization.dart'; | ||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
| import 'package:flutter_hooks/flutter_hooks.dart'; | import 'package:flutter_hooks/flutter_hooks.dart'; | ||||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
| @@ -47,7 +48,7 @@ class PostFeaturedList extends HookConsumerWidget { | |||||||
|         'PostFeaturedList: isCollapsed changed to ${isCollapsed.value}', |         'PostFeaturedList: isCollapsed changed to ${isCollapsed.value}', | ||||||
|       ); |       ); | ||||||
|       return null; |       return null; | ||||||
|     }, [isCollapsed.value]); |     }, [isCollapsed]); | ||||||
|  |  | ||||||
|     useEffect(() { |     useEffect(() { | ||||||
|       if (featuredPostsAsync.hasValue && featuredPostsAsync.value!.isNotEmpty) { |       if (featuredPostsAsync.hasValue && featuredPostsAsync.value!.isNotEmpty) { | ||||||
| @@ -92,7 +93,7 @@ class PostFeaturedList extends HookConsumerWidget { | |||||||
|         ); |         ); | ||||||
|       } |       } | ||||||
|       return null; |       return null; | ||||||
|     }, [featuredPostsAsync.value]); |     }, [featuredPostsAsync]); | ||||||
|  |  | ||||||
|     return ClipRRect( |     return ClipRRect( | ||||||
|       borderRadius: const BorderRadius.all(Radius.circular(8)), |       borderRadius: const BorderRadius.all(Radius.circular(8)), | ||||||
| @@ -105,7 +106,7 @@ class PostFeaturedList extends HookConsumerWidget { | |||||||
|               spacing: 8, |               spacing: 8, | ||||||
|               children: [ |               children: [ | ||||||
|                 const Icon(Symbols.highlight), |                 const Icon(Symbols.highlight), | ||||||
|                 Text('Highlight Posts'), |                 const Text('highlightPost').tr(), | ||||||
|                 Spacer(), |                 Spacer(), | ||||||
|                 IconButton( |                 IconButton( | ||||||
|                   padding: EdgeInsets.zero, |                   padding: EdgeInsets.zero, | ||||||
|   | |||||||
| @@ -18,8 +18,10 @@ import 'package:island/screens/posts/compose.dart'; | |||||||
| import 'package:island/widgets/alert.dart'; | import 'package:island/widgets/alert.dart'; | ||||||
| import 'package:island/widgets/content/markdown.dart'; | import 'package:island/widgets/content/markdown.dart'; | ||||||
| import 'package:island/widgets/post/post_item_screenshot.dart'; | import 'package:island/widgets/post/post_item_screenshot.dart'; | ||||||
|  | import 'package:island/widgets/post/post_award_sheet.dart'; | ||||||
| import 'package:island/widgets/post/post_pin_sheet.dart'; | import 'package:island/widgets/post/post_pin_sheet.dart'; | ||||||
| import 'package:island/widgets/post/post_shared.dart'; | import 'package:island/widgets/post/post_shared.dart'; | ||||||
|  | import 'package:island/widgets/post/embed_view_renderer.dart'; | ||||||
| import 'package:island/widgets/safety/abuse_report_helper.dart'; | import 'package:island/widgets/safety/abuse_report_helper.dart'; | ||||||
| import 'package:island/widgets/share/share_sheet.dart'; | import 'package:island/widgets/share/share_sheet.dart'; | ||||||
| import 'package:material_symbols_icons/symbols.dart'; | import 'package:material_symbols_icons/symbols.dart'; | ||||||
| @@ -36,6 +38,7 @@ class PostActionableItem extends HookConsumerWidget { | |||||||
|   final bool isShowReference; |   final bool isShowReference; | ||||||
|   final bool isEmbedReply; |   final bool isEmbedReply; | ||||||
|   final bool isEmbedOpenable; |   final bool isEmbedOpenable; | ||||||
|  |   final bool isCompact; | ||||||
|   final double? borderRadius; |   final double? borderRadius; | ||||||
|   final VoidCallback? onRefresh; |   final VoidCallback? onRefresh; | ||||||
|   final Function(SnPost)? onUpdate; |   final Function(SnPost)? onUpdate; | ||||||
| @@ -48,6 +51,7 @@ class PostActionableItem extends HookConsumerWidget { | |||||||
|     this.isShowReference = true, |     this.isShowReference = true, | ||||||
|     this.isEmbedReply = true, |     this.isEmbedReply = true, | ||||||
|     this.isEmbedOpenable = false, |     this.isEmbedOpenable = false, | ||||||
|  |     this.isCompact = false, | ||||||
|     this.borderRadius, |     this.borderRadius, | ||||||
|     this.onRefresh, |     this.onRefresh, | ||||||
|     this.onUpdate, |     this.onUpdate, | ||||||
| @@ -76,6 +80,7 @@ class PostActionableItem extends HookConsumerWidget { | |||||||
|         isEmbedReply: isEmbedReply, |         isEmbedReply: isEmbedReply, | ||||||
|         isEmbedOpenable: isEmbedOpenable, |         isEmbedOpenable: isEmbedOpenable, | ||||||
|         isTextSelectable: false, |         isTextSelectable: false, | ||||||
|  |         isCompact: isCompact, | ||||||
|         onRefresh: onRefresh, |         onRefresh: onRefresh, | ||||||
|         onUpdate: onUpdate, |         onUpdate: onUpdate, | ||||||
|         onOpen: onOpen, |         onOpen: onOpen, | ||||||
| @@ -242,6 +247,18 @@ class PostActionableItem extends HookConsumerWidget { | |||||||
|                   ); |                   ); | ||||||
|                 }, |                 }, | ||||||
|               ), |               ), | ||||||
|  |             MenuAction( | ||||||
|  |               title: 'award'.tr(), | ||||||
|  |               image: MenuImage.icon(Symbols.star), | ||||||
|  |               callback: () { | ||||||
|  |                 showModalBottomSheet( | ||||||
|  |                   context: context, | ||||||
|  |                   isScrollControlled: true, | ||||||
|  |                   useRootNavigator: true, | ||||||
|  |                   builder: (context) => PostAwardSheet(post: item), | ||||||
|  |                 ); | ||||||
|  |               }, | ||||||
|  |             ), | ||||||
|             MenuSeparator(), |             MenuSeparator(), | ||||||
|             MenuAction( |             MenuAction( | ||||||
|               title: 'share'.tr(), |               title: 'share'.tr(), | ||||||
| @@ -298,6 +315,7 @@ class PostItem extends HookConsumerWidget { | |||||||
|   final bool isEmbedOpenable; |   final bool isEmbedOpenable; | ||||||
|   final bool isTextSelectable; |   final bool isTextSelectable; | ||||||
|   final bool isTranslatable; |   final bool isTranslatable; | ||||||
|  |   final bool isCompact; | ||||||
|   final VoidCallback? onRefresh; |   final VoidCallback? onRefresh; | ||||||
|   final Function(SnPost)? onUpdate; |   final Function(SnPost)? onUpdate; | ||||||
|   final VoidCallback? onOpen; |   final VoidCallback? onOpen; | ||||||
| @@ -311,6 +329,7 @@ class PostItem extends HookConsumerWidget { | |||||||
|     this.isEmbedOpenable = false, |     this.isEmbedOpenable = false, | ||||||
|     this.isTextSelectable = true, |     this.isTextSelectable = true, | ||||||
|     this.isTranslatable = true, |     this.isTranslatable = true, | ||||||
|  |     this.isCompact = false, | ||||||
|     this.onRefresh, |     this.onRefresh, | ||||||
|     this.onUpdate, |     this.onUpdate, | ||||||
|     this.onOpen, |     this.onOpen, | ||||||
| @@ -340,7 +359,14 @@ class PostItem extends HookConsumerWidget { | |||||||
|             final delta = isRemoving ? -1 : 1; |             final delta = isRemoving ? -1 : 1; | ||||||
|             final reactionsCount = Map<String, int>.from(item.reactionsCount); |             final reactionsCount = Map<String, int>.from(item.reactionsCount); | ||||||
|             reactionsCount[symbol] = (reactionsCount[symbol] ?? 0) + delta; |             reactionsCount[symbol] = (reactionsCount[symbol] ?? 0) + delta; | ||||||
|             onUpdate?.call(item.copyWith(reactionsCount: reactionsCount)); |             final reactionsMade = Map<String, bool>.from(item.reactionsMade); | ||||||
|  |             reactionsMade[symbol] = delta == 1 ? true : false; | ||||||
|  |             onUpdate?.call( | ||||||
|  |               item.copyWith( | ||||||
|  |                 reactionsCount: reactionsCount, | ||||||
|  |                 reactionsMade: reactionsMade, | ||||||
|  |               ), | ||||||
|  |             ); | ||||||
|             HapticFeedback.heavyImpact(); |             HapticFeedback.heavyImpact(); | ||||||
|           }); |           }); | ||||||
|       reacting.value = false; |       reacting.value = false; | ||||||
| @@ -458,54 +484,64 @@ class PostItem extends HookConsumerWidget { | |||||||
|         PostHeader( |         PostHeader( | ||||||
|           item: item, |           item: item, | ||||||
|           isFullPost: isFullPost, |           isFullPost: isFullPost, | ||||||
|  |           isCompact: isCompact, | ||||||
|           renderingPadding: renderingPadding, |           renderingPadding: renderingPadding, | ||||||
|           trailing: IconButton( |           trailing: | ||||||
|             icon: |               isCompact | ||||||
|                 mostReaction == null |                   ? null | ||||||
|                     ? const Icon(Symbols.add_reaction) |                   : IconButton( | ||||||
|                     : Badge( |                     icon: | ||||||
|                       label: Center( |                         mostReaction == null | ||||||
|                         child: Text( |                             ? const Icon(Symbols.add_reaction) | ||||||
|                           'x${item.reactionsCount[mostReaction]}', |                             : Badge( | ||||||
|                           style: const TextStyle(fontSize: 11), |                               label: Center( | ||||||
|                           textAlign: TextAlign.center, |                                 child: Text( | ||||||
|                         ), |                                   'x${item.reactionsCount[mostReaction]}', | ||||||
|                       ), |                                   style: const TextStyle(fontSize: 11), | ||||||
|                       offset: const Offset(4, 20), |                                   textAlign: TextAlign.center, | ||||||
|                       backgroundColor: Theme.of( |                                 ), | ||||||
|                         context, |                               ), | ||||||
|                       ).colorScheme.primary.withOpacity(0.75), |                               offset: const Offset(4, 20), | ||||||
|                       textColor: Theme.of(context).colorScheme.onPrimary, |                               backgroundColor: Theme.of( | ||||||
|                       child: Text( |                                 context, | ||||||
|                         kReactionTemplates[mostReaction]?.icon ?? '', |                               ).colorScheme.primary.withOpacity(0.75), | ||||||
|                         style: const TextStyle(fontSize: 20), |                               textColor: | ||||||
|  |                                   Theme.of(context).colorScheme.onPrimary, | ||||||
|  |                               child: Text( | ||||||
|  |                                 kReactionTemplates[mostReaction]?.icon ?? '', | ||||||
|  |                                 style: const TextStyle(fontSize: 20), | ||||||
|  |                               ), | ||||||
|  |                             ), | ||||||
|  |                     style: ButtonStyle( | ||||||
|  |                       backgroundColor: WidgetStatePropertyAll( | ||||||
|  |                         (item.reactionsMade[mostReaction] ?? false) | ||||||
|  |                             ? Theme.of( | ||||||
|  |                               context, | ||||||
|  |                             ).colorScheme.primary.withOpacity(0.5) | ||||||
|  |                             : null, | ||||||
|                       ), |                       ), | ||||||
|                     ), |                     ), | ||||||
|             style: ButtonStyle( |                     onPressed: () { | ||||||
|               backgroundColor: WidgetStatePropertyAll( |                       showModalBottomSheet( | ||||||
|                 (item.reactionsMade[mostReaction] ?? false) |                         context: context, | ||||||
|                     ? Theme.of(context).colorScheme.primary.withOpacity(0.5) |                         useRootNavigator: true, | ||||||
|                     : null, |                         builder: (BuildContext context) { | ||||||
|               ), |                           return _PostReactionSheet( | ||||||
|             ), |                             reactionsCount: item.reactionsCount, | ||||||
|             onPressed: () { |                             reactionsMade: item.reactionsMade, | ||||||
|               showModalBottomSheet( |                             onReact: (symbol, attitude) { | ||||||
|                 context: context, |                               reactPost(symbol, attitude); | ||||||
|                 useRootNavigator: true, |                             }, | ||||||
|                 builder: (BuildContext context) { |                           ); | ||||||
|                   return _PostReactionSheet( |                         }, | ||||||
|                     reactionsCount: item.reactionsCount, |                       ); | ||||||
|                     reactionsMade: item.reactionsMade, |  | ||||||
|                     onReact: (symbol, attitude) { |  | ||||||
|                       reactPost(symbol, attitude); |  | ||||||
|                     }, |                     }, | ||||||
|                   ); |                     padding: EdgeInsets.zero, | ||||||
|                 }, |                     visualDensity: const VisualDensity( | ||||||
|               ); |                       horizontal: -3, | ||||||
|             }, |                       vertical: -3, | ||||||
|             padding: EdgeInsets.zero, |                     ), | ||||||
|             visualDensity: const VisualDensity(horizontal: -3, vertical: -3), |                   ), | ||||||
|           ), |  | ||||||
|         ), |         ), | ||||||
|         PostBody( |         PostBody( | ||||||
|           item: item, |           item: item, | ||||||
| @@ -514,6 +550,12 @@ class PostItem extends HookConsumerWidget { | |||||||
|           translationSection: translationSection, |           translationSection: translationSection, | ||||||
|           renderingPadding: renderingPadding, |           renderingPadding: renderingPadding, | ||||||
|         ), |         ), | ||||||
|  |         if (item.embedView != null) | ||||||
|  |           EmbedViewRenderer( | ||||||
|  |             embedView: item.embedView!, | ||||||
|  |             maxHeight: 400, | ||||||
|  |             borderRadius: BorderRadius.circular(12), | ||||||
|  |           ).padding(horizontal: renderingPadding.horizontal, vertical: 8), | ||||||
|         if (isShowReference) |         if (isShowReference) | ||||||
|           ReferencedPostWidget(item: item, renderingPadding: renderingPadding), |           ReferencedPostWidget(item: item, renderingPadding: renderingPadding), | ||||||
|         if (item.repliesCount > 0 && isEmbedReply) |         if (item.repliesCount > 0 && isEmbedReply) | ||||||
|   | |||||||
| @@ -10,6 +10,7 @@ import 'package:island/widgets/alert.dart'; | |||||||
| import 'package:island/widgets/post/post_item.dart'; | import 'package:island/widgets/post/post_item.dart'; | ||||||
| import 'package:island/widgets/post/post_shared.dart'; | import 'package:island/widgets/post/post_shared.dart'; | ||||||
| import 'package:material_symbols_icons/symbols.dart'; | import 'package:material_symbols_icons/symbols.dart'; | ||||||
|  | import 'package:styled_widget/styled_widget.dart'; | ||||||
| import 'package:super_context_menu/super_context_menu.dart'; | import 'package:super_context_menu/super_context_menu.dart'; | ||||||
|  |  | ||||||
| class PostItemCreator extends HookConsumerWidget { | class PostItemCreator extends HookConsumerWidget { | ||||||
| @@ -33,7 +34,7 @@ class PostItemCreator extends HookConsumerWidget { | |||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context, WidgetRef ref) { |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|     final renderingPadding = |     final renderingPadding = | ||||||
|         padding ?? const EdgeInsets.symmetric(horizontal: 16, vertical: 16); |         padding ?? const EdgeInsets.symmetric(horizontal: 8, vertical: 8); | ||||||
|  |  | ||||||
|     return ContextMenuWidget( |     return ContextMenuWidget( | ||||||
|       menuProvider: (_) { |       menuProvider: (_) { | ||||||
| @@ -97,18 +98,22 @@ class PostItemCreator extends HookConsumerWidget { | |||||||
|               context.goNamed('postDetail', pathParameters: {'id': item.id}); |               context.goNamed('postDetail', pathParameters: {'id': item.id}); | ||||||
|             } |             } | ||||||
|           }, |           }, | ||||||
|           child: Padding( |           child: Column( | ||||||
|             padding: renderingPadding, |             crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|             child: Column( |             children: [ | ||||||
|               crossAxisAlignment: CrossAxisAlignment.start, |               Gap(renderingPadding.vertical), | ||||||
|               children: [ |               PostHeader(item: item, renderingPadding: renderingPadding), | ||||||
|                 PostHeader(item: item), |               PostBody(item: item, renderingPadding: renderingPadding), | ||||||
|                 PostBody(item: item), |               ReferencedPostWidget( | ||||||
|                 ReferencedPostWidget(item: item), |                 item: item, | ||||||
|                 const Gap(16), |                 renderingPadding: renderingPadding, | ||||||
|                 _buildAnalyticsSection(context), |               ), | ||||||
|               ], |               const Gap(16), | ||||||
|             ), |               _buildAnalyticsSection( | ||||||
|  |                 context, | ||||||
|  |               ).padding(horizontal: renderingPadding.horizontal), | ||||||
|  |               Gap(renderingPadding.vertical), | ||||||
|  |             ], | ||||||
|           ), |           ), | ||||||
|         ), |         ), | ||||||
|       ), |       ), | ||||||
|   | |||||||
| @@ -532,6 +532,7 @@ class PostHeader extends StatelessWidget { | |||||||
|   final bool isInteractive; |   final bool isInteractive; | ||||||
|   final EdgeInsets renderingPadding; |   final EdgeInsets renderingPadding; | ||||||
|   final bool isRelativeTime; |   final bool isRelativeTime; | ||||||
|  |   final bool isCompact; | ||||||
|  |  | ||||||
|   const PostHeader({ |   const PostHeader({ | ||||||
|     super.key, |     super.key, | ||||||
| @@ -541,6 +542,7 @@ class PostHeader extends StatelessWidget { | |||||||
|     this.isInteractive = true, |     this.isInteractive = true, | ||||||
|     this.renderingPadding = EdgeInsets.zero, |     this.renderingPadding = EdgeInsets.zero, | ||||||
|     this.isRelativeTime = true, |     this.isRelativeTime = true, | ||||||
|  |     this.isCompact = false, | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
| @@ -584,11 +586,27 @@ class PostHeader extends StatelessWidget { | |||||||
|                     crossAxisAlignment: CrossAxisAlignment.center, |                     crossAxisAlignment: CrossAxisAlignment.center, | ||||||
|                     spacing: 4, |                     spacing: 4, | ||||||
|                     children: [ |                     children: [ | ||||||
|                       Text(item.publisher.nick).bold(), |                       Flexible( | ||||||
|  |                         child: | ||||||
|  |                             Text( | ||||||
|  |                               item.publisher.nick, | ||||||
|  |                               maxLines: 1, | ||||||
|  |                               overflow: TextOverflow.ellipsis, | ||||||
|  |                             ).bold(), | ||||||
|  |                       ), | ||||||
|                       if (item.publisher.verification != null) |                       if (item.publisher.verification != null) | ||||||
|                         VerificationMark(mark: item.publisher.verification!), |                         VerificationMark(mark: item.publisher.verification!), | ||||||
|                       if (item.realm == null) |                       if (item.realm == null) | ||||||
|                         Text('@${item.publisher.name}').fontSize(11) |                         Flexible( | ||||||
|  |                           child: | ||||||
|  |                               isCompact | ||||||
|  |                                   ? const SizedBox.shrink() | ||||||
|  |                                   : Text( | ||||||
|  |                                     '@${item.publisher.name}', | ||||||
|  |                                     maxLines: 1, | ||||||
|  |                                     overflow: TextOverflow.ellipsis, | ||||||
|  |                                   ).fontSize(11), | ||||||
|  |                         ) | ||||||
|                       else |                       else | ||||||
|                         ...([ |                         ...([ | ||||||
|                           const Icon(Symbols.arrow_right, size: 14), |                           const Icon(Symbols.arrow_right, size: 14), | ||||||
| @@ -762,7 +780,9 @@ class PostBody extends ConsumerWidget { | |||||||
|                   ).padding(bottom: 4), |                   ).padding(bottom: 4), | ||||||
|                 MarkdownTextContent( |                 MarkdownTextContent( | ||||||
|                   content: |                   content: | ||||||
|                       item.isTruncated ? '${item.content!}...' : item.content!, |                       item.isTruncated | ||||||
|  |                           ? '${item.content!}...' | ||||||
|  |                           : item.content ?? '', | ||||||
|                   isSelectable: isTextSelectable, |                   isSelectable: isTextSelectable, | ||||||
|                 ), |                 ), | ||||||
|                 if (translationSection != null) translationSection!, |                 if (translationSection != null) translationSection!, | ||||||
| @@ -880,6 +900,7 @@ class PostBody extends ConsumerWidget { | |||||||
|                               poll: SnPollWithStats.fromJson(embedData['poll']), |                               poll: SnPollWithStats.fromJson(embedData['poll']), | ||||||
|                               onSubmit: (_) {}, |                               onSubmit: (_) {}, | ||||||
|                               isReadonly: !isInteractive, |                               isReadonly: !isInteractive, | ||||||
|  |                               isInitiallyExpanded: isFullPost, | ||||||
|                             ).padding(horizontal: 16, vertical: 12), |                             ).padding(horizontal: 16, vertical: 12), | ||||||
|                   ), |                   ), | ||||||
|                   _ => Text('Unable show embed: ${embedData['type']}'), |                   _ => Text('Unable show embed: ${embedData['type']}'), | ||||||
|   | |||||||
| @@ -25,7 +25,7 @@ class PostShuffleScreen extends HookConsumerWidget { | |||||||
|       return cardSwiperController.dispose; |       return cardSwiperController.dispose; | ||||||
|     }, []); |     }, []); | ||||||
|  |  | ||||||
|     const kBottomControlHeight = 64.0; |     const kBottomControlHeight = 80.0; | ||||||
|  |  | ||||||
|     return AppScaffold( |     return AppScaffold( | ||||||
|       appBar: AppBar(title: const Text('postShuffle').tr()), |       appBar: AppBar(title: const Text('postShuffle').tr()), | ||||||
| @@ -36,45 +36,50 @@ class PostShuffleScreen extends HookConsumerWidget { | |||||||
|               bottom: |               bottom: | ||||||
|                   kBottomControlHeight + MediaQuery.of(context).padding.bottom, |                   kBottomControlHeight + MediaQuery.of(context).padding.bottom, | ||||||
|             ), |             ), | ||||||
|             child: |             child: Builder( | ||||||
|                 (postListState.value?.items.length ?? 0) > 0 |               key: ValueKey(postListState.value?.items.length ?? 0), | ||||||
|                     ? CardSwiper( |               builder: (context) { | ||||||
|                       controller: cardSwiperController, |                 if ((postListState.value?.items.length ?? 0) > 0) { | ||||||
|                       cardsCount: postListState.value!.items.length, |                   return CardSwiper( | ||||||
|                       cardBuilder: ( |                     controller: cardSwiperController, | ||||||
|                         context, |                     cardsCount: postListState.value!.items.length, | ||||||
|                         index, |                     isLoop: false, | ||||||
|                         horizontalOffsetPercentage, |                     cardBuilder: ( | ||||||
|                         verticalOffsetPercentage, |                       context, | ||||||
|                       ) { |                       index, | ||||||
|                         return Center( |                       horizontalOffsetPercentage, | ||||||
|                           child: ConstrainedBox( |                       verticalOffsetPercentage, | ||||||
|                             constraints: BoxConstraints(maxWidth: 540), |                     ) { | ||||||
|                             child: SingleChildScrollView( |                       return Center( | ||||||
|                               child: Card( |                         child: ConstrainedBox( | ||||||
|                                 margin: EdgeInsets.zero, |                           constraints: BoxConstraints(maxWidth: 540), | ||||||
|                                 child: ClipRRect( |                           child: SingleChildScrollView( | ||||||
|                                   borderRadius: const BorderRadius.all( |                             child: Card( | ||||||
|                                     Radius.circular(8), |                               margin: EdgeInsets.zero, | ||||||
|                                   ), |                               child: ClipRRect( | ||||||
|                                   child: PostActionableItem( |                                 borderRadius: const BorderRadius.all( | ||||||
|                                     item: postListState.value!.items[index], |                                   Radius.circular(8), | ||||||
|                                   ), |                                 ), | ||||||
|  |                                 child: PostActionableItem( | ||||||
|  |                                   item: postListState.value!.items[index], | ||||||
|                                 ), |                                 ), | ||||||
|                               ), |                               ), | ||||||
|                             ), |                             ), | ||||||
|                           ), |                           ), | ||||||
|                         ); |                         ), | ||||||
|                       }, |                       ); | ||||||
|                       onEnd: () { |                     }, | ||||||
|                         if (postListState.value?.hasMore ?? true) { |                     onEnd: () async { | ||||||
|                           postListNotifier.fetch( |                       if (postListState.value?.hasMore ?? true) { | ||||||
|                             cursor: postListState.value?.nextCursor, |                         postListNotifier.forceRefresh(); | ||||||
|                           ); |                       } | ||||||
|                         } |                     }, | ||||||
|                       }, |                   ); | ||||||
|                     ) |                 } else { | ||||||
|                     : Center(child: CircularProgressIndicator()), |                   return Center(child: CircularProgressIndicator()); | ||||||
|  |                 } | ||||||
|  |               }, | ||||||
|  |             ), | ||||||
|           ), |           ), | ||||||
|           Positioned( |           Positioned( | ||||||
|             left: 0, |             left: 0, | ||||||
|   | |||||||
| @@ -12,6 +12,7 @@ import 'package:material_symbols_icons/symbols.dart'; | |||||||
| import 'package:riverpod_annotation/riverpod_annotation.dart'; | import 'package:riverpod_annotation/riverpod_annotation.dart'; | ||||||
| import 'package:styled_widget/styled_widget.dart'; | import 'package:styled_widget/styled_widget.dart'; | ||||||
| import 'package:flutter_popup_card/flutter_popup_card.dart'; | import 'package:flutter_popup_card/flutter_popup_card.dart'; | ||||||
|  | import 'package:island/widgets/extended_refresh_indicator.dart'; | ||||||
|  |  | ||||||
| part 'picker.g.dart'; | part 'picker.g.dart'; | ||||||
|  |  | ||||||
| @@ -208,7 +209,7 @@ class _PackSwitcherState extends State<_PackSwitcher> { | |||||||
|  |  | ||||||
|         // Content |         // Content | ||||||
|         Expanded( |         Expanded( | ||||||
|           child: RefreshIndicator( |           child: ExtendedRefreshIndicator( | ||||||
|             onRefresh: widget.onRefresh, |             onRefresh: widget.onRefresh, | ||||||
|             child: _StickersGrid( |             child: _StickersGrid( | ||||||
|               pack: selectedPack, |               pack: selectedPack, | ||||||
|   | |||||||
| @@ -22,6 +22,7 @@ | |||||||
| #include <record_linux/record_linux_plugin.h> | #include <record_linux/record_linux_plugin.h> | ||||||
| #include <sqlite3_flutter_libs/sqlite3_flutter_libs_plugin.h> | #include <sqlite3_flutter_libs/sqlite3_flutter_libs_plugin.h> | ||||||
| #include <super_native_extensions/super_native_extensions_plugin.h> | #include <super_native_extensions/super_native_extensions_plugin.h> | ||||||
|  | #include <tray_manager/tray_manager_plugin.h> | ||||||
| #include <url_launcher_linux/url_launcher_plugin.h> | #include <url_launcher_linux/url_launcher_plugin.h> | ||||||
| #include <volume_controller/volume_controller_plugin.h> | #include <volume_controller/volume_controller_plugin.h> | ||||||
|  |  | ||||||
| @@ -74,6 +75,9 @@ void fl_register_plugins(FlPluginRegistry* registry) { | |||||||
|   g_autoptr(FlPluginRegistrar) super_native_extensions_registrar = |   g_autoptr(FlPluginRegistrar) super_native_extensions_registrar = | ||||||
|       fl_plugin_registry_get_registrar_for_plugin(registry, "SuperNativeExtensionsPlugin"); |       fl_plugin_registry_get_registrar_for_plugin(registry, "SuperNativeExtensionsPlugin"); | ||||||
|   super_native_extensions_plugin_register_with_registrar(super_native_extensions_registrar); |   super_native_extensions_plugin_register_with_registrar(super_native_extensions_registrar); | ||||||
|  |   g_autoptr(FlPluginRegistrar) tray_manager_registrar = | ||||||
|  |       fl_plugin_registry_get_registrar_for_plugin(registry, "TrayManagerPlugin"); | ||||||
|  |   tray_manager_plugin_register_with_registrar(tray_manager_registrar); | ||||||
|   g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = |   g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = | ||||||
|       fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); |       fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); | ||||||
|   url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); |   url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); | ||||||
|   | |||||||
| @@ -19,6 +19,7 @@ list(APPEND FLUTTER_PLUGIN_LIST | |||||||
|   record_linux |   record_linux | ||||||
|   sqlite3_flutter_libs |   sqlite3_flutter_libs | ||||||
|   super_native_extensions |   super_native_extensions | ||||||
|  |   tray_manager | ||||||
|   url_launcher_linux |   url_launcher_linux | ||||||
|   volume_controller |   volume_controller | ||||||
| ) | ) | ||||||
|   | |||||||
| @@ -16,6 +16,7 @@ import firebase_core | |||||||
| import firebase_crashlytics | import firebase_crashlytics | ||||||
| import firebase_messaging | import firebase_messaging | ||||||
| import flutter_inappwebview_macos | import flutter_inappwebview_macos | ||||||
|  | import flutter_local_notifications | ||||||
| import flutter_platform_alert | import flutter_platform_alert | ||||||
| import flutter_secure_storage_macos | import flutter_secure_storage_macos | ||||||
| import flutter_timezone | import flutter_timezone | ||||||
| @@ -37,6 +38,7 @@ import sign_in_with_apple | |||||||
| import sqflite_darwin | import sqflite_darwin | ||||||
| import sqlite3_flutter_libs | import sqlite3_flutter_libs | ||||||
| import super_native_extensions | import super_native_extensions | ||||||
|  | import tray_manager | ||||||
| import url_launcher_macos | import url_launcher_macos | ||||||
| import volume_controller | import volume_controller | ||||||
| import wakelock_plus | import wakelock_plus | ||||||
| @@ -53,6 +55,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { | |||||||
|   FLTFirebaseCrashlyticsPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCrashlyticsPlugin")) |   FLTFirebaseCrashlyticsPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCrashlyticsPlugin")) | ||||||
|   FLTFirebaseMessagingPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseMessagingPlugin")) |   FLTFirebaseMessagingPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseMessagingPlugin")) | ||||||
|   InAppWebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "InAppWebViewFlutterPlugin")) |   InAppWebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "InAppWebViewFlutterPlugin")) | ||||||
|  |   FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin")) | ||||||
|   FlutterPlatformAlertPlugin.register(with: registry.registrar(forPlugin: "FlutterPlatformAlertPlugin")) |   FlutterPlatformAlertPlugin.register(with: registry.registrar(forPlugin: "FlutterPlatformAlertPlugin")) | ||||||
|   FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin")) |   FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin")) | ||||||
|   FlutterTimezonePlugin.register(with: registry.registrar(forPlugin: "FlutterTimezonePlugin")) |   FlutterTimezonePlugin.register(with: registry.registrar(forPlugin: "FlutterTimezonePlugin")) | ||||||
| @@ -74,6 +77,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { | |||||||
|   SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) |   SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) | ||||||
|   Sqlite3FlutterLibsPlugin.register(with: registry.registrar(forPlugin: "Sqlite3FlutterLibsPlugin")) |   Sqlite3FlutterLibsPlugin.register(with: registry.registrar(forPlugin: "Sqlite3FlutterLibsPlugin")) | ||||||
|   SuperNativeExtensionsPlugin.register(with: registry.registrar(forPlugin: "SuperNativeExtensionsPlugin")) |   SuperNativeExtensionsPlugin.register(with: registry.registrar(forPlugin: "SuperNativeExtensionsPlugin")) | ||||||
|  |   TrayManagerPlugin.register(with: registry.registrar(forPlugin: "TrayManagerPlugin")) | ||||||
|   UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) |   UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) | ||||||
|   VolumeControllerPlugin.register(with: registry.registrar(forPlugin: "VolumeControllerPlugin")) |   VolumeControllerPlugin.register(with: registry.registrar(forPlugin: "VolumeControllerPlugin")) | ||||||
|   WakelockPlusMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockPlusMacosPlugin")) |   WakelockPlusMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockPlusMacosPlugin")) | ||||||
|   | |||||||
| @@ -13,85 +13,85 @@ PODS: | |||||||
|     - FlutterMacOS |     - FlutterMacOS | ||||||
|   - file_selector_macos (0.0.1): |   - file_selector_macos (0.0.1): | ||||||
|     - FlutterMacOS |     - FlutterMacOS | ||||||
|   - Firebase/CoreOnly (12.0.0): |   - Firebase/CoreOnly (12.2.0): | ||||||
|     - FirebaseCore (~> 12.0.0) |     - FirebaseCore (~> 12.2.0) | ||||||
|   - Firebase/Crashlytics (12.0.0): |   - Firebase/Crashlytics (12.2.0): | ||||||
|     - Firebase/CoreOnly |     - Firebase/CoreOnly | ||||||
|     - FirebaseCrashlytics (~> 12.0.0) |     - FirebaseCrashlytics (~> 12.2.0) | ||||||
|   - Firebase/Messaging (12.0.0): |   - Firebase/Messaging (12.2.0): | ||||||
|     - Firebase/CoreOnly |     - Firebase/CoreOnly | ||||||
|     - FirebaseMessaging (~> 12.0.0) |     - FirebaseMessaging (~> 12.2.0) | ||||||
|   - firebase_analytics (12.0.0): |   - firebase_analytics (12.0.1): | ||||||
|     - firebase_core |     - firebase_core | ||||||
|     - FirebaseAnalytics (= 12.0.0) |     - FirebaseAnalytics (= 12.2.0) | ||||||
|     - FlutterMacOS |     - FlutterMacOS | ||||||
|   - firebase_core (4.0.0): |   - firebase_core (4.1.0): | ||||||
|     - Firebase/CoreOnly (~> 12.0.0) |     - Firebase/CoreOnly (~> 12.2.0) | ||||||
|     - FlutterMacOS |     - FlutterMacOS | ||||||
|   - firebase_crashlytics (5.0.0): |   - firebase_crashlytics (5.0.1): | ||||||
|     - Firebase/CoreOnly (~> 12.0.0) |     - Firebase/CoreOnly (~> 12.2.0) | ||||||
|     - Firebase/Crashlytics (~> 12.0.0) |     - Firebase/Crashlytics (~> 12.2.0) | ||||||
|     - firebase_core |     - firebase_core | ||||||
|     - FlutterMacOS |     - FlutterMacOS | ||||||
|   - firebase_messaging (16.0.0): |   - firebase_messaging (16.0.1): | ||||||
|     - Firebase/CoreOnly (~> 12.0.0) |     - Firebase/CoreOnly (~> 12.2.0) | ||||||
|     - Firebase/Messaging (~> 12.0.0) |     - Firebase/Messaging (~> 12.2.0) | ||||||
|     - firebase_core |     - firebase_core | ||||||
|     - FlutterMacOS |     - FlutterMacOS | ||||||
|   - FirebaseAnalytics (12.0.0): |   - FirebaseAnalytics (12.2.0): | ||||||
|     - FirebaseAnalytics/Default (= 12.0.0) |     - FirebaseAnalytics/Default (= 12.2.0) | ||||||
|     - FirebaseCore (~> 12.0.0) |     - FirebaseCore (~> 12.2.0) | ||||||
|     - FirebaseInstallations (~> 12.0.0) |     - FirebaseInstallations (~> 12.2.0) | ||||||
|     - GoogleUtilities/AppDelegateSwizzler (~> 8.1) |     - GoogleUtilities/AppDelegateSwizzler (~> 8.1) | ||||||
|     - GoogleUtilities/MethodSwizzler (~> 8.1) |     - GoogleUtilities/MethodSwizzler (~> 8.1) | ||||||
|     - GoogleUtilities/Network (~> 8.1) |     - GoogleUtilities/Network (~> 8.1) | ||||||
|     - "GoogleUtilities/NSData+zlib (~> 8.1)" |     - "GoogleUtilities/NSData+zlib (~> 8.1)" | ||||||
|     - nanopb (~> 3.30910.0) |     - nanopb (~> 3.30910.0) | ||||||
|   - FirebaseAnalytics/Default (12.0.0): |   - FirebaseAnalytics/Default (12.2.0): | ||||||
|     - FirebaseCore (~> 12.0.0) |     - FirebaseCore (~> 12.2.0) | ||||||
|     - FirebaseInstallations (~> 12.0.0) |     - FirebaseInstallations (~> 12.2.0) | ||||||
|     - GoogleAppMeasurement/Default (= 12.0.0) |     - GoogleAppMeasurement/Default (= 12.2.0) | ||||||
|     - GoogleUtilities/AppDelegateSwizzler (~> 8.1) |     - GoogleUtilities/AppDelegateSwizzler (~> 8.1) | ||||||
|     - GoogleUtilities/MethodSwizzler (~> 8.1) |     - GoogleUtilities/MethodSwizzler (~> 8.1) | ||||||
|     - GoogleUtilities/Network (~> 8.1) |     - GoogleUtilities/Network (~> 8.1) | ||||||
|     - "GoogleUtilities/NSData+zlib (~> 8.1)" |     - "GoogleUtilities/NSData+zlib (~> 8.1)" | ||||||
|     - nanopb (~> 3.30910.0) |     - nanopb (~> 3.30910.0) | ||||||
|   - FirebaseCore (12.0.0): |   - FirebaseCore (12.2.0): | ||||||
|     - FirebaseCoreInternal (~> 12.0.0) |     - FirebaseCoreInternal (~> 12.2.0) | ||||||
|     - GoogleUtilities/Environment (~> 8.1) |     - GoogleUtilities/Environment (~> 8.1) | ||||||
|     - GoogleUtilities/Logger (~> 8.1) |     - GoogleUtilities/Logger (~> 8.1) | ||||||
|   - FirebaseCoreExtension (12.0.0): |   - FirebaseCoreExtension (12.2.0): | ||||||
|     - FirebaseCore (~> 12.0.0) |     - FirebaseCore (~> 12.2.0) | ||||||
|   - FirebaseCoreInternal (12.0.0): |   - FirebaseCoreInternal (12.2.0): | ||||||
|     - "GoogleUtilities/NSData+zlib (~> 8.1)" |     - "GoogleUtilities/NSData+zlib (~> 8.1)" | ||||||
|   - FirebaseCrashlytics (12.0.0): |   - FirebaseCrashlytics (12.2.0): | ||||||
|     - FirebaseCore (~> 12.0.0) |     - FirebaseCore (~> 12.2.0) | ||||||
|     - FirebaseInstallations (~> 12.0.0) |     - FirebaseInstallations (~> 12.2.0) | ||||||
|     - FirebaseRemoteConfigInterop (~> 12.0.0) |     - FirebaseRemoteConfigInterop (~> 12.2.0) | ||||||
|     - FirebaseSessions (~> 12.0.0) |     - FirebaseSessions (~> 12.2.0) | ||||||
|     - GoogleDataTransport (~> 10.1) |     - GoogleDataTransport (~> 10.1) | ||||||
|     - GoogleUtilities/Environment (~> 8.1) |     - GoogleUtilities/Environment (~> 8.1) | ||||||
|     - nanopb (~> 3.30910.0) |     - nanopb (~> 3.30910.0) | ||||||
|     - PromisesObjC (~> 2.4) |     - PromisesObjC (~> 2.4) | ||||||
|   - FirebaseInstallations (12.0.0): |   - FirebaseInstallations (12.2.0): | ||||||
|     - FirebaseCore (~> 12.0.0) |     - FirebaseCore (~> 12.2.0) | ||||||
|     - GoogleUtilities/Environment (~> 8.1) |     - GoogleUtilities/Environment (~> 8.1) | ||||||
|     - GoogleUtilities/UserDefaults (~> 8.1) |     - GoogleUtilities/UserDefaults (~> 8.1) | ||||||
|     - PromisesObjC (~> 2.4) |     - PromisesObjC (~> 2.4) | ||||||
|   - FirebaseMessaging (12.0.0): |   - FirebaseMessaging (12.2.0): | ||||||
|     - FirebaseCore (~> 12.0.0) |     - FirebaseCore (~> 12.2.0) | ||||||
|     - FirebaseInstallations (~> 12.0.0) |     - FirebaseInstallations (~> 12.2.0) | ||||||
|     - GoogleDataTransport (~> 10.1) |     - GoogleDataTransport (~> 10.1) | ||||||
|     - GoogleUtilities/AppDelegateSwizzler (~> 8.1) |     - GoogleUtilities/AppDelegateSwizzler (~> 8.1) | ||||||
|     - GoogleUtilities/Environment (~> 8.1) |     - GoogleUtilities/Environment (~> 8.1) | ||||||
|     - GoogleUtilities/Reachability (~> 8.1) |     - GoogleUtilities/Reachability (~> 8.1) | ||||||
|     - GoogleUtilities/UserDefaults (~> 8.1) |     - GoogleUtilities/UserDefaults (~> 8.1) | ||||||
|     - nanopb (~> 3.30910.0) |     - nanopb (~> 3.30910.0) | ||||||
|   - FirebaseRemoteConfigInterop (12.0.0) |   - FirebaseRemoteConfigInterop (12.2.0) | ||||||
|   - FirebaseSessions (12.0.0): |   - FirebaseSessions (12.2.0): | ||||||
|     - FirebaseCore (~> 12.0.0) |     - FirebaseCore (~> 12.2.0) | ||||||
|     - FirebaseCoreExtension (~> 12.0.0) |     - FirebaseCoreExtension (~> 12.2.0) | ||||||
|     - FirebaseInstallations (~> 12.0.0) |     - FirebaseInstallations (~> 12.2.0) | ||||||
|     - GoogleDataTransport (~> 10.1) |     - GoogleDataTransport (~> 10.1) | ||||||
|     - GoogleUtilities/Environment (~> 8.1) |     - GoogleUtilities/Environment (~> 8.1) | ||||||
|     - GoogleUtilities/UserDefaults (~> 8.1) |     - GoogleUtilities/UserDefaults (~> 8.1) | ||||||
| @@ -100,6 +100,8 @@ PODS: | |||||||
|   - flutter_inappwebview_macos (0.0.1): |   - flutter_inappwebview_macos (0.0.1): | ||||||
|     - FlutterMacOS |     - FlutterMacOS | ||||||
|     - OrderedSet (~> 6.0.3) |     - OrderedSet (~> 6.0.3) | ||||||
|  |   - flutter_local_notifications (0.0.1): | ||||||
|  |     - FlutterMacOS | ||||||
|   - flutter_platform_alert (0.0.1): |   - flutter_platform_alert (0.0.1): | ||||||
|     - FlutterMacOS |     - FlutterMacOS | ||||||
|   - flutter_secure_storage_macos (6.1.3): |   - flutter_secure_storage_macos (6.1.3): | ||||||
| @@ -109,30 +111,30 @@ PODS: | |||||||
|   - flutter_udid (0.0.1): |   - flutter_udid (0.0.1): | ||||||
|     - FlutterMacOS |     - FlutterMacOS | ||||||
|     - SAMKeychain |     - SAMKeychain | ||||||
|   - flutter_webrtc (1.0.0): |   - flutter_webrtc (1.1.0): | ||||||
|     - FlutterMacOS |     - FlutterMacOS | ||||||
|     - WebRTC-SDK (= 137.7151.02) |     - WebRTC-SDK (= 137.7151.03) | ||||||
|   - FlutterMacOS (1.0.0) |   - FlutterMacOS (1.0.0) | ||||||
|   - gal (1.0.0): |   - gal (1.0.0): | ||||||
|     - Flutter |     - Flutter | ||||||
|     - FlutterMacOS |     - FlutterMacOS | ||||||
|   - GoogleAppMeasurement/Core (12.0.0): |   - GoogleAppMeasurement/Core (12.2.0): | ||||||
|     - GoogleUtilities/AppDelegateSwizzler (~> 8.1) |     - GoogleUtilities/AppDelegateSwizzler (~> 8.1) | ||||||
|     - GoogleUtilities/MethodSwizzler (~> 8.1) |     - GoogleUtilities/MethodSwizzler (~> 8.1) | ||||||
|     - GoogleUtilities/Network (~> 8.1) |     - GoogleUtilities/Network (~> 8.1) | ||||||
|     - "GoogleUtilities/NSData+zlib (~> 8.1)" |     - "GoogleUtilities/NSData+zlib (~> 8.1)" | ||||||
|     - nanopb (~> 3.30910.0) |     - nanopb (~> 3.30910.0) | ||||||
|   - GoogleAppMeasurement/Default (12.0.0): |   - GoogleAppMeasurement/Default (12.2.0): | ||||||
|     - GoogleAdsOnDeviceConversion (= 2.1.0) |     - GoogleAdsOnDeviceConversion (= 2.3.0) | ||||||
|     - GoogleAppMeasurement/Core (= 12.0.0) |     - GoogleAppMeasurement/Core (= 12.2.0) | ||||||
|     - GoogleAppMeasurement/IdentitySupport (= 12.0.0) |     - GoogleAppMeasurement/IdentitySupport (= 12.2.0) | ||||||
|     - GoogleUtilities/AppDelegateSwizzler (~> 8.1) |     - GoogleUtilities/AppDelegateSwizzler (~> 8.1) | ||||||
|     - GoogleUtilities/MethodSwizzler (~> 8.1) |     - GoogleUtilities/MethodSwizzler (~> 8.1) | ||||||
|     - GoogleUtilities/Network (~> 8.1) |     - GoogleUtilities/Network (~> 8.1) | ||||||
|     - "GoogleUtilities/NSData+zlib (~> 8.1)" |     - "GoogleUtilities/NSData+zlib (~> 8.1)" | ||||||
|     - nanopb (~> 3.30910.0) |     - nanopb (~> 3.30910.0) | ||||||
|   - GoogleAppMeasurement/IdentitySupport (12.0.0): |   - GoogleAppMeasurement/IdentitySupport (12.2.0): | ||||||
|     - GoogleAppMeasurement/Core (= 12.0.0) |     - GoogleAppMeasurement/Core (= 12.2.0) | ||||||
|     - GoogleUtilities/AppDelegateSwizzler (~> 8.1) |     - GoogleUtilities/AppDelegateSwizzler (~> 8.1) | ||||||
|     - GoogleUtilities/MethodSwizzler (~> 8.1) |     - GoogleUtilities/MethodSwizzler (~> 8.1) | ||||||
|     - GoogleUtilities/Network (~> 8.1) |     - GoogleUtilities/Network (~> 8.1) | ||||||
| @@ -173,7 +175,7 @@ PODS: | |||||||
|   - livekit_client (2.5.0): |   - livekit_client (2.5.0): | ||||||
|     - flutter_webrtc |     - flutter_webrtc | ||||||
|     - FlutterMacOS |     - FlutterMacOS | ||||||
|     - WebRTC-SDK (= 137.7151.02) |     - WebRTC-SDK (= 137.7151.03) | ||||||
|   - local_auth_darwin (0.0.1): |   - local_auth_darwin (0.0.1): | ||||||
|     - Flutter |     - Flutter | ||||||
|     - FlutterMacOS |     - FlutterMacOS | ||||||
| @@ -237,13 +239,15 @@ PODS: | |||||||
|     - sqlite3/session |     - sqlite3/session | ||||||
|   - super_native_extensions (0.0.1): |   - super_native_extensions (0.0.1): | ||||||
|     - FlutterMacOS |     - FlutterMacOS | ||||||
|  |   - tray_manager (0.0.1): | ||||||
|  |     - FlutterMacOS | ||||||
|   - url_launcher_macos (0.0.1): |   - url_launcher_macos (0.0.1): | ||||||
|     - FlutterMacOS |     - FlutterMacOS | ||||||
|   - volume_controller (0.0.1): |   - volume_controller (0.0.1): | ||||||
|     - FlutterMacOS |     - FlutterMacOS | ||||||
|   - wakelock_plus (0.0.1): |   - wakelock_plus (0.0.1): | ||||||
|     - FlutterMacOS |     - FlutterMacOS | ||||||
|   - WebRTC-SDK (137.7151.02) |   - WebRTC-SDK (137.7151.03) | ||||||
|  |  | ||||||
| DEPENDENCIES: | DEPENDENCIES: | ||||||
|   - bitsdojo_window_macos (from `Flutter/ephemeral/.symlinks/plugins/bitsdojo_window_macos/macos`) |   - bitsdojo_window_macos (from `Flutter/ephemeral/.symlinks/plugins/bitsdojo_window_macos/macos`) | ||||||
| @@ -258,6 +262,7 @@ DEPENDENCIES: | |||||||
|   - firebase_crashlytics (from `Flutter/ephemeral/.symlinks/plugins/firebase_crashlytics/macos`) |   - firebase_crashlytics (from `Flutter/ephemeral/.symlinks/plugins/firebase_crashlytics/macos`) | ||||||
|   - firebase_messaging (from `Flutter/ephemeral/.symlinks/plugins/firebase_messaging/macos`) |   - firebase_messaging (from `Flutter/ephemeral/.symlinks/plugins/firebase_messaging/macos`) | ||||||
|   - flutter_inappwebview_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_inappwebview_macos/macos`) |   - flutter_inappwebview_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_inappwebview_macos/macos`) | ||||||
|  |   - flutter_local_notifications (from `Flutter/ephemeral/.symlinks/plugins/flutter_local_notifications/macos`) | ||||||
|   - flutter_platform_alert (from `Flutter/ephemeral/.symlinks/plugins/flutter_platform_alert/macos`) |   - flutter_platform_alert (from `Flutter/ephemeral/.symlinks/plugins/flutter_platform_alert/macos`) | ||||||
|   - flutter_secure_storage_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos`) |   - flutter_secure_storage_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos`) | ||||||
|   - flutter_timezone (from `Flutter/ephemeral/.symlinks/plugins/flutter_timezone/macos`) |   - flutter_timezone (from `Flutter/ephemeral/.symlinks/plugins/flutter_timezone/macos`) | ||||||
| @@ -280,6 +285,7 @@ DEPENDENCIES: | |||||||
|   - sqflite_darwin (from `Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin`) |   - sqflite_darwin (from `Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin`) | ||||||
|   - sqlite3_flutter_libs (from `Flutter/ephemeral/.symlinks/plugins/sqlite3_flutter_libs/darwin`) |   - sqlite3_flutter_libs (from `Flutter/ephemeral/.symlinks/plugins/sqlite3_flutter_libs/darwin`) | ||||||
|   - super_native_extensions (from `Flutter/ephemeral/.symlinks/plugins/super_native_extensions/macos`) |   - super_native_extensions (from `Flutter/ephemeral/.symlinks/plugins/super_native_extensions/macos`) | ||||||
|  |   - tray_manager (from `Flutter/ephemeral/.symlinks/plugins/tray_manager/macos`) | ||||||
|   - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`) |   - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`) | ||||||
|   - volume_controller (from `Flutter/ephemeral/.symlinks/plugins/volume_controller/macos`) |   - volume_controller (from `Flutter/ephemeral/.symlinks/plugins/volume_controller/macos`) | ||||||
|   - wakelock_plus (from `Flutter/ephemeral/.symlinks/plugins/wakelock_plus/macos`) |   - wakelock_plus (from `Flutter/ephemeral/.symlinks/plugins/wakelock_plus/macos`) | ||||||
| @@ -332,6 +338,8 @@ EXTERNAL SOURCES: | |||||||
|     :path: Flutter/ephemeral/.symlinks/plugins/firebase_messaging/macos |     :path: Flutter/ephemeral/.symlinks/plugins/firebase_messaging/macos | ||||||
|   flutter_inappwebview_macos: |   flutter_inappwebview_macos: | ||||||
|     :path: Flutter/ephemeral/.symlinks/plugins/flutter_inappwebview_macos/macos |     :path: Flutter/ephemeral/.symlinks/plugins/flutter_inappwebview_macos/macos | ||||||
|  |   flutter_local_notifications: | ||||||
|  |     :path: Flutter/ephemeral/.symlinks/plugins/flutter_local_notifications/macos | ||||||
|   flutter_platform_alert: |   flutter_platform_alert: | ||||||
|     :path: Flutter/ephemeral/.symlinks/plugins/flutter_platform_alert/macos |     :path: Flutter/ephemeral/.symlinks/plugins/flutter_platform_alert/macos | ||||||
|   flutter_secure_storage_macos: |   flutter_secure_storage_macos: | ||||||
| @@ -376,6 +384,8 @@ EXTERNAL SOURCES: | |||||||
|     :path: Flutter/ephemeral/.symlinks/plugins/sqlite3_flutter_libs/darwin |     :path: Flutter/ephemeral/.symlinks/plugins/sqlite3_flutter_libs/darwin | ||||||
|   super_native_extensions: |   super_native_extensions: | ||||||
|     :path: Flutter/ephemeral/.symlinks/plugins/super_native_extensions/macos |     :path: Flutter/ephemeral/.symlinks/plugins/super_native_extensions/macos | ||||||
|  |   tray_manager: | ||||||
|  |     :path: Flutter/ephemeral/.symlinks/plugins/tray_manager/macos | ||||||
|   url_launcher_macos: |   url_launcher_macos: | ||||||
|     :path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos |     :path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos | ||||||
|   volume_controller: |   volume_controller: | ||||||
| @@ -391,33 +401,34 @@ SPEC CHECKSUMS: | |||||||
|   file_picker: 7584aae6fa07a041af2b36a2655122d42f578c1a |   file_picker: 7584aae6fa07a041af2b36a2655122d42f578c1a | ||||||
|   file_saver: e35bd97de451dde55ff8c38862ed7ad0f3418d0f |   file_saver: e35bd97de451dde55ff8c38862ed7ad0f3418d0f | ||||||
|   file_selector_macos: 6280b52b459ae6c590af5d78fc35c7267a3c4b31 |   file_selector_macos: 6280b52b459ae6c590af5d78fc35c7267a3c4b31 | ||||||
|   Firebase: 800d487043c0557d9faed71477a38d9aafb08a41 |   Firebase: 26f6f8d460603af3df970ad505b16b15f5e2e9a1 | ||||||
|   firebase_analytics: 53f0dc87ad10f56a6df8746da60d8a5fe41f886f |   firebase_analytics: efe6e51156f4565f3791d99072e8e3b0fcca0e91 | ||||||
|   firebase_core: eeea10f64026b68cd0bc3dee079ab4717e22909e |   firebase_core: a8d3b82b0a87bd1d0ebc21e686b37e939c56e6e1 | ||||||
|   firebase_crashlytics: 7be1dacc38809971354def57193b280636a3d51a |   firebase_crashlytics: fdbe67a1229a9e583ebf2b155541491aa83927bb | ||||||
|   firebase_messaging: 5eefcd5bde556bfacdd9968e11c52f39032dfbe5 |   firebase_messaging: 6fb526705903e2e56e38a6ff56b43668b052b01b | ||||||
|   FirebaseAnalytics: 6d790cd1b159b4eb61a99948df0934ce505a34f7 |   FirebaseAnalytics: e04e23bc070e3014aa5cf4980f9df7ce5cd79ec8 | ||||||
|   FirebaseCore: 055f4ab117d5964158c833f3d5e7ec6d91648d4a |   FirebaseCore: 311c48a147ad4a0ab7febbaed89e8025c67510cd | ||||||
|   FirebaseCoreExtension: 639afb3de6abd611952be78a794c54a47fa0f361 |   FirebaseCoreExtension: 73af080c22a2f7b44cefa391dc08f7e4ee162cb5 | ||||||
|   FirebaseCoreInternal: dedc28e569a4be85f38f3d6af1070a2e12018d55 |   FirebaseCoreInternal: 56ea29f3dad2894f81b060f706f9d53509b6ed3b | ||||||
|   FirebaseCrashlytics: db75aa0cab8d00f68406fa247c32fe17ade884d7 |   FirebaseCrashlytics: f83cbf176d5c637ade108c0aacf1ccbd5ec499bf | ||||||
|   FirebaseInstallations: d4c7c958f99c8860d7fcece786314ae790e2f988 |   FirebaseInstallations: 3e884b01feabdf67582a80f3250425a00979b4ed | ||||||
|   FirebaseMessaging: af49f8d7c0a3d2a017d9302c80946f45a7777dde |   FirebaseMessaging: 43ec73bbfedd0c385a849bb91593ab4ad4b9e48e | ||||||
|   FirebaseRemoteConfigInterop: bfa0ea72ba3dc5af739777296424e46bd6f42613 |   FirebaseRemoteConfigInterop: 0896fd52ab72586a355c8f389ff85aaa9e5375e1 | ||||||
|   FirebaseSessions: 4e784acda213108aafef536535cdfc03504acc42 |   FirebaseSessions: f4692789e770bec66ce17d772c0e9561c4f11737 | ||||||
|   flutter_inappwebview_macos: c2d68649f9f8f1831bfcd98d73fd6256366d9d1d |   flutter_inappwebview_macos: c2d68649f9f8f1831bfcd98d73fd6256366d9d1d | ||||||
|  |   flutter_local_notifications: 4bf37a31afde695b56091b4ae3e4d9c7a7e6cda0 | ||||||
|   flutter_platform_alert: 8fa7a7c21f95b26d08b4a3891936ca27e375f284 |   flutter_platform_alert: 8fa7a7c21f95b26d08b4a3891936ca27e375f284 | ||||||
|   flutter_secure_storage_macos: 7f45e30f838cf2659862a4e4e3ee1c347c2b3b54 |   flutter_secure_storage_macos: 7f45e30f838cf2659862a4e4e3ee1c347c2b3b54 | ||||||
|   flutter_timezone: d59eea86178cbd7943cd2431cc2eaa9850f935d8 |   flutter_timezone: d59eea86178cbd7943cd2431cc2eaa9850f935d8 | ||||||
|   flutter_udid: d26e455e8c06174e6aff476e147defc6cae38495 |   flutter_udid: d26e455e8c06174e6aff476e147defc6cae38495 | ||||||
|   flutter_webrtc: 0d70bd8782c19bde286dc52f766eebbea26de201 |   flutter_webrtc: 1ce7fe9a42f085286378355a575e682edd7f114d | ||||||
|   FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 |   FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1 | ||||||
|   gal: baecd024ebfd13c441269ca7404792a7152fde89 |   gal: baecd024ebfd13c441269ca7404792a7152fde89 | ||||||
|   GoogleAppMeasurement: 8f6ab04ad6ae493b53fcf56bd26323fb2f1384f3 |   GoogleAppMeasurement: 09f341dfa8527d1612a09cbfe809a242c0b737af | ||||||
|   GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7 |   GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7 | ||||||
|   GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1 |   GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1 | ||||||
|   irondash_engine_context: 893c7d96d20ce361d7e996f39d360c4c2f9869ba |   irondash_engine_context: 893c7d96d20ce361d7e996f39d360c4c2f9869ba | ||||||
|   livekit_client: 0b0515e03858b86a7c14cc7fd6f772331f6ee84c |   livekit_client: 5a5c0f1081978542bbf9a986c7ac9bffcdb73906 | ||||||
|   local_auth_darwin: d2e8c53ef0c4f43c646462e3415432c4dab3ae19 |   local_auth_darwin: d2e8c53ef0c4f43c646462e3415432c4dab3ae19 | ||||||
|   media_kit_libs_macos_video: 85a23e549b5f480e72cae3e5634b5514bc692f65 |   media_kit_libs_macos_video: 85a23e549b5f480e72cae3e5634b5514bc692f65 | ||||||
|   media_kit_video: fa6564e3799a0a28bff39442334817088b7ca758 |   media_kit_video: fa6564e3799a0a28bff39442334817088b7ca758 | ||||||
| @@ -437,10 +448,11 @@ SPEC CHECKSUMS: | |||||||
|   sqlite3: 73513155ec6979715d3904ef53a8d68892d4032b |   sqlite3: 73513155ec6979715d3904ef53a8d68892d4032b | ||||||
|   sqlite3_flutter_libs: 83f8e9f5b6554077f1d93119fe20ebaa5f3a9ef1 |   sqlite3_flutter_libs: 83f8e9f5b6554077f1d93119fe20ebaa5f3a9ef1 | ||||||
|   super_native_extensions: c2795d6d9aedf4a79fae25cb6160b71b50549189 |   super_native_extensions: c2795d6d9aedf4a79fae25cb6160b71b50549189 | ||||||
|  |   tray_manager: a104b5c81b578d83f3c3d0f40a997c8b10810166 | ||||||
|   url_launcher_macos: 0fba8ddabfc33ce0a9afe7c5fef5aab3d8d2d673 |   url_launcher_macos: 0fba8ddabfc33ce0a9afe7c5fef5aab3d8d2d673 | ||||||
|   volume_controller: 5c068e6d085c80dadd33fc2c918d2114b775b3dd |   volume_controller: 5c068e6d085c80dadd33fc2c918d2114b775b3dd | ||||||
|   wakelock_plus: 21ddc249ac4b8d018838dbdabd65c5976c308497 |   wakelock_plus: 21ddc249ac4b8d018838dbdabd65c5976c308497 | ||||||
|   WebRTC-SDK: d20de357dcbf7c9696b124b39f3ff62125107e4b |   WebRTC-SDK: 69d4e56b0b4b27d788e87bab9b9a1326ed05b1e3 | ||||||
|  |  | ||||||
| PODFILE CHECKSUM: 346bfb2deb41d4a6ebd6f6799f92188bde2d246f | PODFILE CHECKSUM: 346bfb2deb41d4a6ebd6f6799f92188bde2d246f | ||||||
|  |  | ||||||
|   | |||||||
| @@ -586,7 +586,7 @@ | |||||||
| 				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; | 				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; | ||||||
| 				GCC_WARN_UNUSED_FUNCTION = YES; | 				GCC_WARN_UNUSED_FUNCTION = YES; | ||||||
| 				GCC_WARN_UNUSED_VARIABLE = YES; | 				GCC_WARN_UNUSED_VARIABLE = YES; | ||||||
| 				MACOSX_DEPLOYMENT_TARGET = 10.14; | 				MACOSX_DEPLOYMENT_TARGET = 10.15; | ||||||
| 				MTL_ENABLE_DEBUG_INFO = NO; | 				MTL_ENABLE_DEBUG_INFO = NO; | ||||||
| 				SDKROOT = macosx; | 				SDKROOT = macosx; | ||||||
| 				SWIFT_COMPILATION_MODE = wholemodule; | 				SWIFT_COMPILATION_MODE = wholemodule; | ||||||
| @@ -674,7 +674,7 @@ | |||||||
| 				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; | 				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; | ||||||
| 				GCC_WARN_UNUSED_FUNCTION = YES; | 				GCC_WARN_UNUSED_FUNCTION = YES; | ||||||
| 				GCC_WARN_UNUSED_VARIABLE = YES; | 				GCC_WARN_UNUSED_VARIABLE = YES; | ||||||
| 				MACOSX_DEPLOYMENT_TARGET = 10.14; | 				MACOSX_DEPLOYMENT_TARGET = 10.15; | ||||||
| 				MTL_ENABLE_DEBUG_INFO = YES; | 				MTL_ENABLE_DEBUG_INFO = YES; | ||||||
| 				ONLY_ACTIVE_ARCH = YES; | 				ONLY_ACTIVE_ARCH = YES; | ||||||
| 				SDKROOT = macosx; | 				SDKROOT = macosx; | ||||||
| @@ -724,7 +724,7 @@ | |||||||
| 				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; | 				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; | ||||||
| 				GCC_WARN_UNUSED_FUNCTION = YES; | 				GCC_WARN_UNUSED_FUNCTION = YES; | ||||||
| 				GCC_WARN_UNUSED_VARIABLE = YES; | 				GCC_WARN_UNUSED_VARIABLE = YES; | ||||||
| 				MACOSX_DEPLOYMENT_TARGET = 10.14; | 				MACOSX_DEPLOYMENT_TARGET = 10.15; | ||||||
| 				MTL_ENABLE_DEBUG_INFO = NO; | 				MTL_ENABLE_DEBUG_INFO = NO; | ||||||
| 				SDKROOT = macosx; | 				SDKROOT = macosx; | ||||||
| 				SWIFT_COMPILATION_MODE = wholemodule; | 				SWIFT_COMPILATION_MODE = wholemodule; | ||||||
|   | |||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user