Compare commits
	
		
			81 Commits
		
	
	
		
			3.1.0+123
			...
			a127b5bace
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| a127b5bace | |||
| b2097cf044 | |||
| 701f29748d | |||
| 9e40ed4600 | |||
| c90e6fe661 | |||
| 569483300d | |||
| bab602d98b | |||
| b4f2bb803a | |||
| 03bfed6f46 | |||
| f98e5a0aec | |||
| 3d473e2fec | |||
| 0b6efa373a | |||
| 9b60e96cde | |||
| 81cd9b2082 | |||
| 923d5d7514 | |||
| 7169aff841 | |||
| fac3efb50c | |||
| e809aadaea | |||
| f33b569221 | |||
| e5f2e2d146 | |||
| 11368d064f | |||
| 246b163aec | |||
| 10e0d2fe5f | |||
| 99e10cb612 | |||
| 1db6941431 | |||
| 8370da4fe3 | |||
| 2bdf7029e9 | |||
| 86682a3a9a | |||
| c3925e81b5 | |||
| 6f1f488490 | |||
| 31b2de2e46 | |||
| 412dcfa62a | |||
| ffdc7e81ae | |||
| 1d3357803d | |||
| 6c48aa2356 | |||
| 466e354679 | |||
| 5d4b896f70 | |||
| a04dffdfe8 | |||
| ff871943cf | |||
| 1a892ab227 | |||
| af1b303211 | |||
| 6fd702eba8 | |||
| d220d43cd2 | |||
| 6892afb974 | |||
| 007b46b080 | |||
| 67d130dc34 | |||
| 7e923c77fe | |||
| a593b52812 | |||
|  | 520dc80303 | ||
| 001897bbcd | |||
|  | bab29c23e3 | ||
| 76b39f2df3 | |||
| 509b3e145b | |||
| 2b80ebc2d0 | |||
| 0ab908dd2a | |||
| 6007467e7a | |||
| 3745157c42 | |||
| 94481ec7bd | |||
| fbfe8cbdee | |||
| fbbab0a981 | |||
| ae2fb3b303 | |||
| 3d7a4666ed | |||
| 5d3e0fb800 | |||
| 85ff52a661 | |||
| da7fd64a43 | |||
| 3902633217 | |||
| f478ea8b84 | |||
| 0f481aff5b | |||
| 7a31663310 | |||
| 0239c53c04 | |||
| 16987c758e | |||
| 3a36915140 | |||
| 4bde708878 | |||
| 2f0cf560f8 | |||
| cf355a95fd | |||
| 2f43073172 | |||
| 8236d31ecc | |||
| 459a7dade0 | |||
| e6000a660a | |||
| 75abaac205 | |||
| 603d5c3f73 | 
							
								
								
									
										9
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										9
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							| @@ -41,6 +41,15 @@ jobs: | |||||||
|         with: |         with: | ||||||
|           name: build-output-windows |           name: build-output-windows | ||||||
|           path: build/windows/x64/runner/Release |           path: build/windows/x64/runner/Release | ||||||
|  |       - name: Compile Installer | ||||||
|  |         uses: Minionguyjpro/Inno-Setup-Action@v1.2.2 | ||||||
|  |         with: | ||||||
|  |           path: setup.iss | ||||||
|  |       - name: Archive installer artifacts | ||||||
|  |         uses: actions/upload-artifact@v4 | ||||||
|  |         with: | ||||||
|  |           name: build-output-windows-installer | ||||||
|  |           path: Installer/windows-x86_64-setup.exe | ||||||
|   build-linux: |   build-linux: | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|     steps: |     steps: | ||||||
|   | |||||||
							
								
								
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -12,6 +12,9 @@ | |||||||
| .swiftpm/ | .swiftpm/ | ||||||
| migrate_working_dir/ | migrate_working_dir/ | ||||||
|  |  | ||||||
|  | # Inno Setup | ||||||
|  | Installer/ | ||||||
|  |  | ||||||
| # IntelliJ related | # IntelliJ related | ||||||
| *.iml | *.iml | ||||||
| *.ipr | *.ipr | ||||||
|   | |||||||
| @@ -5,6 +5,7 @@ plugins { | |||||||
|     id("com.android.application") |     id("com.android.application") | ||||||
|     // START: FlutterFire Configuration |     // START: FlutterFire Configuration | ||||||
|     id("com.google.gms.google-services") |     id("com.google.gms.google-services") | ||||||
|  |     id("com.google.firebase.crashlytics") | ||||||
|     // END: FlutterFire Configuration |     // END: FlutterFire Configuration | ||||||
|     id("kotlin-android") |     id("kotlin-android") | ||||||
|     // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. |     // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. | ||||||
|   | |||||||
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 70 KiB | 
							
								
								
									
										41
									
								
								android/app/src/main/res/drawable/ic_notification.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								android/app/src/main/res/drawable/ic_notification.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,41 @@ | |||||||
|  | <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|  |     android:width="192dp" | ||||||
|  |     android:height="192dp" | ||||||
|  |     android:viewportWidth="192" | ||||||
|  |     android:viewportHeight="192"> | ||||||
|  |   <path | ||||||
|  |       android:pathData="M54,147h86" | ||||||
|  |       android:strokeLineJoin="round" | ||||||
|  |       android:strokeWidth="12" | ||||||
|  |       android:fillColor="#00000000" | ||||||
|  |       android:strokeColor="#000" | ||||||
|  |       android:strokeLineCap="round"/> | ||||||
|  |   <path | ||||||
|  |       android:pathData="M57,111s-2,-4.5 -2,-10m22,22s-4,7 -11,4m9,-22s-2,-4.5 -2,-10" | ||||||
|  |       android:strokeLineJoin="round" | ||||||
|  |       android:strokeWidth="10" | ||||||
|  |       android:fillColor="#00000000" | ||||||
|  |       android:strokeColor="#000" | ||||||
|  |       android:strokeLineCap="round"/> | ||||||
|  |   <path | ||||||
|  |       android:pathData="M54,147a32,32 0,0 1,-12 -61.67A39,39 0,0 1,81 46m59,101a30,30 0,0 0,29.93 -28" | ||||||
|  |       android:strokeLineJoin="round" | ||||||
|  |       android:strokeWidth="12" | ||||||
|  |       android:fillColor="#00000000" | ||||||
|  |       android:strokeColor="#000" | ||||||
|  |       android:strokeLineCap="round"/> | ||||||
|  |   <path | ||||||
|  |       android:pathData="M132,75m-4,0a4,4 0,1 1,8 0a4,4 0,1 1,-8 0" | ||||||
|  |       android:strokeLineJoin="round" | ||||||
|  |       android:strokeWidth="8" | ||||||
|  |       android:fillColor="#00000000" | ||||||
|  |       android:strokeColor="#000" | ||||||
|  |       android:strokeLineCap="round"/> | ||||||
|  |   <path | ||||||
|  |       android:pathData="M112.5,41.22C100.84,47.96 93,60.56 93,75c0,6.38 1.53,12.39 4.24,17.71m69.51,-35.42A38.84,38.84 0,0 1,171 75c0,14.43 -7.84,27.03 -19.49,33.78m-0.79,-43.32A20.9,20.9 0,0 1,153 75c0,7.77 -4.22,14.56 -10.49,18.19m-21,-36.38C115.22,60.44 111,67.23 111,75a20.9,20.9 0,0 0,2.28 9.53" | ||||||
|  |       android:strokeLineJoin="round" | ||||||
|  |       android:strokeWidth="10" | ||||||
|  |       android:fillColor="#00000000" | ||||||
|  |       android:strokeColor="#000" | ||||||
|  |       android:strokeLineCap="round"/> | ||||||
|  | </vector> | ||||||
| @@ -21,6 +21,7 @@ plugins { | |||||||
|     id("com.android.application") version "8.12.0" apply false |     id("com.android.application") version "8.12.0" apply false | ||||||
|     // START: FlutterFire Configuration |     // START: FlutterFire Configuration | ||||||
|     id("com.google.gms.google-services") version("4.3.15") apply false |     id("com.google.gms.google-services") version("4.3.15") apply false | ||||||
|  |     id("com.google.firebase.crashlytics") version("2.8.1") apply false | ||||||
|     // END: FlutterFire Configuration |     // END: FlutterFire Configuration | ||||||
|     id("org.jetbrains.kotlin.android") version("2.2.0") apply false |     id("org.jetbrains.kotlin.android") version("2.2.0") apply false | ||||||
| } | } | ||||||
|   | |||||||
| @@ -334,6 +334,7 @@ | |||||||
|   "walletCreate": "Create a Wallet", |   "walletCreate": "Create a Wallet", | ||||||
|   "settingsServerUrl": "Server URL", |   "settingsServerUrl": "Server URL", | ||||||
|   "settingsApplied": "The settings has been applied.", |   "settingsApplied": "The settings has been applied.", | ||||||
|  |   "settingsCustomFontsHelper": "Use comma to seprate.", | ||||||
|   "notifications": "Notifications", |   "notifications": "Notifications", | ||||||
|   "posts": "Posts", |   "posts": "Posts", | ||||||
|   "settingsBackgroundImage": "Background Image", |   "settingsBackgroundImage": "Background Image", | ||||||
| @@ -573,6 +574,7 @@ | |||||||
|   "keyboardShortcuts": "Keyboard Shortcuts", |   "keyboardShortcuts": "Keyboard Shortcuts", | ||||||
|   "share": "Share", |   "share": "Share", | ||||||
|   "sharePost": "Share Post", |   "sharePost": "Share Post", | ||||||
|  |   "sharePostPhoto": "Share Post as Photo", | ||||||
|   "quickActions": "Quick Actions", |   "quickActions": "Quick Actions", | ||||||
|   "post": "Post", |   "post": "Post", | ||||||
|   "copy": "Copy", |   "copy": "Copy", | ||||||
| @@ -760,6 +762,7 @@ | |||||||
|   "pollsRecent": "Recent Polls", |   "pollsRecent": "Recent Polls", | ||||||
|   "pollCreateNew": "Create New", |   "pollCreateNew": "Create New", | ||||||
|   "pollCreateNewHint": "Create a new poll for your post. Pick a publisher and continue.", |   "pollCreateNewHint": "Create a new poll for your post. Pick a publisher and continue.", | ||||||
|  |   "pollQuestions": "Questions", | ||||||
|   "publisher": "Publisher", |   "publisher": "Publisher", | ||||||
|   "publisherHint": "Enter the publisher name", |   "publisherHint": "Enter the publisher name", | ||||||
|   "publisherCannotBeEmpty": "Publisher cannot be empty", |   "publisherCannotBeEmpty": "Publisher cannot be empty", | ||||||
| @@ -792,5 +795,85 @@ | |||||||
|   "joinedAt": "Joined at {}", |   "joinedAt": "Joined at {}", | ||||||
|   "searchAccounts": "Search accounts...", |   "searchAccounts": "Search accounts...", | ||||||
|   "webFeeds": "Web Feeds", |   "webFeeds": "Web Feeds", | ||||||
|   "polls": "Polls" |   "polls": "Polls", | ||||||
|  |   "sharePostSlogan": "Explore more on the Solar Network", | ||||||
|  |   "filesListAdditional": { | ||||||
|  |     "one": "+{} file remaining", | ||||||
|  |     "other": "+{} files remaining" | ||||||
|  |   }, | ||||||
|  |   "pollAnswerSubmitted": "Poll answer has been submitted.", | ||||||
|  |   "modifyAnswers": "Modify Answers", | ||||||
|  |   "back": "Back", | ||||||
|  |   "submit": "Submit", | ||||||
|  |   "pollOptionDefaultLabel": "Option 1", | ||||||
|  |   "pollUpdated": "Poll updated.", | ||||||
|  |   "pollCreated": "Poll created.", | ||||||
|  |   "pollCreate": "Create Poll", | ||||||
|  |   "pollEdit": "Edit Poll", | ||||||
|  |   "pollPreviewJsonDebug": "Debug Preview", | ||||||
|  |   "pollTitleRequired": "Title is required", | ||||||
|  |   "pollEndDateOptional": "End date & time (optional)", | ||||||
|  |   "notSet": "Not set", | ||||||
|  |   "pick": "Pick", | ||||||
|  |   "clear": "Clear", | ||||||
|  |   "questions": "Questions", | ||||||
|  |   "pollAddQuestion": "Add question", | ||||||
|  |   "pollQuestionTypeSingleChoice": "Single choice", | ||||||
|  |   "pollQuestionTypeMultipleChoice": "Multiple choice", | ||||||
|  |   "pollQuestionTypeFreeText": "Free text", | ||||||
|  |   "pollQuestionTypeYesNo": "Yes / No", | ||||||
|  |   "pollQuestionTypeRating": "Rating", | ||||||
|  |   "pollNoQuestionsYet": "No questions yet", | ||||||
|  |   "pollNoQuestionsHint": "Use \"Add question\" to start building your poll.", | ||||||
|  |   "pollDebugPreview": "Debug Preview", | ||||||
|  |   "pollUntitledQuestion": "Untitled question", | ||||||
|  |   "moveUp": "Move up", | ||||||
|  |   "moveDown": "Move down", | ||||||
|  |   "required": "Required", | ||||||
|  |   "pollQuestionTitle": "Question title", | ||||||
|  |   "pollQuestionTitleRequired": "Question title is required", | ||||||
|  |   "pollQuestionDescriptionOptional": "Question description (optional)", | ||||||
|  |   "options": "Options", | ||||||
|  |   "pollAddOption": "Add option", | ||||||
|  |   "pollOptionLabel": "Option label", | ||||||
|  |   "pollLongTextAnswerPreview": "Long text answer (preview)", | ||||||
|  |   "pollShortTextAnswerPreview": "Short text answer (preview)", | ||||||
|  |   "messageJumpNotLoaded": "The referenced message was not loaded, unable to jump to it.", | ||||||
|  |   "postUnlinkRealm": "No linked realm", | ||||||
|  |   "postSlug": "Slug", | ||||||
|  |   "postSlugHint": "The slug can be used to access your post via URL in the webpage, it should be publisher-wide unique.", | ||||||
|  |   "attachmentOnDevice": "On-device", | ||||||
|  |   "attachmentOnCloud": "On-cloud", | ||||||
|  |   "attachments": "Attachments", | ||||||
|  |   "publisherCollabInvitation": "Collabration invitations", | ||||||
|  |   "publisherCollabInvitationCount": { | ||||||
|  |     "zero": "No invitation", | ||||||
|  |     "one": "{} available invitation", | ||||||
|  |     "other": "{} available invitations" | ||||||
|  |   }, | ||||||
|  |   "failedToLoadUserInfo": "Failed to load user info", | ||||||
|  |   "failedToLoadUserInfoNetwork": "It seems be network issue, you can tap the button below to try again.", | ||||||
|  |   "failedToLoadUserInfoUnauthorized": "It seems your session has been logged out or not available anymore, you can still try agian to fetch the user info if you want.", | ||||||
|  |   "okay": "Okay", | ||||||
|  |   "postDetails": "Post Details", | ||||||
|  |   "postCount": { | ||||||
|  |     "zero": "No posts", | ||||||
|  |     "one": "{} post", | ||||||
|  |     "other": "{} posts" | ||||||
|  |   }, | ||||||
|  |   "mimeType": "MIME Type", | ||||||
|  |   "fileSize": "File Size", | ||||||
|  |   "fileHash": "File Hash", | ||||||
|  |   "exifData": "EXIF Data", | ||||||
|  |   "postShuffle": "Shuffle Posts", | ||||||
|  |   "leveling": "Leveling", | ||||||
|  |   "levelingHistory": "Leveling History", | ||||||
|  |   "stellarProgram": "Stellar Program", | ||||||
|  |   "socialCredits": "Social Credits", | ||||||
|  |   "credits": "Credits", | ||||||
|  |   "socialCreditsDescription": "Social Credit is a way for Solar Network to evaluate users. It is calculated based on their behavior and interactions. With a base score of 100, higher scores indicate a user's credibility within the community. Scores change over time to reflect a user's recent behavior. Users with higher credit ratings enjoy more benefits, while users with lower credit ratings may have some functionality restricted.", | ||||||
|  |   "socialCreditsLevelPoor": "Poor", | ||||||
|  |   "socialCreditsLevelNormal": "Normal", | ||||||
|  |   "socialCreditsLevelGood": "Good", | ||||||
|  |   "socialCreditsLevelExcellent": "Excellent" | ||||||
| } | } | ||||||
|   | |||||||
| @@ -46,7 +46,6 @@ | |||||||
|   "delete": "删除", |   "delete": "删除", | ||||||
|   "deletePublisher": "删除发布者", |   "deletePublisher": "删除发布者", | ||||||
|   "deletePublisherHint": "确定要删除此发布者吗?这也会删除此发布者下的所有帖子和收藏。", |   "deletePublisherHint": "确定要删除此发布者吗?这也会删除此发布者下的所有帖子和收藏。", | ||||||
|   "somethingWentWrong": "发生了一些错误……", |  | ||||||
|   "deletePost": "删除帖子", |   "deletePost": "删除帖子", | ||||||
|   "deletePostHint": "确定要删除这篇帖子吗?", |   "deletePostHint": "确定要删除这篇帖子吗?", | ||||||
|   "copyLink": "复制链接", |   "copyLink": "复制链接", | ||||||
| @@ -301,6 +300,7 @@ | |||||||
|   "walletCreate": "创建钱包", |   "walletCreate": "创建钱包", | ||||||
|   "settingsServerUrl": "服务器 URL", |   "settingsServerUrl": "服务器 URL", | ||||||
|   "settingsApplied": "设置已应用。", |   "settingsApplied": "设置已应用。", | ||||||
|  |   "settingsCustomFontsHelper": "用逗号分隔。", | ||||||
|   "notifications": "通知", |   "notifications": "通知", | ||||||
|   "posts": "帖子", |   "posts": "帖子", | ||||||
|   "settingsBackgroundImage": "背景图片", |   "settingsBackgroundImage": "背景图片", | ||||||
| @@ -349,7 +349,6 @@ | |||||||
|   "postContent": "内容", |   "postContent": "内容", | ||||||
|   "postSettings": "设置", |   "postSettings": "设置", | ||||||
|   "postPublisherUnselected": "未指定发布者", |   "postPublisherUnselected": "未指定发布者", | ||||||
|   "postVisibility": "可见性", |  | ||||||
|   "postVisibilityPublic": "公开", |   "postVisibilityPublic": "公开", | ||||||
|   "postVisibilityFriends": "仅好友可见", |   "postVisibilityFriends": "仅好友可见", | ||||||
|   "postVisibilityUnlisted": "不公开", |   "postVisibilityUnlisted": "不公开", | ||||||
| @@ -501,9 +500,6 @@ | |||||||
|   "membershipTierNova": "新星", |   "membershipTierNova": "新星", | ||||||
|   "membershipTierSupernova": "超新星", |   "membershipTierSupernova": "超新星", | ||||||
|   "membershipTierUnknown": "未知", |   "membershipTierUnknown": "未知", | ||||||
|   "membershipPriceStellar": "每月 10 金点", |  | ||||||
|   "membershipPriceNova": "每月 20 金点", |  | ||||||
|   "membershipPriceSupernova": "每月 30 金点", |  | ||||||
|   "membershipFeatureBasic": "基础功能", |   "membershipFeatureBasic": "基础功能", | ||||||
|   "membershipFeaturePrioritySupport": "优先支持", |   "membershipFeaturePrioritySupport": "优先支持", | ||||||
|   "membershipFeatureAdFree": "无广告", |   "membershipFeatureAdFree": "无广告", | ||||||
| @@ -573,37 +569,36 @@ | |||||||
|   "share": "分享", |   "share": "分享", | ||||||
|   "sharePost": "分享帖子", |   "sharePost": "分享帖子", | ||||||
|   "quickActions": "快捷操作", |   "quickActions": "快捷操作", | ||||||
|   "post": "帖子", |   "post": "发帖", | ||||||
|   "copy": "复制", |   "copy": "复制", | ||||||
|   "sendToChat": "发送到聊天", |   "sendToChat": "发送到聊天", | ||||||
|   "failedToShareToPost": "分享到帖子失败:{}", |   "failedToShareToPost": "分享到帖子失败:{}", | ||||||
|   "shareToChatComingSoon": "分享到聊天的功能即将到来", |   "shareToChatComingSoon": "分享到聊天功能即将推出", | ||||||
|   "failedToShareToChat": "分享到聊天失败:{}", |   "failedToShareToChat": "分享到聊天失败:{}", | ||||||
|   "shareToSpecificChatComingSoon": "分享到 {} 的功能即将到来", |   "shareToSpecificChatComingSoon": "分享到 {} 功能即将推出", | ||||||
|   "directChat": "私信", |   "directChat": "私信", | ||||||
|   "systemShareComingSoon": "系统分享功能即将到来", |   "systemShareComingSoon": "系统分享功能即将推出", | ||||||
|   "failedToShareToSystem": "分享到系统失败:{}", |   "failedToShareToSystem": "分享到系统失败:{}", | ||||||
|   "failedToCopy": "复制失败:{}", |   "failedToCopy": "复制失败:{}", | ||||||
|   "noChatRoomsAvailable": "没有聊天室可用", |   "noChatRoomsAvailable": "无可用聊天室", | ||||||
|   "failedToLoadChats": "加载聊天室失败", |   "failedToLoadChats": "加载聊天失败", | ||||||
|   "contentToShare": "要分享的内容:", |   "contentToShare": "分享内容:", | ||||||
|   "unknownChat": "未知聊天室", |   "unknownChat": "未知聊天", | ||||||
|   "addAdditionalMessage": "添加额外消息……", |   "addAdditionalMessage": "添加附加消息……", | ||||||
|   "uploadingFiles": "上传文件中……", |   "uploadingFiles": "上传文件中……", | ||||||
|   "sharedSuccessfully": "分享成功!", |   "sharedSuccessfully": "分享成功!", | ||||||
|   "shareSuccess": "分享成功!", |   "shareSuccess": "分享成功!", | ||||||
|   "shareToSpecificChatSuccess": "分享到 {} 成功!", |   "shareToSpecificChatSuccess": "成功分享至 {}!", | ||||||
|   "wouldYouLikeToGoToChat": "你想要前往聊天页面吗?", |   "wouldYouLikeToGoToChat": "是否前往该聊天?", | ||||||
|   "no": "是", |   "no": "否", | ||||||
|   "yes": "否", |   "yes": "是", | ||||||
|   "navigateToChat": "前往聊天室", |   "navigateToChat": "前往聊天", | ||||||
|   "wouldYouLikeToNavigateToChat": "你想要前往聊天页面吗?", |  | ||||||
|   "abuseReport": "举报", |   "abuseReport": "举报", | ||||||
|   "abuseReportTitle": "举报内容", |   "abuseReportTitle": "举报内容", | ||||||
|   "abuseReportDescription": "通过举报不合适的内容和行为来帮助我们维护社区的健康稳定发展。", |   "abuseReportDescription": "举报不当内容或行为,协助维护社区安全。", | ||||||
|   "abuseReportType": "举报类型", |   "abuseReportType": "举报类型", | ||||||
|   "abuseReportReason": "额外细节", |   "abuseReportReason": "补充详情", | ||||||
|   "abuseReportReasonHint": "请提供更多关于此的细节……", |   "abuseReportReasonHint": "请提供更多详情……", | ||||||
|   "abuseReportSubmit": "提交举报", |   "abuseReportSubmit": "提交举报", | ||||||
|   "abuseReportSuccess": "举报提交成功,感谢你为社区维护作出贡献。", |   "abuseReportSuccess": "举报提交成功,感谢你为社区维护作出贡献。", | ||||||
|   "abuseReportError": "无法提交举报,请稍后再试。", |   "abuseReportError": "无法提交举报,请稍后再试。", | ||||||
| @@ -629,8 +624,6 @@ | |||||||
|   "chatJoin": "加入聊天", |   "chatJoin": "加入聊天", | ||||||
|   "realmJoin": "加入领域", |   "realmJoin": "加入领域", | ||||||
|   "realmJoinSuccess": "成功加入领域。", |   "realmJoinSuccess": "成功加入领域。", | ||||||
|   "discoverRealms": "发现领域", |  | ||||||
|   "discoverPublishers": "发现发布者", |  | ||||||
|   "search": "搜索", |   "search": "搜索", | ||||||
|   "publisherMembers": "合作者", |   "publisherMembers": "合作者", | ||||||
|   "developerHub": "开发者中心", |   "developerHub": "开发者中心", | ||||||
| @@ -679,10 +672,32 @@ | |||||||
|   "discoverWebArticles": "来自站外的文章", |   "discoverWebArticles": "来自站外的文章", | ||||||
|   "webArticlesStand": "文章亭", |   "webArticlesStand": "文章亭", | ||||||
|   "about": "关于", |   "about": "关于", | ||||||
|   "membershipCancel": "取消会员资格", |   "somethingWentWrong": "发生了一些错误", | ||||||
|   "membershipCancelConfirm": "你确定要取消会员资格吗?", |   "editedAt": "编辑于 {}", | ||||||
|   "membershipCancelHint": "你确定要取消会员资格吗?你将不会再次被扣费。你的会员资格将在当前计费周期结束前保持有效。并且你将无法重新订阅,直到当前订阅结束。", |   "addAudio": "添加音频", | ||||||
|   "membershipCancelSuccess": "你的会员资格已成功取消。", |   "recordAudio": "录制音频", | ||||||
|  |   "linkAttachment": "链接附件", | ||||||
|  |   "fileIdCannotBeEmpty": "文件 ID 不能为空", | ||||||
|  |   "fileIdLinkHint": "还没有上传到 Solar Network?点击此处打开 Solar Network Drive,自定义您的上传内容。", | ||||||
|  |   "failedToFetchFile": "获取文件失败:{}", | ||||||
|  |   "callLeave": "离开", | ||||||
|  |   "callEnd": "挂断通话", | ||||||
|  |   "postType": "帖子类型", | ||||||
|  |   "articleAttachmentHint": "附件必须上传并插入到文章主体中才能显示出来。", | ||||||
|  |   "postVisibility": "可见性", | ||||||
|  |   "currentMembershipMember": "恒星计划成员 · {}", | ||||||
|  |   "membershipPriceStellar": "需要用户等级 3+,每月价格 1200 NSP", | ||||||
|  |   "membershipPriceNova": "需要用户等级 6+,每月价格 2400 NSP", | ||||||
|  |   "membershipPriceSupernova": "需要用户等级 9+,每月价格 3600 NSP", | ||||||
|  |   "sharePostPhoto": "通过图片分享帖子", | ||||||
|  |   "wouldYouLikeToNavigateToChat": "你想要前往聊天页面吗?", | ||||||
|  |   "abuseReports": "举报", | ||||||
|  |   "discoverRealms": "发现领域", | ||||||
|  |   "discoverPublishers": "发现发布者", | ||||||
|  |   "membershipCancel": "取消会员订阅", | ||||||
|  |   "membershipCancelConfirm": "你确定要取消会员订阅吗?", | ||||||
|  |   "membershipCancelHint": "你确定要取消会员订阅吗?你将不会再次被扣费。你的会员资格将在当前计费周期结束前保持有效。并且你将无法重新订阅,直到当前订阅结束。", | ||||||
|  |   "membershipCancelSuccess": "你的会员订阅已成功取消。", | ||||||
|   "aboutScreenTitle": "关于", |   "aboutScreenTitle": "关于", | ||||||
|   "aboutScreenVersionInfo": "版本 {} ({})", |   "aboutScreenVersionInfo": "版本 {} ({})", | ||||||
|   "aboutScreenAppInfoSectionTitle": "应用信息", |   "aboutScreenAppInfoSectionTitle": "应用信息", | ||||||
| @@ -696,7 +711,7 @@ | |||||||
|   "aboutScreenDeveloperSectionTitle": "开发者", |   "aboutScreenDeveloperSectionTitle": "开发者", | ||||||
|   "aboutScreenContactUsTitle": "联系我们", |   "aboutScreenContactUsTitle": "联系我们", | ||||||
|   "aboutScreenLicenseTitle": "许可", |   "aboutScreenLicenseTitle": "许可", | ||||||
|   "aboutScreenLicenseContent": "GNU Affero General Public License v3.0", |   "aboutScreenLicenseContent": "无法翻译", | ||||||
|   "aboutScreenCopyright": "版权所有 © Solsynth {}", |   "aboutScreenCopyright": "版权所有 © Solsynth {}", | ||||||
|   "aboutScreenMadeWith": "由 Solar Network 团队用 ❤︎️ 制作", |   "aboutScreenMadeWith": "由 Solar Network 团队用 ❤︎️ 制作", | ||||||
|   "aboutScreenFailedToLoadPackageInfo": "无法加载包信息:{error}", |   "aboutScreenFailedToLoadPackageInfo": "无法加载包信息:{error}", | ||||||
| @@ -710,13 +725,13 @@ | |||||||
|   "aboutDeviceName": "设备名称", |   "aboutDeviceName": "设备名称", | ||||||
|   "aboutDeviceIdentifier": "设备标识符", |   "aboutDeviceIdentifier": "设备标识符", | ||||||
|   "donate": "捐赠", |   "donate": "捐赠", | ||||||
|   "donateDescription": "支持我们继续开发 Solar Network,并保持服务器运行。", |   "donateDescription": "支持我们继续开发 Solar Network,并维持服务器运行。", | ||||||
|   "fileId": "文件ID", |   "fileId": "文件 ID", | ||||||
|   "fileIdHint": "文件ID是你通过 Solar Network Drive 上传文件后获得的ID。", |   "fileIdHint": "文件 ID 是你通过 Solar Network Drive 上传文件后获得的 ID。", | ||||||
|   "translate": "翻译", |   "translate": "翻译", | ||||||
|   "translating": "正在翻译", |   "translating": "正在翻译", | ||||||
|   "translated": "已翻译", |   "translated": "已翻译", | ||||||
|   "reactionThumbUp": "点赞", |   "reactionThumbUp": "赞", | ||||||
|   "reactionThumbDown": "踩", |   "reactionThumbDown": "踩", | ||||||
|   "reactionJustOkay": "还行", |   "reactionJustOkay": "还行", | ||||||
|   "reactionCry": "哭", |   "reactionCry": "哭", | ||||||
| @@ -726,7 +741,7 @@ | |||||||
|   "reactionAngry": "生气", |   "reactionAngry": "生气", | ||||||
|   "reactionParty": "派对", |   "reactionParty": "派对", | ||||||
|   "reactionPray": "祈祷", |   "reactionPray": "祈祷", | ||||||
|   "reactionHeart": "心", |   "reactionHeart": "爱心", | ||||||
|   "selectMicrophone": "选择麦克风", |   "selectMicrophone": "选择麦克风", | ||||||
|   "selectCamera": "选择摄像头", |   "selectCamera": "选择摄像头", | ||||||
|   "switchedTo": "已切换到 {}", |   "switchedTo": "已切换到 {}", | ||||||
| @@ -741,19 +756,21 @@ | |||||||
|   "rename": "重命名", |   "rename": "重命名", | ||||||
|   "markAsSensitive": "标记为敏感", |   "markAsSensitive": "标记为敏感", | ||||||
|   "fileName": "文件名", |   "fileName": "文件名", | ||||||
|   "sensitiveCategories.language": "语言", |   "sensitiveCategories": { | ||||||
|   "sensitiveCategories.sexualContent": "色情内容", |     "language": "语言", | ||||||
|   "sensitiveCategories.violence": "暴力", |     "sexualContent": "色情内容", | ||||||
|   "sensitiveCategories.profanity": "亵渎", |     "violence": "暴力", | ||||||
|   "sensitiveCategories.hateSpeech": "仇恨言论", |     "profanity": "亵渎", | ||||||
|   "sensitiveCategories.racism": "种族主义", |     "hateSpeech": "仇恨言论", | ||||||
|   "sensitiveCategories.adultContent": "成人内容", |     "racism": "种族主义", | ||||||
|   "sensitiveCategories.drugAbuse": "药物滥用", |     "adultContent": "成人内容", | ||||||
|   "sensitiveCategories.alcoholAbuse": "酗酒", |     "drugAbuse": "药物滥用", | ||||||
|   "sensitiveCategories.gambling": "赌博", |     "alcoholAbuse": "酗酒", | ||||||
|   "sensitiveCategories.selfHarm": "自残", |     "gambling": "赌博", | ||||||
|   "sensitiveCategories.childAbuse": "虐待儿童", |     "selfHarm": "自残", | ||||||
|   "sensitiveCategories.other": "其他", |     "childAbuse": "虐待儿童", | ||||||
|  |     "other": "其他" | ||||||
|  |   }, | ||||||
|   "poll": "投票", |   "poll": "投票", | ||||||
|   "pollsRecent": "最近投票", |   "pollsRecent": "最近投票", | ||||||
|   "pollCreateNew": "创建新投票", |   "pollCreateNew": "创建新投票", | ||||||
| @@ -785,10 +802,46 @@ | |||||||
|   "links": "链接", |   "links": "链接", | ||||||
|   "addLink": "添加链接", |   "addLink": "添加链接", | ||||||
|   "linkKey": "链接名称", |   "linkKey": "链接名称", | ||||||
|   "linkValue": "URL", |   "linkValue": "链接", | ||||||
|   "debugOptions": "调试选项", |   "debugOptions": "调试选项", | ||||||
|   "joinedAt": "加入于 {}", |   "joinedAt": "加入于 {}", | ||||||
|   "searchAccounts": "搜索帐号……", |   "searchAccounts": "搜索帐号……", | ||||||
|   "webFeeds": "订阅源", |   "webFeeds": "订阅源", | ||||||
|   "polls": "投票" |   "polls": "投票", | ||||||
|  |   "sharePostSlogan": "加入 Solar Network 以便探索更多", | ||||||
|  |   "filesListAdditional": { | ||||||
|  |     "one": "+{} 个文件被折叠", | ||||||
|  |     "other": "+{} 个文件被折叠" | ||||||
|  |   }, | ||||||
|  |   "messageJumpNotLoaded": "引用的消息没有被加载,无法跳转。", | ||||||
|  |   "postUnlinkRealm": "不关联领域", | ||||||
|  |   "postSlug": "别名", | ||||||
|  |   "postSlugHint": "这个别名可以用于在网页通过 URL 浏览到你的帖子,它应该在同一发布者中是唯一。", | ||||||
|  |   "attachmentOnDevice": "离线", | ||||||
|  |   "attachmentOnCloud": "在线", | ||||||
|  |   "publisherCollabInvitation": "协作邀请", | ||||||
|  |   "publisherCollabInvitationCount": { | ||||||
|  |     "zero": "无邀请", | ||||||
|  |     "one": "{} 个可用邀请", | ||||||
|  |     "other": "{} 个可用邀请" | ||||||
|  |   }, | ||||||
|  |   "failedToLoadUserInfo": "加载用户信息失败", | ||||||
|  |   "failedToLoadUserInfoNetwork": "这看起来是个网络问题,你可以按下面的按钮来重试", | ||||||
|  |   "failedToLoadUserInfoUnauthorized": "看来您的会话已被注销或不再可用,如果您愿意,您仍然可以再次尝试获取用户信息。", | ||||||
|  |   "okay": "了解", | ||||||
|  |   "postDetails": "帖子详情", | ||||||
|  |   "mimeType": "类型", | ||||||
|  |   "fileSize": "大小", | ||||||
|  |   "fileHash": "哈希", | ||||||
|  |   "exifData": "EXIF 数据", | ||||||
|  |   "leveling": "等级", | ||||||
|  |   "levelingHistory": "经验记录", | ||||||
|  |   "stellarProgram": "恒星计划", | ||||||
|  |   "socialCredits": "社会信用点", | ||||||
|  |   "credits": "信用", | ||||||
|  |   "socialCreditsDescription": "社会信用是 Solar Network 评价用户的一种方式。它基于用户的行为和互动来计算。以 100 分为基准,分数越高表示用户在社区中的信誉越好。分数会随着时间的推移而变化,反映用户的最新行为。信用等级高的用户可以享受到更多的福利,反之的用户部份功能可能受到限制。", | ||||||
|  |   "socialCreditsLevelPoor": "糟糕", | ||||||
|  |   "socialCreditsLevelNormal": "正常", | ||||||
|  |   "socialCreditsLevelGood": "良好", | ||||||
|  |   "socialCreditsLevelExcellent": "优秀" | ||||||
| } | } | ||||||
|   | |||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								assets/icons/icon.ico
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								assets/icons/icon.ico
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 108 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/images/media-offline.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								assets/images/media-offline.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 307 KiB | 
							
								
								
									
										109
									
								
								ios/Podfile.lock
									
									
									
									
									
								
							
							
						
						
									
										109
									
								
								ios/Podfile.lock
									
									
									
									
									
								
							| @@ -42,22 +42,62 @@ PODS: | |||||||
|     - Flutter |     - Flutter | ||||||
|   - Firebase/CoreOnly (12.0.0): |   - Firebase/CoreOnly (12.0.0): | ||||||
|     - FirebaseCore (~> 12.0.0) |     - FirebaseCore (~> 12.0.0) | ||||||
|  |   - Firebase/Crashlytics (12.0.0): | ||||||
|  |     - Firebase/CoreOnly | ||||||
|  |     - FirebaseCrashlytics (~> 12.0.0) | ||||||
|   - Firebase/Messaging (12.0.0): |   - Firebase/Messaging (12.0.0): | ||||||
|     - Firebase/CoreOnly |     - Firebase/CoreOnly | ||||||
|     - FirebaseMessaging (~> 12.0.0) |     - FirebaseMessaging (~> 12.0.0) | ||||||
|  |   - firebase_analytics (12.0.0): | ||||||
|  |     - firebase_core | ||||||
|  |     - FirebaseAnalytics (= 12.0.0) | ||||||
|  |     - Flutter | ||||||
|   - firebase_core (4.0.0): |   - firebase_core (4.0.0): | ||||||
|     - Firebase/CoreOnly (= 12.0.0) |     - Firebase/CoreOnly (= 12.0.0) | ||||||
|     - Flutter |     - Flutter | ||||||
|  |   - firebase_crashlytics (5.0.0): | ||||||
|  |     - Firebase/Crashlytics (= 12.0.0) | ||||||
|  |     - firebase_core | ||||||
|  |     - Flutter | ||||||
|   - firebase_messaging (16.0.0): |   - firebase_messaging (16.0.0): | ||||||
|     - Firebase/Messaging (= 12.0.0) |     - Firebase/Messaging (= 12.0.0) | ||||||
|     - firebase_core |     - firebase_core | ||||||
|     - Flutter |     - Flutter | ||||||
|  |   - FirebaseAnalytics (12.0.0): | ||||||
|  |     - FirebaseAnalytics/Default (= 12.0.0) | ||||||
|  |     - FirebaseCore (~> 12.0.0) | ||||||
|  |     - FirebaseInstallations (~> 12.0.0) | ||||||
|  |     - GoogleUtilities/AppDelegateSwizzler (~> 8.1) | ||||||
|  |     - GoogleUtilities/MethodSwizzler (~> 8.1) | ||||||
|  |     - GoogleUtilities/Network (~> 8.1) | ||||||
|  |     - "GoogleUtilities/NSData+zlib (~> 8.1)" | ||||||
|  |     - nanopb (~> 3.30910.0) | ||||||
|  |   - FirebaseAnalytics/Default (12.0.0): | ||||||
|  |     - FirebaseCore (~> 12.0.0) | ||||||
|  |     - FirebaseInstallations (~> 12.0.0) | ||||||
|  |     - GoogleAppMeasurement/Default (= 12.0.0) | ||||||
|  |     - GoogleUtilities/AppDelegateSwizzler (~> 8.1) | ||||||
|  |     - GoogleUtilities/MethodSwizzler (~> 8.1) | ||||||
|  |     - GoogleUtilities/Network (~> 8.1) | ||||||
|  |     - "GoogleUtilities/NSData+zlib (~> 8.1)" | ||||||
|  |     - nanopb (~> 3.30910.0) | ||||||
|   - FirebaseCore (12.0.0): |   - FirebaseCore (12.0.0): | ||||||
|     - FirebaseCoreInternal (~> 12.0.0) |     - FirebaseCoreInternal (~> 12.0.0) | ||||||
|     - GoogleUtilities/Environment (~> 8.1) |     - GoogleUtilities/Environment (~> 8.1) | ||||||
|     - GoogleUtilities/Logger (~> 8.1) |     - GoogleUtilities/Logger (~> 8.1) | ||||||
|  |   - FirebaseCoreExtension (12.0.0): | ||||||
|  |     - FirebaseCore (~> 12.0.0) | ||||||
|   - FirebaseCoreInternal (12.0.0): |   - FirebaseCoreInternal (12.0.0): | ||||||
|     - "GoogleUtilities/NSData+zlib (~> 8.1)" |     - "GoogleUtilities/NSData+zlib (~> 8.1)" | ||||||
|  |   - FirebaseCrashlytics (12.0.0): | ||||||
|  |     - FirebaseCore (~> 12.0.0) | ||||||
|  |     - FirebaseInstallations (~> 12.0.0) | ||||||
|  |     - FirebaseRemoteConfigInterop (~> 12.0.0) | ||||||
|  |     - FirebaseSessions (~> 12.0.0) | ||||||
|  |     - GoogleDataTransport (~> 10.1) | ||||||
|  |     - GoogleUtilities/Environment (~> 8.1) | ||||||
|  |     - nanopb (~> 3.30910.0) | ||||||
|  |     - PromisesObjC (~> 2.4) | ||||||
|   - FirebaseInstallations (12.0.0): |   - FirebaseInstallations (12.0.0): | ||||||
|     - FirebaseCore (~> 12.0.0) |     - FirebaseCore (~> 12.0.0) | ||||||
|     - GoogleUtilities/Environment (~> 8.1) |     - GoogleUtilities/Environment (~> 8.1) | ||||||
| @@ -72,6 +112,16 @@ PODS: | |||||||
|     - 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) | ||||||
|  |   - FirebaseSessions (12.0.0): | ||||||
|  |     - FirebaseCore (~> 12.0.0) | ||||||
|  |     - FirebaseCoreExtension (~> 12.0.0) | ||||||
|  |     - FirebaseInstallations (~> 12.0.0) | ||||||
|  |     - GoogleDataTransport (~> 10.1) | ||||||
|  |     - GoogleUtilities/Environment (~> 8.1) | ||||||
|  |     - GoogleUtilities/UserDefaults (~> 8.1) | ||||||
|  |     - nanopb (~> 3.30910.0) | ||||||
|  |     - PromisesSwift (~> 2.1) | ||||||
|   - Flutter (1.0.0) |   - Flutter (1.0.0) | ||||||
|   - flutter_app_update (0.0.1): |   - flutter_app_update (0.0.1): | ||||||
|     - Flutter |     - Flutter | ||||||
| @@ -101,6 +151,32 @@ PODS: | |||||||
|   - gal (1.0.0): |   - gal (1.0.0): | ||||||
|     - Flutter |     - Flutter | ||||||
|     - FlutterMacOS |     - FlutterMacOS | ||||||
|  |   - GoogleAdsOnDeviceConversion (2.1.0): | ||||||
|  |     - GoogleUtilities/Logger (~> 8.1) | ||||||
|  |     - GoogleUtilities/Network (~> 8.1) | ||||||
|  |     - nanopb (~> 3.30910.0) | ||||||
|  |   - GoogleAppMeasurement/Core (12.0.0): | ||||||
|  |     - GoogleUtilities/AppDelegateSwizzler (~> 8.1) | ||||||
|  |     - GoogleUtilities/MethodSwizzler (~> 8.1) | ||||||
|  |     - GoogleUtilities/Network (~> 8.1) | ||||||
|  |     - "GoogleUtilities/NSData+zlib (~> 8.1)" | ||||||
|  |     - nanopb (~> 3.30910.0) | ||||||
|  |   - GoogleAppMeasurement/Default (12.0.0): | ||||||
|  |     - GoogleAdsOnDeviceConversion (= 2.1.0) | ||||||
|  |     - GoogleAppMeasurement/Core (= 12.0.0) | ||||||
|  |     - GoogleAppMeasurement/IdentitySupport (= 12.0.0) | ||||||
|  |     - GoogleUtilities/AppDelegateSwizzler (~> 8.1) | ||||||
|  |     - GoogleUtilities/MethodSwizzler (~> 8.1) | ||||||
|  |     - GoogleUtilities/Network (~> 8.1) | ||||||
|  |     - "GoogleUtilities/NSData+zlib (~> 8.1)" | ||||||
|  |     - nanopb (~> 3.30910.0) | ||||||
|  |   - GoogleAppMeasurement/IdentitySupport (12.0.0): | ||||||
|  |     - GoogleAppMeasurement/Core (= 12.0.0) | ||||||
|  |     - GoogleUtilities/AppDelegateSwizzler (~> 8.1) | ||||||
|  |     - GoogleUtilities/MethodSwizzler (~> 8.1) | ||||||
|  |     - GoogleUtilities/Network (~> 8.1) | ||||||
|  |     - "GoogleUtilities/NSData+zlib (~> 8.1)" | ||||||
|  |     - nanopb (~> 3.30910.0) | ||||||
|   - GoogleDataTransport (10.1.0): |   - GoogleDataTransport (10.1.0): | ||||||
|     - nanopb (~> 3.30910.0) |     - nanopb (~> 3.30910.0) | ||||||
|     - PromisesObjC (~> 2.4) |     - PromisesObjC (~> 2.4) | ||||||
| @@ -114,6 +190,9 @@ PODS: | |||||||
|   - GoogleUtilities/Logger (8.1.0): |   - GoogleUtilities/Logger (8.1.0): | ||||||
|     - GoogleUtilities/Environment |     - GoogleUtilities/Environment | ||||||
|     - GoogleUtilities/Privacy |     - GoogleUtilities/Privacy | ||||||
|  |   - GoogleUtilities/MethodSwizzler (8.1.0): | ||||||
|  |     - GoogleUtilities/Logger | ||||||
|  |     - GoogleUtilities/Privacy | ||||||
|   - GoogleUtilities/Network (8.1.0): |   - GoogleUtilities/Network (8.1.0): | ||||||
|     - GoogleUtilities/Logger |     - GoogleUtilities/Logger | ||||||
|     - "GoogleUtilities/NSData+zlib" |     - "GoogleUtilities/NSData+zlib" | ||||||
| @@ -162,9 +241,11 @@ PODS: | |||||||
|   - pointer_interceptor_ios (0.0.1): |   - pointer_interceptor_ios (0.0.1): | ||||||
|     - Flutter |     - Flutter | ||||||
|   - PromisesObjC (2.4.0) |   - PromisesObjC (2.4.0) | ||||||
|  |   - PromisesSwift (2.4.0): | ||||||
|  |     - PromisesObjC (= 2.4.0) | ||||||
|   - receive_sharing_intent (1.8.1): |   - receive_sharing_intent (1.8.1): | ||||||
|     - Flutter |     - Flutter | ||||||
|   - record_ios (1.0.0): |   - record_ios (1.1.0): | ||||||
|     - Flutter |     - Flutter | ||||||
|   - SAMKeychain (1.5.3) |   - SAMKeychain (1.5.3) | ||||||
|   - SDWebImage (5.21.1): |   - SDWebImage (5.21.1): | ||||||
| @@ -222,7 +303,9 @@ DEPENDENCIES: | |||||||
|   - croppy (from `.symlinks/plugins/croppy/ios`) |   - croppy (from `.symlinks/plugins/croppy/ios`) | ||||||
|   - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) |   - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) | ||||||
|   - file_picker (from `.symlinks/plugins/file_picker/ios`) |   - file_picker (from `.symlinks/plugins/file_picker/ios`) | ||||||
|  |   - firebase_analytics (from `.symlinks/plugins/firebase_analytics/ios`) | ||||||
|   - firebase_core (from `.symlinks/plugins/firebase_core/ios`) |   - firebase_core (from `.symlinks/plugins/firebase_core/ios`) | ||||||
|  |   - firebase_crashlytics (from `.symlinks/plugins/firebase_crashlytics/ios`) | ||||||
|   - firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`) |   - firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`) | ||||||
|   - Flutter (from `Flutter`) |   - Flutter (from `Flutter`) | ||||||
|   - flutter_app_update (from `.symlinks/plugins/flutter_app_update/ios`) |   - flutter_app_update (from `.symlinks/plugins/flutter_app_update/ios`) | ||||||
| @@ -265,16 +348,24 @@ SPEC REPOS: | |||||||
|     - DKImagePickerController |     - DKImagePickerController | ||||||
|     - DKPhotoGallery |     - DKPhotoGallery | ||||||
|     - Firebase |     - Firebase | ||||||
|  |     - FirebaseAnalytics | ||||||
|     - FirebaseCore |     - FirebaseCore | ||||||
|  |     - FirebaseCoreExtension | ||||||
|     - FirebaseCoreInternal |     - FirebaseCoreInternal | ||||||
|  |     - FirebaseCrashlytics | ||||||
|     - FirebaseInstallations |     - FirebaseInstallations | ||||||
|     - FirebaseMessaging |     - FirebaseMessaging | ||||||
|  |     - FirebaseRemoteConfigInterop | ||||||
|  |     - FirebaseSessions | ||||||
|  |     - GoogleAdsOnDeviceConversion | ||||||
|  |     - GoogleAppMeasurement | ||||||
|     - GoogleDataTransport |     - GoogleDataTransport | ||||||
|     - GoogleUtilities |     - GoogleUtilities | ||||||
|     - Kingfisher |     - Kingfisher | ||||||
|     - nanopb |     - nanopb | ||||||
|     - OrderedSet |     - OrderedSet | ||||||
|     - PromisesObjC |     - PromisesObjC | ||||||
|  |     - PromisesSwift | ||||||
|     - SAMKeychain |     - SAMKeychain | ||||||
|     - SDWebImage |     - SDWebImage | ||||||
|     - sqlite3 |     - sqlite3 | ||||||
| @@ -290,8 +381,12 @@ EXTERNAL SOURCES: | |||||||
|     :path: ".symlinks/plugins/device_info_plus/ios" |     :path: ".symlinks/plugins/device_info_plus/ios" | ||||||
|   file_picker: |   file_picker: | ||||||
|     :path: ".symlinks/plugins/file_picker/ios" |     :path: ".symlinks/plugins/file_picker/ios" | ||||||
|  |   firebase_analytics: | ||||||
|  |     :path: ".symlinks/plugins/firebase_analytics/ios" | ||||||
|   firebase_core: |   firebase_core: | ||||||
|     :path: ".symlinks/plugins/firebase_core/ios" |     :path: ".symlinks/plugins/firebase_core/ios" | ||||||
|  |   firebase_crashlytics: | ||||||
|  |     :path: ".symlinks/plugins/firebase_crashlytics/ios" | ||||||
|   firebase_messaging: |   firebase_messaging: | ||||||
|     :path: ".symlinks/plugins/firebase_messaging/ios" |     :path: ".symlinks/plugins/firebase_messaging/ios" | ||||||
|   Flutter: |   Flutter: | ||||||
| @@ -370,12 +465,19 @@ SPEC CHECKSUMS: | |||||||
|   DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60 |   DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60 | ||||||
|   file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be |   file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be | ||||||
|   Firebase: 800d487043c0557d9faed71477a38d9aafb08a41 |   Firebase: 800d487043c0557d9faed71477a38d9aafb08a41 | ||||||
|  |   firebase_analytics: cd56fc56f75c1df30a6ff5290cd56e230996a76d | ||||||
|   firebase_core: 633e1851ffe1b9ab875f6467a4f574c79cef02e4 |   firebase_core: 633e1851ffe1b9ab875f6467a4f574c79cef02e4 | ||||||
|  |   firebase_crashlytics: 2c6c1a17900a38081d938330e9f48e60ec5b255d | ||||||
|   firebase_messaging: d17feef781edc84ebefe62624fb384358ad96361 |   firebase_messaging: d17feef781edc84ebefe62624fb384358ad96361 | ||||||
|  |   FirebaseAnalytics: 6d790cd1b159b4eb61a99948df0934ce505a34f7 | ||||||
|   FirebaseCore: 055f4ab117d5964158c833f3d5e7ec6d91648d4a |   FirebaseCore: 055f4ab117d5964158c833f3d5e7ec6d91648d4a | ||||||
|  |   FirebaseCoreExtension: 639afb3de6abd611952be78a794c54a47fa0f361 | ||||||
|   FirebaseCoreInternal: dedc28e569a4be85f38f3d6af1070a2e12018d55 |   FirebaseCoreInternal: dedc28e569a4be85f38f3d6af1070a2e12018d55 | ||||||
|  |   FirebaseCrashlytics: db75aa0cab8d00f68406fa247c32fe17ade884d7 | ||||||
|   FirebaseInstallations: d4c7c958f99c8860d7fcece786314ae790e2f988 |   FirebaseInstallations: d4c7c958f99c8860d7fcece786314ae790e2f988 | ||||||
|   FirebaseMessaging: af49f8d7c0a3d2a017d9302c80946f45a7777dde |   FirebaseMessaging: af49f8d7c0a3d2a017d9302c80946f45a7777dde | ||||||
|  |   FirebaseRemoteConfigInterop: bfa0ea72ba3dc5af739777296424e46bd6f42613 | ||||||
|  |   FirebaseSessions: 4e784acda213108aafef536535cdfc03504acc42 | ||||||
|   Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 |   Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 | ||||||
|   flutter_app_update: 816fdb2e30e4832a7c45e3f108d391c42ef040a9 |   flutter_app_update: 816fdb2e30e4832a7c45e3f108d391c42ef040a9 | ||||||
|   flutter_inappwebview_ios: b89ba3482b96fb25e00c967aae065701b66e9b99 |   flutter_inappwebview_ios: b89ba3482b96fb25e00c967aae065701b66e9b99 | ||||||
| @@ -387,6 +489,8 @@ SPEC CHECKSUMS: | |||||||
|   flutter_udid: f7c3884e6ec2951efe4f9de082257fc77c4d15e9 |   flutter_udid: f7c3884e6ec2951efe4f9de082257fc77c4d15e9 | ||||||
|   flutter_webrtc: 6f7da106613d52ade777d5b4875a43f48c28b457 |   flutter_webrtc: 6f7da106613d52ade777d5b4875a43f48c28b457 | ||||||
|   gal: baecd024ebfd13c441269ca7404792a7152fde89 |   gal: baecd024ebfd13c441269ca7404792a7152fde89 | ||||||
|  |   GoogleAdsOnDeviceConversion: 2be6297a4f048459e0ae17fad9bfd2844e10cf64 | ||||||
|  |   GoogleAppMeasurement: 8f6ab04ad6ae493b53fcf56bd26323fb2f1384f3 | ||||||
|   GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7 |   GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7 | ||||||
|   GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1 |   GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1 | ||||||
|   image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a |   image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a | ||||||
| @@ -404,8 +508,9 @@ SPEC CHECKSUMS: | |||||||
|   path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 |   path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 | ||||||
|   pointer_interceptor_ios: ec847ef8b0915778bed2b2cef636f4d177fa8eed |   pointer_interceptor_ios: ec847ef8b0915778bed2b2cef636f4d177fa8eed | ||||||
|   PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 |   PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 | ||||||
|  |   PromisesSwift: 9d77319bbe72ebf6d872900551f7eeba9bce2851 | ||||||
|   receive_sharing_intent: 222384f00ffe7e952bbfabaa9e3967cb87e5fe00 |   receive_sharing_intent: 222384f00ffe7e952bbfabaa9e3967cb87e5fe00 | ||||||
|   record_ios: fee1c924aa4879b882ebca2b4bce6011bcfc3d8b |   record_ios: f75fa1d57f840012775c0e93a38a7f3ceea1a374 | ||||||
|   SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c |   SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c | ||||||
|   SDWebImage: f29024626962457f3470184232766516dee8dfea |   SDWebImage: f29024626962457f3470184232766516dee8dfea | ||||||
|   share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a |   share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a | ||||||
|   | |||||||
| @@ -439,6 +439,7 @@ | |||||||
| 				3B06AD1E1E4923F5004D2608 /* Thin Binary */, | 				3B06AD1E1E4923F5004D2608 /* Thin Binary */, | ||||||
| 				8C0351B03869BBF493808288 /* [CP] Embed Pods Frameworks */, | 				8C0351B03869BBF493808288 /* [CP] Embed Pods Frameworks */, | ||||||
| 				5E7D6EF29B671AC7EDBA5649 /* [CP] Copy Pods Resources */, | 				5E7D6EF29B671AC7EDBA5649 /* [CP] Copy Pods Resources */, | ||||||
|  | 				E86CDE9D6464F4F52B910856 /* FlutterFire: "flutterfire upload-crashlytics-symbols" */, | ||||||
| 			); | 			); | ||||||
| 			buildRules = ( | 			buildRules = ( | ||||||
| 			); | 			); | ||||||
| @@ -682,6 +683,24 @@ | |||||||
| 			shellPath = /bin/sh; | 			shellPath = /bin/sh; | ||||||
| 			shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; | 			shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; | ||||||
| 		}; | 		}; | ||||||
|  | 		E86CDE9D6464F4F52B910856 /* FlutterFire: "flutterfire upload-crashlytics-symbols" */ = { | ||||||
|  | 			isa = PBXShellScriptBuildPhase; | ||||||
|  | 			buildActionMask = 2147483647; | ||||||
|  | 			files = ( | ||||||
|  | 			); | ||||||
|  | 			inputFileListPaths = ( | ||||||
|  | 			); | ||||||
|  | 			inputPaths = ( | ||||||
|  | 			); | ||||||
|  | 			name = "FlutterFire: \"flutterfire upload-crashlytics-symbols\""; | ||||||
|  | 			outputFileListPaths = ( | ||||||
|  | 			); | ||||||
|  | 			outputPaths = ( | ||||||
|  | 			); | ||||||
|  | 			runOnlyForDeploymentPostprocessing = 0; | ||||||
|  | 			shellPath = /bin/sh; | ||||||
|  | 			shellScript = "\n#!/bin/bash\nPATH=\"${PATH}:$FLUTTER_ROOT/bin:${PUB_CACHE}/bin:$HOME/.pub-cache/bin\"\n\nif [ -z \"$PODS_ROOT\" ] || [ ! -d \"$PODS_ROOT/FirebaseCrashlytics\" ]; then\n  # Cannot use \"BUILD_DIR%/Build/*\" as per Firebase documentation, it points to \"flutter-project/build/ios/*\" path which doesn't have run script\n  DERIVED_DATA_PATH=$(echo \"$BUILD_ROOT\" | sed -E 's|(.*DerivedData/[^/]+).*|\\1|')\n  PATH_TO_CRASHLYTICS_UPLOAD_SCRIPT=\"${DERIVED_DATA_PATH}/SourcePackages/checkouts/firebase-ios-sdk/Crashlytics/run\"\nelse\n  PATH_TO_CRASHLYTICS_UPLOAD_SCRIPT=\"$PODS_ROOT/FirebaseCrashlytics/run\"\nfi\n\n# Command to upload symbols script used to upload symbols to Firebase server\nflutterfire upload-crashlytics-symbols --upload-symbols-script-path=\"$PATH_TO_CRASHLYTICS_UPLOAD_SCRIPT\" --platform=ios --apple-project-path=\"${SRCROOT}\" --env-platform-name=\"${PLATFORM_NAME}\" --env-configuration=\"${CONFIGURATION}\" --env-project-dir=\"${PROJECT_DIR}\" --env-built-products-dir=\"${BUILT_PRODUCTS_DIR}\" --env-dwarf-dsym-folder-path=\"${DWARF_DSYM_FOLDER_PATH}\" --env-dwarf-dsym-file-name=\"${DWARF_DSYM_FILE_NAME}\" --env-infoplist-path=\"${INFOPLIST_PATH}\" --default-config=default\n"; | ||||||
|  | 		}; | ||||||
| 		E947029FCA058878F9B63890 /* [CP] Check Pods Manifest.lock */ = { | 		E947029FCA058878F9B63890 /* [CP] Check Pods Manifest.lock */ = { | ||||||
| 			isa = PBXShellScriptBuildPhase; | 			isa = PBXShellScriptBuildPhase; | ||||||
| 			buildActionMask = 2147483647; | 			buildActionMask = 2147483647; | ||||||
|   | |||||||
| @@ -1,484 +0,0 @@ | |||||||
| import 'package:dio/dio.dart'; |  | ||||||
| import 'package:island/database/drift_db.dart'; |  | ||||||
| import 'package:island/database/message.dart'; |  | ||||||
| import 'package:island/models/chat.dart'; |  | ||||||
| import 'package:island/models/file.dart'; |  | ||||||
| import 'package:island/services/file.dart'; |  | ||||||
| import 'package:island/widgets/alert.dart'; |  | ||||||
| import 'package:uuid/uuid.dart'; |  | ||||||
|  |  | ||||||
| class MessageRepository { |  | ||||||
|   final SnChatRoom room; |  | ||||||
|   final SnChatMember identity; |  | ||||||
|   final Dio _apiClient; |  | ||||||
|   final AppDatabase _database; |  | ||||||
|  |  | ||||||
|   final Map<String, LocalChatMessage> pendingMessages = {}; |  | ||||||
|   final Map<String, Map<int, double>> fileUploadProgress = {}; |  | ||||||
|   int? _totalCount; |  | ||||||
|  |  | ||||||
|   MessageRepository(this.room, this.identity, this._apiClient, this._database); |  | ||||||
|  |  | ||||||
|   Future<LocalChatMessage?> getLastMessages() async { |  | ||||||
|     final dbMessages = await _database.getMessagesForRoom( |  | ||||||
|       room.id, |  | ||||||
|       offset: 0, |  | ||||||
|       limit: 1, |  | ||||||
|     ); |  | ||||||
|  |  | ||||||
|     if (dbMessages.isEmpty) { |  | ||||||
|       return null; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     return _database.companionToMessage(dbMessages.first); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   Future<bool> syncMessages() async { |  | ||||||
|     final lastMessage = await getLastMessages(); |  | ||||||
|     if (lastMessage == null) return false; |  | ||||||
|     try { |  | ||||||
|       final resp = await _apiClient.post( |  | ||||||
|         '/sphere/chat/${room.id}/sync', |  | ||||||
|         data: { |  | ||||||
|           'last_sync_timestamp': |  | ||||||
|               lastMessage.toRemoteMessage().updatedAt.millisecondsSinceEpoch, |  | ||||||
|         }, |  | ||||||
|       ); |  | ||||||
|  |  | ||||||
|       final response = MessageSyncResponse.fromJson(resp.data); |  | ||||||
|       for (final change in response.changes) { |  | ||||||
|         switch (change.action) { |  | ||||||
|           case MessageChangeAction.create: |  | ||||||
|             await receiveMessage(change.message!); |  | ||||||
|             break; |  | ||||||
|           case MessageChangeAction.update: |  | ||||||
|             await receiveMessageUpdate(change.message!); |  | ||||||
|             break; |  | ||||||
|           case MessageChangeAction.delete: |  | ||||||
|             await receiveMessageDeletion(change.messageId.toString()); |  | ||||||
|             break; |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|     } catch (err) { |  | ||||||
|       showErrorAlert(err); |  | ||||||
|     } |  | ||||||
|     return true; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   Future<List<LocalChatMessage>> listMessages({ |  | ||||||
|     int offset = 0, |  | ||||||
|     int take = 20, |  | ||||||
|     bool synced = false, |  | ||||||
|   }) async { |  | ||||||
|     try { |  | ||||||
|       // For initial load, fetch latest messages in the background to sync. |  | ||||||
|       if (offset == 0 && !synced) { |  | ||||||
|         // Not awaiting this is intentional, for a quicker UI response. |  | ||||||
|         // The UI should rely on a stream from the database to get updates. |  | ||||||
|         _fetchAndCacheMessages(room.id, offset: 0, take: take).catchError((_) { |  | ||||||
|           // Best effort, errors will be handled by later fetches. |  | ||||||
|           return <LocalChatMessage>[]; |  | ||||||
|         }); |  | ||||||
|       } |  | ||||||
|  |  | ||||||
|       final localMessages = await _getCachedMessages( |  | ||||||
|         room.id, |  | ||||||
|         offset: offset, |  | ||||||
|         take: take, |  | ||||||
|       ); |  | ||||||
|  |  | ||||||
|       // If local cache has messages, return them. This is the common case for scrolling up. |  | ||||||
|       if (localMessages.isNotEmpty) { |  | ||||||
|         return localMessages; |  | ||||||
|       } |  | ||||||
|  |  | ||||||
|       // If local cache is empty, we've probably reached the end of cached history. |  | ||||||
|       // Fetch from remote. This will also be hit on first load if cache is empty. |  | ||||||
|       return await _fetchAndCacheMessages(room.id, offset: offset, take: take); |  | ||||||
|     } catch (e) { |  | ||||||
|       // Final fallback to cache in case of network errors during fetch. |  | ||||||
|       final localMessages = await _getCachedMessages( |  | ||||||
|         room.id, |  | ||||||
|         offset: offset, |  | ||||||
|         take: take, |  | ||||||
|       ); |  | ||||||
|  |  | ||||||
|       if (localMessages.isNotEmpty) { |  | ||||||
|         return localMessages; |  | ||||||
|       } |  | ||||||
|       rethrow; |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   Future<List<LocalChatMessage>> _getCachedMessages( |  | ||||||
|     String roomId, { |  | ||||||
|     int offset = 0, |  | ||||||
|     int take = 20, |  | ||||||
|   }) async { |  | ||||||
|     // Get messages from local database |  | ||||||
|     final dbMessages = await _database.getMessagesForRoom( |  | ||||||
|       roomId, |  | ||||||
|       offset: offset, |  | ||||||
|       limit: take, |  | ||||||
|     ); |  | ||||||
|     final dbLocalMessages = |  | ||||||
|         dbMessages.map(_database.companionToMessage).toList(); |  | ||||||
|  |  | ||||||
|     // Combine with pending messages for the first page |  | ||||||
|     if (offset == 0) { |  | ||||||
|       final pendingForRoom = |  | ||||||
|           pendingMessages.values.where((msg) => msg.roomId == roomId).toList(); |  | ||||||
|  |  | ||||||
|       final allMessages = [...pendingForRoom, ...dbLocalMessages]; |  | ||||||
|       allMessages.sort((a, b) => b.createdAt.compareTo(a.createdAt)); |  | ||||||
|  |  | ||||||
|       // Remove duplicates by ID, preserving the order |  | ||||||
|       final uniqueMessages = <LocalChatMessage>[]; |  | ||||||
|       final seenIds = <String>{}; |  | ||||||
|       for (final message in allMessages) { |  | ||||||
|         if (seenIds.add(message.id)) { |  | ||||||
|           uniqueMessages.add(message); |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|       return uniqueMessages; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     return dbLocalMessages; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   Future<List<LocalChatMessage>> _fetchAndCacheMessages( |  | ||||||
|     String roomId, { |  | ||||||
|     int offset = 0, |  | ||||||
|     int take = 20, |  | ||||||
|   }) async { |  | ||||||
|     // Use cached total count if available, otherwise fetch it |  | ||||||
|     if (_totalCount == null) { |  | ||||||
|       final response = await _apiClient.get( |  | ||||||
|         '/sphere/chat/$roomId/messages', |  | ||||||
|         queryParameters: {'offset': 0, 'take': 1}, |  | ||||||
|       ); |  | ||||||
|       _totalCount = int.parse(response.headers['x-total']?.firstOrNull ?? '0'); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     if (offset >= _totalCount!) { |  | ||||||
|       return []; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     final response = await _apiClient.get( |  | ||||||
|       '/sphere/chat/$roomId/messages', |  | ||||||
|       queryParameters: {'offset': offset, 'take': take}, |  | ||||||
|     ); |  | ||||||
|  |  | ||||||
|     final List<dynamic> data = response.data; |  | ||||||
|     // Update total count from response headers |  | ||||||
|     _totalCount = int.parse(response.headers['x-total']?.firstOrNull ?? '0'); |  | ||||||
|  |  | ||||||
|     final messages = |  | ||||||
|         data.map((json) { |  | ||||||
|           final remoteMessage = SnChatMessage.fromJson(json); |  | ||||||
|           return LocalChatMessage.fromRemoteMessage( |  | ||||||
|             remoteMessage, |  | ||||||
|             MessageStatus.sent, |  | ||||||
|           ); |  | ||||||
|         }).toList(); |  | ||||||
|  |  | ||||||
|     for (final message in messages) { |  | ||||||
|       await _database.saveMessage(_database.messageToCompanion(message)); |  | ||||||
|       if (message.nonce != null) { |  | ||||||
|         pendingMessages.removeWhere( |  | ||||||
|           (_, pendingMsg) => pendingMsg.nonce == message.nonce, |  | ||||||
|         ); |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     return messages; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   Future<LocalChatMessage> sendMessage( |  | ||||||
|     String token, |  | ||||||
|     String baseUrl, |  | ||||||
|     String roomId, |  | ||||||
|     String content, |  | ||||||
|     String nonce, { |  | ||||||
|     required List<UniversalFile> attachments, |  | ||||||
|     Map<String, dynamic>? meta, |  | ||||||
|     SnChatMessage? replyingTo, |  | ||||||
|     SnChatMessage? forwardingTo, |  | ||||||
|     SnChatMessage? editingTo, |  | ||||||
|     Function(LocalChatMessage)? onPending, |  | ||||||
|     Function(String, Map<int, double>)? onProgress, |  | ||||||
|   }) async { |  | ||||||
|     // Generate a unique nonce for this message |  | ||||||
|     final nonce = const Uuid().v4(); |  | ||||||
|  |  | ||||||
|     // Create a local message with pending status |  | ||||||
|     final mockMessage = SnChatMessage( |  | ||||||
|       id: 'pending_$nonce', |  | ||||||
|       chatRoomId: roomId, |  | ||||||
|       senderId: identity.id, |  | ||||||
|       content: content, |  | ||||||
|       createdAt: DateTime.now(), |  | ||||||
|       updatedAt: DateTime.now(), |  | ||||||
|       nonce: nonce, |  | ||||||
|       sender: identity, |  | ||||||
|     ); |  | ||||||
|  |  | ||||||
|     final localMessage = LocalChatMessage.fromRemoteMessage( |  | ||||||
|       mockMessage, |  | ||||||
|       MessageStatus.pending, |  | ||||||
|     ); |  | ||||||
|  |  | ||||||
|     // Store in memory and database |  | ||||||
|     pendingMessages[localMessage.id] = localMessage; |  | ||||||
|     fileUploadProgress[localMessage.id] = {}; |  | ||||||
|     await _database.saveMessage(_database.messageToCompanion(localMessage)); |  | ||||||
|     onPending?.call(localMessage); |  | ||||||
|  |  | ||||||
|     try { |  | ||||||
|       var cloudAttachments = List.empty(growable: true); |  | ||||||
|       // Upload files |  | ||||||
|       for (var idx = 0; idx < attachments.length; idx++) { |  | ||||||
|         final cloudFile = |  | ||||||
|             await putMediaToCloud( |  | ||||||
|               fileData: attachments[idx], |  | ||||||
|               atk: token, |  | ||||||
|               baseUrl: baseUrl, |  | ||||||
|               filename: attachments[idx].data.name ?? 'Post media', |  | ||||||
|               mimetype: |  | ||||||
|                   attachments[idx].data.mimeType ?? |  | ||||||
|                   switch (attachments[idx].type) { |  | ||||||
|                     UniversalFileType.image => 'image/unknown', |  | ||||||
|                     UniversalFileType.video => 'video/unknown', |  | ||||||
|                     UniversalFileType.audio => 'audio/unknown', |  | ||||||
|                     UniversalFileType.file => 'application/octet-stream', |  | ||||||
|                   }, |  | ||||||
|               onProgress: (progress, _) { |  | ||||||
|                 fileUploadProgress[localMessage.id]?[idx] = progress; |  | ||||||
|                 onProgress?.call( |  | ||||||
|                   localMessage.id, |  | ||||||
|                   fileUploadProgress[localMessage.id] ?? {}, |  | ||||||
|                 ); |  | ||||||
|               }, |  | ||||||
|             ).future; |  | ||||||
|         if (cloudFile == null) { |  | ||||||
|           throw ArgumentError('Failed to upload the file...'); |  | ||||||
|         } |  | ||||||
|         cloudAttachments.add(cloudFile); |  | ||||||
|       } |  | ||||||
|  |  | ||||||
|       // Send to server |  | ||||||
|       final response = await _apiClient.request( |  | ||||||
|         editingTo == null |  | ||||||
|             ? '/sphere/chat/$roomId/messages' |  | ||||||
|             : '/sphere/chat/$roomId/messages/${editingTo.id}', |  | ||||||
|         data: { |  | ||||||
|           'content': content, |  | ||||||
|           'attachments_id': cloudAttachments.map((e) => e.id).toList(), |  | ||||||
|           'replied_message_id': replyingTo?.id, |  | ||||||
|           'forwarded_message_id': forwardingTo?.id, |  | ||||||
|           'meta': meta, |  | ||||||
|           'nonce': nonce, |  | ||||||
|         }, |  | ||||||
|         options: Options(method: editingTo == null ? 'POST' : 'PATCH'), |  | ||||||
|       ); |  | ||||||
|  |  | ||||||
|       // Update with server response |  | ||||||
|       final remoteMessage = SnChatMessage.fromJson(response.data); |  | ||||||
|       final updatedMessage = LocalChatMessage.fromRemoteMessage( |  | ||||||
|         remoteMessage, |  | ||||||
|         MessageStatus.sent, |  | ||||||
|       ); |  | ||||||
|  |  | ||||||
|       // Remove from pending and update in database |  | ||||||
|       pendingMessages.remove(localMessage.id); |  | ||||||
|       await _database.deleteMessage(localMessage.id); |  | ||||||
|       await _database.saveMessage(_database.messageToCompanion(updatedMessage)); |  | ||||||
|  |  | ||||||
|       return updatedMessage; |  | ||||||
|     } catch (e) { |  | ||||||
|       // Update status to failed |  | ||||||
|       localMessage.status = MessageStatus.failed; |  | ||||||
|       pendingMessages[localMessage.id] = localMessage; |  | ||||||
|       await _database.updateMessageStatus( |  | ||||||
|         localMessage.id, |  | ||||||
|         MessageStatus.failed, |  | ||||||
|       ); |  | ||||||
|       rethrow; |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   Future<LocalChatMessage> retryMessage(String pendingMessageId) async { |  | ||||||
|     final message = await getMessageById(pendingMessageId); |  | ||||||
|     if (message == null) { |  | ||||||
|       throw Exception('Message not found'); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // Update status back to pending |  | ||||||
|     message.status = MessageStatus.pending; |  | ||||||
|     pendingMessages[pendingMessageId] = message; |  | ||||||
|     await _database.updateMessageStatus( |  | ||||||
|       pendingMessageId, |  | ||||||
|       MessageStatus.pending, |  | ||||||
|     ); |  | ||||||
|  |  | ||||||
|     try { |  | ||||||
|       // Send to server |  | ||||||
|       var remoteMessage = message.toRemoteMessage(); |  | ||||||
|       final response = await _apiClient.post( |  | ||||||
|         '/sphere/chat/${message.roomId}/messages', |  | ||||||
|         data: { |  | ||||||
|           'content': remoteMessage.content, |  | ||||||
|           'attachments_id': remoteMessage.attachments, |  | ||||||
|           'meta': remoteMessage.meta, |  | ||||||
|           'nonce': message.nonce, |  | ||||||
|         }, |  | ||||||
|       ); |  | ||||||
|  |  | ||||||
|       // Update with server response |  | ||||||
|       remoteMessage = SnChatMessage.fromJson(response.data); |  | ||||||
|       final updatedMessage = LocalChatMessage.fromRemoteMessage( |  | ||||||
|         remoteMessage, |  | ||||||
|         MessageStatus.sent, |  | ||||||
|       ); |  | ||||||
|  |  | ||||||
|       // Remove from pending and update in database |  | ||||||
|       pendingMessages.remove(pendingMessageId); |  | ||||||
|       await _database.deleteMessage(pendingMessageId); |  | ||||||
|       await _database.saveMessage(_database.messageToCompanion(updatedMessage)); |  | ||||||
|  |  | ||||||
|       return updatedMessage; |  | ||||||
|     } catch (e) { |  | ||||||
|       // Update status to failed |  | ||||||
|       message.status = MessageStatus.failed; |  | ||||||
|       pendingMessages[pendingMessageId] = message; |  | ||||||
|       await _database.updateMessageStatus( |  | ||||||
|         pendingMessageId, |  | ||||||
|         MessageStatus.failed, |  | ||||||
|       ); |  | ||||||
|       rethrow; |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   Future<LocalChatMessage> receiveMessage(SnChatMessage remoteMessage) async { |  | ||||||
|     final localMessage = LocalChatMessage.fromRemoteMessage( |  | ||||||
|       remoteMessage, |  | ||||||
|       MessageStatus.sent, |  | ||||||
|     ); |  | ||||||
|  |  | ||||||
|     if (remoteMessage.nonce != null) { |  | ||||||
|       pendingMessages.removeWhere( |  | ||||||
|         (_, pendingMsg) => pendingMsg.nonce == remoteMessage.nonce, |  | ||||||
|       ); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     await _database.saveMessage(_database.messageToCompanion(localMessage)); |  | ||||||
|     return localMessage; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   Future<LocalChatMessage> receiveMessageUpdate( |  | ||||||
|     SnChatMessage remoteMessage, |  | ||||||
|   ) async { |  | ||||||
|     final localMessage = LocalChatMessage.fromRemoteMessage( |  | ||||||
|       remoteMessage, |  | ||||||
|       MessageStatus.sent, |  | ||||||
|     ); |  | ||||||
|  |  | ||||||
|     await _database.updateMessage(_database.messageToCompanion(localMessage)); |  | ||||||
|     return localMessage; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   Future<void> receiveMessageDeletion(String messageId) async { |  | ||||||
|     // Remove from pending messages if exists |  | ||||||
|     pendingMessages.remove(messageId); |  | ||||||
|  |  | ||||||
|     // Delete from local database |  | ||||||
|     await _database.deleteMessage(messageId); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   Future<LocalChatMessage> updateMessage( |  | ||||||
|     String messageId, |  | ||||||
|     String content, { |  | ||||||
|     List<SnCloudFile>? attachments, |  | ||||||
|     Map<String, dynamic>? meta, |  | ||||||
|   }) async { |  | ||||||
|     final message = pendingMessages[messageId]; |  | ||||||
|     if (message != null) { |  | ||||||
|       // Update pending message |  | ||||||
|       final rmMessage = message.toRemoteMessage(); |  | ||||||
|       final updatedRemoteMessage = rmMessage.copyWith( |  | ||||||
|         content: content, |  | ||||||
|         meta: meta ?? rmMessage.meta, |  | ||||||
|       ); |  | ||||||
|       final updatedLocalMessage = LocalChatMessage.fromRemoteMessage( |  | ||||||
|         updatedRemoteMessage, |  | ||||||
|         MessageStatus.pending, |  | ||||||
|       ); |  | ||||||
|       pendingMessages[messageId] = updatedLocalMessage; |  | ||||||
|       await _database.updateMessage( |  | ||||||
|         _database.messageToCompanion(updatedLocalMessage), |  | ||||||
|       ); |  | ||||||
|       return message; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     try { |  | ||||||
|       // Update on server |  | ||||||
|       final response = await _apiClient.put( |  | ||||||
|         '/sphere/chat/${room.id}/messages/$messageId', |  | ||||||
|         data: {'content': content, 'attachments': attachments, 'meta': meta}, |  | ||||||
|       ); |  | ||||||
|  |  | ||||||
|       // Update local copy |  | ||||||
|       final remoteMessage = SnChatMessage.fromJson(response.data); |  | ||||||
|       final updatedMessage = LocalChatMessage.fromRemoteMessage( |  | ||||||
|         remoteMessage, |  | ||||||
|         MessageStatus.sent, |  | ||||||
|       ); |  | ||||||
|       await _database.updateMessage( |  | ||||||
|         _database.messageToCompanion(updatedMessage), |  | ||||||
|       ); |  | ||||||
|       return updatedMessage; |  | ||||||
|     } catch (e) { |  | ||||||
|       rethrow; |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   Future<void> deleteMessage(String messageId) async { |  | ||||||
|     try { |  | ||||||
|       await _apiClient.delete('/sphere/chat/${room.id}/messages/$messageId'); |  | ||||||
|       pendingMessages.remove(messageId); |  | ||||||
|       await _database.deleteMessage(messageId); |  | ||||||
|     } catch (e) { |  | ||||||
|       rethrow; |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   Future<LocalChatMessage?> getMessageById(String messageId) async { |  | ||||||
|     try { |  | ||||||
|       // Attempt to get the message from the local database |  | ||||||
|       final localMessage = |  | ||||||
|           await (_database.select(_database.chatMessages) |  | ||||||
|             ..where((tbl) => tbl.id.equals(messageId))).getSingleOrNull(); |  | ||||||
|       if (localMessage != null) { |  | ||||||
|         return _database.companionToMessage(localMessage); |  | ||||||
|       } |  | ||||||
|  |  | ||||||
|       // If not found locally, fetch from the server |  | ||||||
|       final response = await _apiClient.get( |  | ||||||
|         '/sphere/chat/${room.id}/messages/$messageId', |  | ||||||
|       ); |  | ||||||
|       final remoteMessage = SnChatMessage.fromJson(response.data); |  | ||||||
|       final message = LocalChatMessage.fromRemoteMessage( |  | ||||||
|         remoteMessage, |  | ||||||
|         MessageStatus.sent, |  | ||||||
|       ); |  | ||||||
|  |  | ||||||
|       // Save the fetched message to the local database |  | ||||||
|       await _database.saveMessage(_database.messageToCompanion(message)); |  | ||||||
|       return message; |  | ||||||
|     } catch (e) { |  | ||||||
|       if (e is DioException) return null; |  | ||||||
|       // Handle errors |  | ||||||
|       rethrow; |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| @@ -61,10 +61,8 @@ class DefaultFirebaseOptions { | |||||||
|     messagingSenderId: '961776991058', |     messagingSenderId: '961776991058', | ||||||
|     projectId: 'solian-0x001', |     projectId: 'solian-0x001', | ||||||
|     storageBucket: 'solian-0x001.firebasestorage.app', |     storageBucket: 'solian-0x001.firebasestorage.app', | ||||||
|     androidClientId: |     androidClientId: '961776991058-r4iv9qoio57ul7utbfpgfrda2etvtch8.apps.googleusercontent.com', | ||||||
|         '961776991058-r4iv9qoio57ul7utbfpgfrda2etvtch8.apps.googleusercontent.com', |     iosClientId: '961776991058-stt7et4qvn3cpscl4r61gl1hnlatqkig.apps.googleusercontent.com', | ||||||
|     iosClientId: |  | ||||||
|         '961776991058-stt7et4qvn3cpscl4r61gl1hnlatqkig.apps.googleusercontent.com', |  | ||||||
|     iosBundleId: 'dev.solsynth.solian', |     iosBundleId: 'dev.solsynth.solian', | ||||||
|   ); |   ); | ||||||
|  |  | ||||||
| @@ -74,10 +72,8 @@ class DefaultFirebaseOptions { | |||||||
|     messagingSenderId: '961776991058', |     messagingSenderId: '961776991058', | ||||||
|     projectId: 'solian-0x001', |     projectId: 'solian-0x001', | ||||||
|     storageBucket: 'solian-0x001.firebasestorage.app', |     storageBucket: 'solian-0x001.firebasestorage.app', | ||||||
|     androidClientId: |     androidClientId: '961776991058-r4iv9qoio57ul7utbfpgfrda2etvtch8.apps.googleusercontent.com', | ||||||
|         '961776991058-r4iv9qoio57ul7utbfpgfrda2etvtch8.apps.googleusercontent.com', |     iosClientId: '961776991058-stt7et4qvn3cpscl4r61gl1hnlatqkig.apps.googleusercontent.com', | ||||||
|     iosClientId: |  | ||||||
|         '961776991058-stt7et4qvn3cpscl4r61gl1hnlatqkig.apps.googleusercontent.com', |  | ||||||
|     iosBundleId: 'dev.solsynth.solian', |     iosBundleId: 'dev.solsynth.solian', | ||||||
|   ); |   ); | ||||||
|  |  | ||||||
| @@ -90,4 +86,5 @@ class DefaultFirebaseOptions { | |||||||
|     storageBucket: 'solian-0x001.firebasestorage.app', |     storageBucket: 'solian-0x001.firebasestorage.app', | ||||||
|     measurementId: 'G-JD1YEG9D6F', |     measurementId: 'G-JD1YEG9D6F', | ||||||
|   ); |   ); | ||||||
| } |  | ||||||
|  | } | ||||||
| @@ -4,6 +4,7 @@ import 'dart:io'; | |||||||
| import 'package:croppy/croppy.dart'; | import 'package:croppy/croppy.dart'; | ||||||
| import 'package:easy_localization/easy_localization.dart' hide TextDirection; | import 'package:easy_localization/easy_localization.dart' hide TextDirection; | ||||||
| import 'package:firebase_core/firebase_core.dart'; | import 'package:firebase_core/firebase_core.dart'; | ||||||
|  | import 'package:firebase_crashlytics/firebase_crashlytics.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'; | ||||||
| @@ -28,7 +29,6 @@ import 'package:relative_time/relative_time.dart'; | |||||||
| import 'package:shared_preferences/shared_preferences.dart'; | 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:island/widgets/keyboard_navigation.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; | import 'package:flutter_langdetect/flutter_langdetect.dart' as langdetect; | ||||||
|  |  | ||||||
| @@ -62,6 +62,17 @@ void main() async { | |||||||
|       FirebaseMessaging.onBackgroundMessage( |       FirebaseMessaging.onBackgroundMessage( | ||||||
|         _firebaseMessagingBackgroundHandler, |         _firebaseMessagingBackgroundHandler, | ||||||
|       ); |       ); | ||||||
|  |       // Although previous if case checked this. Still check is web or not | ||||||
|  |       // Otherwise the web platform will broke due to there is no Platform api on the web | ||||||
|  |       // Skip crashlytics setup on debug mode to prevent unexpected report to firebase | ||||||
|  |       if ((kIsWeb || !Platform.isWindows) && !kDebugMode) { | ||||||
|  |         FlutterError.onError = | ||||||
|  |             FirebaseCrashlytics.instance.recordFlutterFatalError; | ||||||
|  |         PlatformDispatcher.instance.onError = (error, stack) { | ||||||
|  |           FirebaseCrashlytics.instance.recordError(error, stack, fatal: true); | ||||||
|  |           return true; | ||||||
|  |         }; | ||||||
|  |       } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     log("[SplashScreen] Firebase is ready!"); |     log("[SplashScreen] Firebase is ready!"); | ||||||
| @@ -245,32 +256,30 @@ class IslandApp extends HookConsumerWidget { | |||||||
|  |  | ||||||
|     final router = ref.watch(routerProvider); |     final router = ref.watch(routerProvider); | ||||||
|  |  | ||||||
|     return KeyboardNavigation( |     return MaterialApp.router( | ||||||
|       child: MaterialApp.router( |       theme: theme?.light, | ||||||
|         theme: theme?.light, |       darkTheme: theme?.dark, | ||||||
|         darkTheme: theme?.dark, |       themeMode: ThemeMode.system, | ||||||
|         themeMode: ThemeMode.system, |       routerConfig: router, | ||||||
|         routerConfig: router, |       supportedLocales: context.supportedLocales, | ||||||
|         supportedLocales: context.supportedLocales, |       localizationsDelegates: [ | ||||||
|         localizationsDelegates: [ |         ...context.localizationDelegates, | ||||||
|           ...context.localizationDelegates, |         CroppyLocalizations.delegate, | ||||||
|           CroppyLocalizations.delegate, |         RelativeTimeLocalizations.delegate, | ||||||
|           RelativeTimeLocalizations.delegate, |       ], | ||||||
|         ], |       locale: context.locale, | ||||||
|         locale: context.locale, |       builder: (context, child) { | ||||||
|         builder: (context, child) { |         return Overlay( | ||||||
|           return Overlay( |           key: globalOverlay, | ||||||
|             key: globalOverlay, |           initialEntries: [ | ||||||
|             initialEntries: [ |             OverlayEntry( | ||||||
|               OverlayEntry( |               builder: | ||||||
|                 builder: |                   (_) => | ||||||
|                     (_) => |                       WindowScaffold(child: child ?? const SizedBox.shrink()), | ||||||
|                         WindowScaffold(child: child ?? const SizedBox.shrink()), |             ), | ||||||
|               ), |           ], | ||||||
|             ], |         ); | ||||||
|           ); |       }, | ||||||
|         }, |  | ||||||
|       ), |  | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,9 +1,10 @@ | |||||||
| import 'package:freezed_annotation/freezed_annotation.dart'; | import 'package:freezed_annotation/freezed_annotation.dart'; | ||||||
|  | import 'package:island/models/auth.dart'; | ||||||
| import 'package:island/models/file.dart'; | import 'package:island/models/file.dart'; | ||||||
| import 'package:island/models/wallet.dart'; | import 'package:island/models/wallet.dart'; | ||||||
| 
 | 
 | ||||||
| part 'user.freezed.dart'; | part 'account.freezed.dart'; | ||||||
| part 'user.g.dart'; | part 'account.g.dart'; | ||||||
| 
 | 
 | ||||||
| @freezed | @freezed | ||||||
| sealed class SnAccount with _$SnAccount { | sealed class SnAccount with _$SnAccount { | ||||||
| @@ -174,3 +175,70 @@ sealed class SnVerificationMark with _$SnVerificationMark { | |||||||
|   factory SnVerificationMark.fromJson(Map<String, dynamic> json) => |   factory SnVerificationMark.fromJson(Map<String, dynamic> json) => | ||||||
|       _$SnVerificationMarkFromJson(json); |       _$SnVerificationMarkFromJson(json); | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | @freezed | ||||||
|  | sealed class SnAuthDevice with _$SnAuthDevice { | ||||||
|  |   const factory SnAuthDevice({ | ||||||
|  |     required String id, | ||||||
|  |     required String deviceId, | ||||||
|  |     required String deviceName, | ||||||
|  |     required String? deviceLabel, | ||||||
|  |     required String accountId, | ||||||
|  |     required int platform, | ||||||
|  |     @Default(false) bool isCurrent, | ||||||
|  |   }) = _SnAuthDevice; | ||||||
|  | 
 | ||||||
|  |   factory SnAuthDevice.fromJson(Map<String, dynamic> json) => | ||||||
|  |       _$SnAuthDeviceFromJson(json); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @freezed | ||||||
|  | sealed class SnAuthDeviceWithChallenge with _$SnAuthDeviceWithChallenge { | ||||||
|  |   const factory SnAuthDeviceWithChallenge({ | ||||||
|  |     required String id, | ||||||
|  |     required String deviceId, | ||||||
|  |     required String deviceName, | ||||||
|  |     required String? deviceLabel, | ||||||
|  |     required String accountId, | ||||||
|  |     required int platform, | ||||||
|  |     required List<SnAuthChallenge> challenges, | ||||||
|  |     @Default(false) bool isCurrent, | ||||||
|  |   }) = _SnAuthDeviceWithChallengee; | ||||||
|  | 
 | ||||||
|  |   factory SnAuthDeviceWithChallenge.fromJson(Map<String, dynamic> json) => | ||||||
|  |       _$SnAuthDeviceWithChallengeFromJson(json); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @freezed | ||||||
|  | sealed class SnExperienceRecord with _$SnExperienceRecord { | ||||||
|  |   const factory SnExperienceRecord({ | ||||||
|  |     required String id, | ||||||
|  |     required int delta, | ||||||
|  |     required String reasonType, | ||||||
|  |     required String reason, | ||||||
|  |     @Default(1.0) double? bonusMultiplier, | ||||||
|  |     required DateTime createdAt, | ||||||
|  |     required DateTime updatedAt, | ||||||
|  |     required DateTime? deletedAt, | ||||||
|  |   }) = _SnExperienceRecord; | ||||||
|  | 
 | ||||||
|  |   factory SnExperienceRecord.fromJson(Map<String, dynamic> json) => | ||||||
|  |       _$SnExperienceRecordFromJson(json); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @freezed | ||||||
|  | sealed class SnSocialCreditRecord with _$SnSocialCreditRecord { | ||||||
|  |   const factory SnSocialCreditRecord({ | ||||||
|  |     required String id, | ||||||
|  |     required double delta, | ||||||
|  |     required String reasonType, | ||||||
|  |     required String reason, | ||||||
|  |     required DateTime? expiredAt, | ||||||
|  |     required DateTime createdAt, | ||||||
|  |     required DateTime updatedAt, | ||||||
|  |     required DateTime? deletedAt, | ||||||
|  |   }) = _SnSocialCreditRecord; | ||||||
|  | 
 | ||||||
|  |   factory SnSocialCreditRecord.fromJson(Map<String, dynamic> json) => | ||||||
|  |       _$SnSocialCreditRecordFromJson(json); | ||||||
|  | } | ||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,6 +1,6 @@ | |||||||
| // GENERATED CODE - DO NOT MODIFY BY HAND | // GENERATED CODE - DO NOT MODIFY BY HAND | ||||||
| 
 | 
 | ||||||
| part of 'user.dart'; | part of 'account.dart'; | ||||||
| 
 | 
 | ||||||
| // ************************************************************************** | // ************************************************************************** | ||||||
| // JsonSerializableGenerator | // JsonSerializableGenerator | ||||||
| @@ -297,3 +297,113 @@ Map<String, dynamic> _$SnVerificationMarkToJson(_SnVerificationMark instance) => | |||||||
|       'description': instance.description, |       'description': instance.description, | ||||||
|       'verified_by': instance.verifiedBy, |       'verified_by': instance.verifiedBy, | ||||||
|     }; |     }; | ||||||
|  | 
 | ||||||
|  | _SnAuthDevice _$SnAuthDeviceFromJson(Map<String, dynamic> json) => | ||||||
|  |     _SnAuthDevice( | ||||||
|  |       id: json['id'] as String, | ||||||
|  |       deviceId: json['device_id'] as String, | ||||||
|  |       deviceName: json['device_name'] as String, | ||||||
|  |       deviceLabel: json['device_label'] as String?, | ||||||
|  |       accountId: json['account_id'] as String, | ||||||
|  |       platform: (json['platform'] as num).toInt(), | ||||||
|  |       isCurrent: json['is_current'] as bool? ?? false, | ||||||
|  |     ); | ||||||
|  | 
 | ||||||
|  | Map<String, dynamic> _$SnAuthDeviceToJson(_SnAuthDevice instance) => | ||||||
|  |     <String, dynamic>{ | ||||||
|  |       'id': instance.id, | ||||||
|  |       'device_id': instance.deviceId, | ||||||
|  |       'device_name': instance.deviceName, | ||||||
|  |       'device_label': instance.deviceLabel, | ||||||
|  |       'account_id': instance.accountId, | ||||||
|  |       'platform': instance.platform, | ||||||
|  |       'is_current': instance.isCurrent, | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  | _SnAuthDeviceWithChallengee _$SnAuthDeviceWithChallengeeFromJson( | ||||||
|  |   Map<String, dynamic> json, | ||||||
|  | ) => _SnAuthDeviceWithChallengee( | ||||||
|  |   id: json['id'] as String, | ||||||
|  |   deviceId: json['device_id'] as String, | ||||||
|  |   deviceName: json['device_name'] as String, | ||||||
|  |   deviceLabel: json['device_label'] as String?, | ||||||
|  |   accountId: json['account_id'] as String, | ||||||
|  |   platform: (json['platform'] as num).toInt(), | ||||||
|  |   challenges: | ||||||
|  |       (json['challenges'] as List<dynamic>) | ||||||
|  |           .map((e) => SnAuthChallenge.fromJson(e as Map<String, dynamic>)) | ||||||
|  |           .toList(), | ||||||
|  |   isCurrent: json['is_current'] as bool? ?? false, | ||||||
|  | ); | ||||||
|  | 
 | ||||||
|  | Map<String, dynamic> _$SnAuthDeviceWithChallengeeToJson( | ||||||
|  |   _SnAuthDeviceWithChallengee instance, | ||||||
|  | ) => <String, dynamic>{ | ||||||
|  |   'id': instance.id, | ||||||
|  |   'device_id': instance.deviceId, | ||||||
|  |   'device_name': instance.deviceName, | ||||||
|  |   'device_label': instance.deviceLabel, | ||||||
|  |   'account_id': instance.accountId, | ||||||
|  |   'platform': instance.platform, | ||||||
|  |   'challenges': instance.challenges.map((e) => e.toJson()).toList(), | ||||||
|  |   'is_current': instance.isCurrent, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | _SnExperienceRecord _$SnExperienceRecordFromJson(Map<String, dynamic> json) => | ||||||
|  |     _SnExperienceRecord( | ||||||
|  |       id: json['id'] as String, | ||||||
|  |       delta: (json['delta'] as num).toInt(), | ||||||
|  |       reasonType: json['reason_type'] as String, | ||||||
|  |       reason: json['reason'] as String, | ||||||
|  |       bonusMultiplier: (json['bonus_multiplier'] as num?)?.toDouble() ?? 1.0, | ||||||
|  |       createdAt: DateTime.parse(json['created_at'] as String), | ||||||
|  |       updatedAt: DateTime.parse(json['updated_at'] as String), | ||||||
|  |       deletedAt: | ||||||
|  |           json['deleted_at'] == null | ||||||
|  |               ? null | ||||||
|  |               : DateTime.parse(json['deleted_at'] as String), | ||||||
|  |     ); | ||||||
|  | 
 | ||||||
|  | Map<String, dynamic> _$SnExperienceRecordToJson(_SnExperienceRecord instance) => | ||||||
|  |     <String, dynamic>{ | ||||||
|  |       'id': instance.id, | ||||||
|  |       'delta': instance.delta, | ||||||
|  |       'reason_type': instance.reasonType, | ||||||
|  |       'reason': instance.reason, | ||||||
|  |       'bonus_multiplier': instance.bonusMultiplier, | ||||||
|  |       'created_at': instance.createdAt.toIso8601String(), | ||||||
|  |       'updated_at': instance.updatedAt.toIso8601String(), | ||||||
|  |       'deleted_at': instance.deletedAt?.toIso8601String(), | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  | _SnSocialCreditRecord _$SnSocialCreditRecordFromJson( | ||||||
|  |   Map<String, dynamic> json, | ||||||
|  | ) => _SnSocialCreditRecord( | ||||||
|  |   id: json['id'] as String, | ||||||
|  |   delta: (json['delta'] as num).toDouble(), | ||||||
|  |   reasonType: json['reason_type'] as String, | ||||||
|  |   reason: json['reason'] as String, | ||||||
|  |   expiredAt: | ||||||
|  |       json['expired_at'] == null | ||||||
|  |           ? null | ||||||
|  |           : DateTime.parse(json['expired_at'] as String), | ||||||
|  |   createdAt: DateTime.parse(json['created_at'] as String), | ||||||
|  |   updatedAt: DateTime.parse(json['updated_at'] as String), | ||||||
|  |   deletedAt: | ||||||
|  |       json['deleted_at'] == null | ||||||
|  |           ? null | ||||||
|  |           : DateTime.parse(json['deleted_at'] as String), | ||||||
|  | ); | ||||||
|  | 
 | ||||||
|  | Map<String, dynamic> _$SnSocialCreditRecordToJson( | ||||||
|  |   _SnSocialCreditRecord instance, | ||||||
|  | ) => <String, dynamic>{ | ||||||
|  |   'id': instance.id, | ||||||
|  |   'delta': instance.delta, | ||||||
|  |   'reason_type': instance.reasonType, | ||||||
|  |   'reason': instance.reason, | ||||||
|  |   'expired_at': instance.expiredAt?.toIso8601String(), | ||||||
|  |   'created_at': instance.createdAt.toIso8601String(), | ||||||
|  |   'updated_at': instance.updatedAt.toIso8601String(), | ||||||
|  |   'deleted_at': instance.deletedAt?.toIso8601String(), | ||||||
|  | }; | ||||||
| @@ -1,5 +1,5 @@ | |||||||
| import 'package:freezed_annotation/freezed_annotation.dart'; | import 'package:freezed_annotation/freezed_annotation.dart'; | ||||||
| import 'package:island/models/user.dart'; | import 'package:island/models/account.dart'; | ||||||
|  |  | ||||||
| part 'activity.freezed.dart'; | part 'activity.freezed.dart'; | ||||||
| part 'activity.g.dart'; | part 'activity.g.dart'; | ||||||
|   | |||||||
| @@ -19,14 +19,12 @@ sealed class SnAuthChallenge with _$SnAuthChallenge { | |||||||
|     required int stepRemain, |     required int stepRemain, | ||||||
|     required int stepTotal, |     required int stepTotal, | ||||||
|     required int failedAttempts, |     required int failedAttempts, | ||||||
|     required int platform, |  | ||||||
|     required int type, |     required int type, | ||||||
|     required List<String> blacklistFactors, |     required List<String> blacklistFactors, | ||||||
|     required List<dynamic> audiences, |     required List<dynamic> audiences, | ||||||
|     required List<dynamic> scopes, |     required List<dynamic> scopes, | ||||||
|     required String ipAddress, |     required String ipAddress, | ||||||
|     required String userAgent, |     required String userAgent, | ||||||
|     required String deviceId, |  | ||||||
|     required String? nonce, |     required String? nonce, | ||||||
|     required String? location, |     required String? location, | ||||||
|     required String accountId, |     required String accountId, | ||||||
| @@ -76,22 +74,6 @@ sealed class SnAuthFactor with _$SnAuthFactor { | |||||||
|       _$SnAuthFactorFromJson(json); |       _$SnAuthFactorFromJson(json); | ||||||
| } | } | ||||||
|  |  | ||||||
| @freezed |  | ||||||
| sealed class SnAuthDevice with _$SnAuthDevice { |  | ||||||
|   const factory SnAuthDevice({ |  | ||||||
|     required dynamic label, |  | ||||||
|     required String userAgent, |  | ||||||
|     required String deviceId, |  | ||||||
|     required int platform, |  | ||||||
|     required List<SnAuthSession> sessions, |  | ||||||
|     // Not from backend, used for UI |  | ||||||
|     @Default(false) bool isCurrent, |  | ||||||
|   }) = _SnAuthDevice; |  | ||||||
|  |  | ||||||
|   factory SnAuthDevice.fromJson(Map<String, dynamic> json) => |  | ||||||
|       _$SnAuthDeviceFromJson(json); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| @freezed | @freezed | ||||||
| sealed class SnAccountConnection with _$SnAccountConnection { | sealed class SnAccountConnection with _$SnAccountConnection { | ||||||
|   const factory SnAccountConnection({ |   const factory SnAccountConnection({ | ||||||
|   | |||||||
| @@ -272,7 +272,7 @@ 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 platform; int get type; List<String> get blacklistFactors; List<dynamic> get audiences; List<dynamic> get scopes; String get ipAddress; String get userAgent; String get deviceId; 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; String? 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) | ||||||
| @@ -285,16 +285,16 @@ $SnAuthChallengeCopyWith<SnAuthChallenge> get copyWith => _$SnAuthChallengeCopyW | |||||||
|  |  | ||||||
| @override | @override | ||||||
| bool operator ==(Object other) { | bool operator ==(Object other) { | ||||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is SnAuthChallenge&&(identical(other.id, id) || other.id == id)&&(identical(other.expiredAt, expiredAt) || other.expiredAt == expiredAt)&&(identical(other.stepRemain, stepRemain) || other.stepRemain == stepRemain)&&(identical(other.stepTotal, stepTotal) || other.stepTotal == stepTotal)&&(identical(other.failedAttempts, failedAttempts) || other.failedAttempts == failedAttempts)&&(identical(other.platform, platform) || other.platform == platform)&&(identical(other.type, type) || other.type == type)&&const DeepCollectionEquality().equals(other.blacklistFactors, blacklistFactors)&&const DeepCollectionEquality().equals(other.audiences, audiences)&&const DeepCollectionEquality().equals(other.scopes, scopes)&&(identical(other.ipAddress, ipAddress) || other.ipAddress == ipAddress)&&(identical(other.userAgent, userAgent) || other.userAgent == userAgent)&&(identical(other.deviceId, deviceId) || other.deviceId == deviceId)&&(identical(other.nonce, nonce) || other.nonce == nonce)&&(identical(other.location, location) || other.location == location)&&(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 SnAuthChallenge&&(identical(other.id, id) || other.id == id)&&(identical(other.expiredAt, expiredAt) || other.expiredAt == expiredAt)&&(identical(other.stepRemain, stepRemain) || other.stepRemain == stepRemain)&&(identical(other.stepTotal, stepTotal) || other.stepTotal == stepTotal)&&(identical(other.failedAttempts, failedAttempts) || other.failedAttempts == failedAttempts)&&(identical(other.type, type) || other.type == type)&&const DeepCollectionEquality().equals(other.blacklistFactors, blacklistFactors)&&const DeepCollectionEquality().equals(other.audiences, audiences)&&const DeepCollectionEquality().equals(other.scopes, scopes)&&(identical(other.ipAddress, ipAddress) || other.ipAddress == ipAddress)&&(identical(other.userAgent, userAgent) || other.userAgent == userAgent)&&(identical(other.nonce, nonce) || other.nonce == nonce)&&(identical(other.location, location) || other.location == location)&&(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.hashAll([runtimeType,id,expiredAt,stepRemain,stepTotal,failedAttempts,platform,type,const DeepCollectionEquality().hash(blacklistFactors),const DeepCollectionEquality().hash(audiences),const DeepCollectionEquality().hash(scopes),ipAddress,userAgent,deviceId,nonce,location,accountId,createdAt,updatedAt,deletedAt]); | int get hashCode => Object.hash(runtimeType,id,expiredAt,stepRemain,stepTotal,failedAttempts,type,const DeepCollectionEquality().hash(blacklistFactors),const DeepCollectionEquality().hash(audiences),const DeepCollectionEquality().hash(scopes),ipAddress,userAgent,nonce,location,accountId,createdAt,updatedAt,deletedAt); | ||||||
|  |  | ||||||
| @override | @override | ||||||
| String toString() { | String toString() { | ||||||
|   return 'SnAuthChallenge(id: $id, expiredAt: $expiredAt, stepRemain: $stepRemain, stepTotal: $stepTotal, failedAttempts: $failedAttempts, platform: $platform, type: $type, blacklistFactors: $blacklistFactors, audiences: $audiences, scopes: $scopes, ipAddress: $ipAddress, userAgent: $userAgent, deviceId: $deviceId, nonce: $nonce, location: $location, accountId: $accountId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; |   return 'SnAuthChallenge(id: $id, expiredAt: $expiredAt, stepRemain: $stepRemain, stepTotal: $stepTotal, failedAttempts: $failedAttempts, type: $type, blacklistFactors: $blacklistFactors, audiences: $audiences, scopes: $scopes, ipAddress: $ipAddress, userAgent: $userAgent, nonce: $nonce, location: $location, accountId: $accountId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -305,7 +305,7 @@ 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 platform, int type, List<String> blacklistFactors, List<dynamic> audiences, List<dynamic> scopes, String ipAddress, String userAgent, String deviceId, 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, String? location, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -322,21 +322,19 @@ class _$SnAuthChallengeCopyWithImpl<$Res> | |||||||
|  |  | ||||||
| /// 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. | ||||||
| @pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? expiredAt = null,Object? stepRemain = null,Object? stepTotal = null,Object? failedAttempts = null,Object? platform = null,Object? type = null,Object? blacklistFactors = null,Object? audiences = null,Object? scopes = null,Object? ipAddress = null,Object? userAgent = null,Object? deviceId = null,Object? nonce = freezed,Object? location = freezed,Object? accountId = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) { | @pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? expiredAt = null,Object? stepRemain = null,Object? stepTotal = null,Object? failedAttempts = null,Object? type = null,Object? blacklistFactors = null,Object? audiences = null,Object? scopes = null,Object? ipAddress = null,Object? userAgent = null,Object? nonce = freezed,Object? location = freezed,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,expiredAt: null == expiredAt ? _self.expiredAt : expiredAt // ignore: cast_nullable_to_non_nullable | as String,expiredAt: null == expiredAt ? _self.expiredAt : expiredAt // ignore: cast_nullable_to_non_nullable | ||||||
| as DateTime,stepRemain: null == stepRemain ? _self.stepRemain : stepRemain // ignore: cast_nullable_to_non_nullable | as DateTime,stepRemain: null == stepRemain ? _self.stepRemain : stepRemain // ignore: cast_nullable_to_non_nullable | ||||||
| as int,stepTotal: null == stepTotal ? _self.stepTotal : stepTotal // ignore: cast_nullable_to_non_nullable | as int,stepTotal: null == stepTotal ? _self.stepTotal : stepTotal // ignore: cast_nullable_to_non_nullable | ||||||
| as int,failedAttempts: null == failedAttempts ? _self.failedAttempts : failedAttempts // ignore: cast_nullable_to_non_nullable | as int,failedAttempts: null == failedAttempts ? _self.failedAttempts : failedAttempts // ignore: cast_nullable_to_non_nullable | ||||||
| as int,platform: null == platform ? _self.platform : platform // ignore: cast_nullable_to_non_nullable |  | ||||||
| as int,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable | as int,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable | ||||||
| as int,blacklistFactors: null == blacklistFactors ? _self.blacklistFactors : blacklistFactors // ignore: cast_nullable_to_non_nullable | as int,blacklistFactors: null == blacklistFactors ? _self.blacklistFactors : blacklistFactors // ignore: cast_nullable_to_non_nullable | ||||||
| as List<String>,audiences: null == audiences ? _self.audiences : audiences // ignore: cast_nullable_to_non_nullable | as List<String>,audiences: null == audiences ? _self.audiences : audiences // ignore: cast_nullable_to_non_nullable | ||||||
| as List<dynamic>,scopes: null == scopes ? _self.scopes : scopes // ignore: cast_nullable_to_non_nullable | as List<dynamic>,scopes: null == scopes ? _self.scopes : scopes // ignore: cast_nullable_to_non_nullable | ||||||
| as List<dynamic>,ipAddress: null == ipAddress ? _self.ipAddress : ipAddress // ignore: cast_nullable_to_non_nullable | as List<dynamic>,ipAddress: null == ipAddress ? _self.ipAddress : ipAddress // ignore: cast_nullable_to_non_nullable | ||||||
| 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,deviceId: null == deviceId ? _self.deviceId : deviceId // 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 String?,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable | ||||||
| @@ -425,10 +423,10 @@ return $default(_that);case _: | |||||||
| /// } | /// } | ||||||
| /// ``` | /// ``` | ||||||
|  |  | ||||||
| @optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id,  DateTime expiredAt,  int stepRemain,  int stepTotal,  int failedAttempts,  int platform,  int type,  List<String> blacklistFactors,  List<dynamic> audiences,  List<dynamic> scopes,  String ipAddress,  String userAgent,  String deviceId,  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,  String? 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.platform,_that.type,_that.blacklistFactors,_that.audiences,_that.scopes,_that.ipAddress,_that.userAgent,_that.deviceId,_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 _: | ||||||
|   return orElse(); |   return orElse(); | ||||||
|  |  | ||||||
| } | } | ||||||
| @@ -446,10 +444,10 @@ 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 platform,  int type,  List<String> blacklistFactors,  List<dynamic> audiences,  List<dynamic> scopes,  String ipAddress,  String userAgent,  String deviceId,  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,  String? 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.platform,_that.type,_that.blacklistFactors,_that.audiences,_that.scopes,_that.ipAddress,_that.userAgent,_that.deviceId,_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);} | ||||||
| } | } | ||||||
| /// A variant of `when` that fallback to returning `null` | /// A variant of `when` that fallback to returning `null` | ||||||
| /// | /// | ||||||
| @@ -463,10 +461,10 @@ 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 platform,  int type,  List<String> blacklistFactors,  List<dynamic> audiences,  List<dynamic> scopes,  String ipAddress,  String userAgent,  String deviceId,  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,  String? 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.platform,_that.type,_that.blacklistFactors,_that.audiences,_that.scopes,_that.ipAddress,_that.userAgent,_that.deviceId,_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 _: | ||||||
|   return null; |   return null; | ||||||
|  |  | ||||||
| } | } | ||||||
| @@ -478,7 +476,7 @@ return $default(_that.id,_that.expiredAt,_that.stepRemain,_that.stepTotal,_that. | |||||||
| @JsonSerializable() | @JsonSerializable() | ||||||
|  |  | ||||||
| class _SnAuthChallenge implements SnAuthChallenge { | class _SnAuthChallenge implements SnAuthChallenge { | ||||||
|   const _SnAuthChallenge({required this.id, required this.expiredAt, required this.stepRemain, required this.stepTotal, required this.failedAttempts, required this.platform, required this.type, required final  List<String> blacklistFactors, required final  List<dynamic> audiences, required final  List<dynamic> scopes, required this.ipAddress, required this.userAgent, required this.deviceId, required this.nonce, required this.location, required this.accountId, required this.createdAt, required this.updatedAt, required this.deletedAt}): _blacklistFactors = blacklistFactors,_audiences = audiences,_scopes = scopes; |   const _SnAuthChallenge({required this.id, required this.expiredAt, required this.stepRemain, required this.stepTotal, required this.failedAttempts, required this.type, required final  List<String> blacklistFactors, required final  List<dynamic> audiences, required final  List<dynamic> scopes, required this.ipAddress, required this.userAgent, required this.nonce, required this.location, required this.accountId, required this.createdAt, required this.updatedAt, required this.deletedAt}): _blacklistFactors = blacklistFactors,_audiences = audiences,_scopes = scopes; | ||||||
|   factory _SnAuthChallenge.fromJson(Map<String, dynamic> json) => _$SnAuthChallengeFromJson(json); |   factory _SnAuthChallenge.fromJson(Map<String, dynamic> json) => _$SnAuthChallengeFromJson(json); | ||||||
|  |  | ||||||
| @override final  String id; | @override final  String id; | ||||||
| @@ -486,7 +484,6 @@ class _SnAuthChallenge implements SnAuthChallenge { | |||||||
| @override final  int stepRemain; | @override final  int stepRemain; | ||||||
| @override final  int stepTotal; | @override final  int stepTotal; | ||||||
| @override final  int failedAttempts; | @override final  int failedAttempts; | ||||||
| @override final  int platform; |  | ||||||
| @override final  int type; | @override final  int type; | ||||||
|  final  List<String> _blacklistFactors; |  final  List<String> _blacklistFactors; | ||||||
| @override List<String> get blacklistFactors { | @override List<String> get blacklistFactors { | ||||||
| @@ -511,7 +508,6 @@ class _SnAuthChallenge implements SnAuthChallenge { | |||||||
|  |  | ||||||
| @override final  String ipAddress; | @override final  String ipAddress; | ||||||
| @override final  String userAgent; | @override final  String userAgent; | ||||||
| @override final  String deviceId; |  | ||||||
| @override final  String? nonce; | @override final  String? nonce; | ||||||
| @override final  String? location; | @override final  String? location; | ||||||
| @override final  String accountId; | @override final  String accountId; | ||||||
| @@ -532,16 +528,16 @@ Map<String, dynamic> toJson() { | |||||||
|  |  | ||||||
| @override | @override | ||||||
| bool operator ==(Object other) { | bool operator ==(Object other) { | ||||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnAuthChallenge&&(identical(other.id, id) || other.id == id)&&(identical(other.expiredAt, expiredAt) || other.expiredAt == expiredAt)&&(identical(other.stepRemain, stepRemain) || other.stepRemain == stepRemain)&&(identical(other.stepTotal, stepTotal) || other.stepTotal == stepTotal)&&(identical(other.failedAttempts, failedAttempts) || other.failedAttempts == failedAttempts)&&(identical(other.platform, platform) || other.platform == platform)&&(identical(other.type, type) || other.type == type)&&const DeepCollectionEquality().equals(other._blacklistFactors, _blacklistFactors)&&const DeepCollectionEquality().equals(other._audiences, _audiences)&&const DeepCollectionEquality().equals(other._scopes, _scopes)&&(identical(other.ipAddress, ipAddress) || other.ipAddress == ipAddress)&&(identical(other.userAgent, userAgent) || other.userAgent == userAgent)&&(identical(other.deviceId, deviceId) || other.deviceId == deviceId)&&(identical(other.nonce, nonce) || other.nonce == nonce)&&(identical(other.location, location) || other.location == location)&&(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 _SnAuthChallenge&&(identical(other.id, id) || other.id == id)&&(identical(other.expiredAt, expiredAt) || other.expiredAt == expiredAt)&&(identical(other.stepRemain, stepRemain) || other.stepRemain == stepRemain)&&(identical(other.stepTotal, stepTotal) || other.stepTotal == stepTotal)&&(identical(other.failedAttempts, failedAttempts) || other.failedAttempts == failedAttempts)&&(identical(other.type, type) || other.type == type)&&const DeepCollectionEquality().equals(other._blacklistFactors, _blacklistFactors)&&const DeepCollectionEquality().equals(other._audiences, _audiences)&&const DeepCollectionEquality().equals(other._scopes, _scopes)&&(identical(other.ipAddress, ipAddress) || other.ipAddress == ipAddress)&&(identical(other.userAgent, userAgent) || other.userAgent == userAgent)&&(identical(other.nonce, nonce) || other.nonce == nonce)&&(identical(other.location, location) || other.location == location)&&(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.hashAll([runtimeType,id,expiredAt,stepRemain,stepTotal,failedAttempts,platform,type,const DeepCollectionEquality().hash(_blacklistFactors),const DeepCollectionEquality().hash(_audiences),const DeepCollectionEquality().hash(_scopes),ipAddress,userAgent,deviceId,nonce,location,accountId,createdAt,updatedAt,deletedAt]); | int get hashCode => Object.hash(runtimeType,id,expiredAt,stepRemain,stepTotal,failedAttempts,type,const DeepCollectionEquality().hash(_blacklistFactors),const DeepCollectionEquality().hash(_audiences),const DeepCollectionEquality().hash(_scopes),ipAddress,userAgent,nonce,location,accountId,createdAt,updatedAt,deletedAt); | ||||||
|  |  | ||||||
| @override | @override | ||||||
| String toString() { | String toString() { | ||||||
|   return 'SnAuthChallenge(id: $id, expiredAt: $expiredAt, stepRemain: $stepRemain, stepTotal: $stepTotal, failedAttempts: $failedAttempts, platform: $platform, type: $type, blacklistFactors: $blacklistFactors, audiences: $audiences, scopes: $scopes, ipAddress: $ipAddress, userAgent: $userAgent, deviceId: $deviceId, nonce: $nonce, location: $location, accountId: $accountId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; |   return 'SnAuthChallenge(id: $id, expiredAt: $expiredAt, stepRemain: $stepRemain, stepTotal: $stepTotal, failedAttempts: $failedAttempts, type: $type, blacklistFactors: $blacklistFactors, audiences: $audiences, scopes: $scopes, ipAddress: $ipAddress, userAgent: $userAgent, nonce: $nonce, location: $location, accountId: $accountId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -552,7 +548,7 @@ 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 platform, int type, List<String> blacklistFactors, List<dynamic> audiences, List<dynamic> scopes, String ipAddress, String userAgent, String deviceId, 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, String? location, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -569,21 +565,19 @@ class __$SnAuthChallengeCopyWithImpl<$Res> | |||||||
|  |  | ||||||
| /// 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. | ||||||
| @override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? expiredAt = null,Object? stepRemain = null,Object? stepTotal = null,Object? failedAttempts = null,Object? platform = null,Object? type = null,Object? blacklistFactors = null,Object? audiences = null,Object? scopes = null,Object? ipAddress = null,Object? userAgent = null,Object? deviceId = null,Object? nonce = freezed,Object? location = freezed,Object? accountId = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) { | @override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? expiredAt = null,Object? stepRemain = null,Object? stepTotal = null,Object? failedAttempts = null,Object? type = null,Object? blacklistFactors = null,Object? audiences = null,Object? scopes = null,Object? ipAddress = null,Object? userAgent = null,Object? nonce = freezed,Object? location = freezed,Object? accountId = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) { | ||||||
|   return _then(_SnAuthChallenge( |   return _then(_SnAuthChallenge( | ||||||
| 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,expiredAt: null == expiredAt ? _self.expiredAt : expiredAt // ignore: cast_nullable_to_non_nullable | as String,expiredAt: null == expiredAt ? _self.expiredAt : expiredAt // ignore: cast_nullable_to_non_nullable | ||||||
| as DateTime,stepRemain: null == stepRemain ? _self.stepRemain : stepRemain // ignore: cast_nullable_to_non_nullable | as DateTime,stepRemain: null == stepRemain ? _self.stepRemain : stepRemain // ignore: cast_nullable_to_non_nullable | ||||||
| as int,stepTotal: null == stepTotal ? _self.stepTotal : stepTotal // ignore: cast_nullable_to_non_nullable | as int,stepTotal: null == stepTotal ? _self.stepTotal : stepTotal // ignore: cast_nullable_to_non_nullable | ||||||
| as int,failedAttempts: null == failedAttempts ? _self.failedAttempts : failedAttempts // ignore: cast_nullable_to_non_nullable | as int,failedAttempts: null == failedAttempts ? _self.failedAttempts : failedAttempts // ignore: cast_nullable_to_non_nullable | ||||||
| as int,platform: null == platform ? _self.platform : platform // ignore: cast_nullable_to_non_nullable |  | ||||||
| as int,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable | as int,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable | ||||||
| as int,blacklistFactors: null == blacklistFactors ? _self._blacklistFactors : blacklistFactors // ignore: cast_nullable_to_non_nullable | as int,blacklistFactors: null == blacklistFactors ? _self._blacklistFactors : blacklistFactors // ignore: cast_nullable_to_non_nullable | ||||||
| as List<String>,audiences: null == audiences ? _self._audiences : audiences // ignore: cast_nullable_to_non_nullable | as List<String>,audiences: null == audiences ? _self._audiences : audiences // ignore: cast_nullable_to_non_nullable | ||||||
| as List<dynamic>,scopes: null == scopes ? _self._scopes : scopes // ignore: cast_nullable_to_non_nullable | as List<dynamic>,scopes: null == scopes ? _self._scopes : scopes // ignore: cast_nullable_to_non_nullable | ||||||
| as List<dynamic>,ipAddress: null == ipAddress ? _self.ipAddress : ipAddress // ignore: cast_nullable_to_non_nullable | as List<dynamic>,ipAddress: null == ipAddress ? _self.ipAddress : ipAddress // ignore: cast_nullable_to_non_nullable | ||||||
| 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,deviceId: null == deviceId ? _self.deviceId : deviceId // 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 String?,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable | ||||||
| @@ -1189,286 +1183,6 @@ as Map<String, dynamic>?, | |||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| /// @nodoc |  | ||||||
| mixin _$SnAuthDevice { |  | ||||||
|  |  | ||||||
|  dynamic get label; String get userAgent; String get deviceId; int get platform; List<SnAuthSession> get sessions;// Not from backend, used for UI |  | ||||||
|  bool get isCurrent; |  | ||||||
| /// Create a copy of SnAuthDevice |  | ||||||
| /// with the given fields replaced by the non-null parameter values. |  | ||||||
| @JsonKey(includeFromJson: false, includeToJson: false) |  | ||||||
| @pragma('vm:prefer-inline') |  | ||||||
| $SnAuthDeviceCopyWith<SnAuthDevice> get copyWith => _$SnAuthDeviceCopyWithImpl<SnAuthDevice>(this as SnAuthDevice, _$identity); |  | ||||||
|  |  | ||||||
|   /// Serializes this SnAuthDevice to a JSON map. |  | ||||||
|   Map<String, dynamic> toJson(); |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @override |  | ||||||
| bool operator ==(Object other) { |  | ||||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is SnAuthDevice&&const DeepCollectionEquality().equals(other.label, label)&&(identical(other.userAgent, userAgent) || other.userAgent == userAgent)&&(identical(other.deviceId, deviceId) || other.deviceId == deviceId)&&(identical(other.platform, platform) || other.platform == platform)&&const DeepCollectionEquality().equals(other.sessions, sessions)&&(identical(other.isCurrent, isCurrent) || other.isCurrent == isCurrent)); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| @JsonKey(includeFromJson: false, includeToJson: false) |  | ||||||
| @override |  | ||||||
| int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(label),userAgent,deviceId,platform,const DeepCollectionEquality().hash(sessions),isCurrent); |  | ||||||
|  |  | ||||||
| @override |  | ||||||
| String toString() { |  | ||||||
|   return 'SnAuthDevice(label: $label, userAgent: $userAgent, deviceId: $deviceId, platform: $platform, sessions: $sessions, isCurrent: $isCurrent)'; |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /// @nodoc |  | ||||||
| abstract mixin class $SnAuthDeviceCopyWith<$Res>  { |  | ||||||
|   factory $SnAuthDeviceCopyWith(SnAuthDevice value, $Res Function(SnAuthDevice) _then) = _$SnAuthDeviceCopyWithImpl; |  | ||||||
| @useResult |  | ||||||
| $Res call({ |  | ||||||
|  dynamic label, String userAgent, String deviceId, int platform, List<SnAuthSession> sessions, bool isCurrent |  | ||||||
| }); |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| } |  | ||||||
| /// @nodoc |  | ||||||
| class _$SnAuthDeviceCopyWithImpl<$Res> |  | ||||||
|     implements $SnAuthDeviceCopyWith<$Res> { |  | ||||||
|   _$SnAuthDeviceCopyWithImpl(this._self, this._then); |  | ||||||
|  |  | ||||||
|   final SnAuthDevice _self; |  | ||||||
|   final $Res Function(SnAuthDevice) _then; |  | ||||||
|  |  | ||||||
| /// Create a copy of SnAuthDevice |  | ||||||
| /// with the given fields replaced by the non-null parameter values. |  | ||||||
| @pragma('vm:prefer-inline') @override $Res call({Object? label = freezed,Object? userAgent = null,Object? deviceId = null,Object? platform = null,Object? sessions = null,Object? isCurrent = null,}) { |  | ||||||
|   return _then(_self.copyWith( |  | ||||||
| label: freezed == label ? _self.label : label // ignore: cast_nullable_to_non_nullable |  | ||||||
| as dynamic,userAgent: null == userAgent ? _self.userAgent : userAgent // ignore: cast_nullable_to_non_nullable |  | ||||||
| as String,deviceId: null == deviceId ? _self.deviceId : deviceId // ignore: cast_nullable_to_non_nullable |  | ||||||
| as String,platform: null == platform ? _self.platform : platform // ignore: cast_nullable_to_non_nullable |  | ||||||
| as int,sessions: null == sessions ? _self.sessions : sessions // ignore: cast_nullable_to_non_nullable |  | ||||||
| as List<SnAuthSession>,isCurrent: null == isCurrent ? _self.isCurrent : isCurrent // ignore: cast_nullable_to_non_nullable |  | ||||||
| as bool, |  | ||||||
|   )); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| /// Adds pattern-matching-related methods to [SnAuthDevice]. |  | ||||||
| extension SnAuthDevicePatterns on SnAuthDevice { |  | ||||||
| /// 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( _SnAuthDevice value)?  $default,{required TResult orElse(),}){ |  | ||||||
| final _that = this; |  | ||||||
| switch (_that) { |  | ||||||
| case _SnAuthDevice() 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( _SnAuthDevice value)  $default,){ |  | ||||||
| final _that = this; |  | ||||||
| switch (_that) { |  | ||||||
| case _SnAuthDevice(): |  | ||||||
| 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( _SnAuthDevice value)?  $default,){ |  | ||||||
| final _that = this; |  | ||||||
| switch (_that) { |  | ||||||
| case _SnAuthDevice() 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( dynamic label,  String userAgent,  String deviceId,  int platform,  List<SnAuthSession> sessions,  bool isCurrent)?  $default,{required TResult orElse(),}) {final _that = this; |  | ||||||
| switch (_that) { |  | ||||||
| case _SnAuthDevice() when $default != null: |  | ||||||
| return $default(_that.label,_that.userAgent,_that.deviceId,_that.platform,_that.sessions,_that.isCurrent);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( dynamic label,  String userAgent,  String deviceId,  int platform,  List<SnAuthSession> sessions,  bool isCurrent)  $default,) {final _that = this; |  | ||||||
| switch (_that) { |  | ||||||
| case _SnAuthDevice(): |  | ||||||
| return $default(_that.label,_that.userAgent,_that.deviceId,_that.platform,_that.sessions,_that.isCurrent);} |  | ||||||
| } |  | ||||||
| /// 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( dynamic label,  String userAgent,  String deviceId,  int platform,  List<SnAuthSession> sessions,  bool isCurrent)?  $default,) {final _that = this; |  | ||||||
| switch (_that) { |  | ||||||
| case _SnAuthDevice() when $default != null: |  | ||||||
| return $default(_that.label,_that.userAgent,_that.deviceId,_that.platform,_that.sessions,_that.isCurrent);case _: |  | ||||||
|   return null; |  | ||||||
|  |  | ||||||
| } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /// @nodoc |  | ||||||
| @JsonSerializable() |  | ||||||
|  |  | ||||||
| class _SnAuthDevice implements SnAuthDevice { |  | ||||||
|   const _SnAuthDevice({required this.label, required this.userAgent, required this.deviceId, required this.platform, required final  List<SnAuthSession> sessions, this.isCurrent = false}): _sessions = sessions; |  | ||||||
|   factory _SnAuthDevice.fromJson(Map<String, dynamic> json) => _$SnAuthDeviceFromJson(json); |  | ||||||
|  |  | ||||||
| @override final  dynamic label; |  | ||||||
| @override final  String userAgent; |  | ||||||
| @override final  String deviceId; |  | ||||||
| @override final  int platform; |  | ||||||
|  final  List<SnAuthSession> _sessions; |  | ||||||
| @override List<SnAuthSession> get sessions { |  | ||||||
|   if (_sessions is EqualUnmodifiableListView) return _sessions; |  | ||||||
|   // ignore: implicit_dynamic_type |  | ||||||
|   return EqualUnmodifiableListView(_sessions); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Not from backend, used for UI |  | ||||||
| @override@JsonKey() final  bool isCurrent; |  | ||||||
|  |  | ||||||
| /// Create a copy of SnAuthDevice |  | ||||||
| /// with the given fields replaced by the non-null parameter values. |  | ||||||
| @override @JsonKey(includeFromJson: false, includeToJson: false) |  | ||||||
| @pragma('vm:prefer-inline') |  | ||||||
| _$SnAuthDeviceCopyWith<_SnAuthDevice> get copyWith => __$SnAuthDeviceCopyWithImpl<_SnAuthDevice>(this, _$identity); |  | ||||||
|  |  | ||||||
| @override |  | ||||||
| Map<String, dynamic> toJson() { |  | ||||||
|   return _$SnAuthDeviceToJson(this, ); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| @override |  | ||||||
| bool operator ==(Object other) { |  | ||||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnAuthDevice&&const DeepCollectionEquality().equals(other.label, label)&&(identical(other.userAgent, userAgent) || other.userAgent == userAgent)&&(identical(other.deviceId, deviceId) || other.deviceId == deviceId)&&(identical(other.platform, platform) || other.platform == platform)&&const DeepCollectionEquality().equals(other._sessions, _sessions)&&(identical(other.isCurrent, isCurrent) || other.isCurrent == isCurrent)); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| @JsonKey(includeFromJson: false, includeToJson: false) |  | ||||||
| @override |  | ||||||
| int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(label),userAgent,deviceId,platform,const DeepCollectionEquality().hash(_sessions),isCurrent); |  | ||||||
|  |  | ||||||
| @override |  | ||||||
| String toString() { |  | ||||||
|   return 'SnAuthDevice(label: $label, userAgent: $userAgent, deviceId: $deviceId, platform: $platform, sessions: $sessions, isCurrent: $isCurrent)'; |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /// @nodoc |  | ||||||
| abstract mixin class _$SnAuthDeviceCopyWith<$Res> implements $SnAuthDeviceCopyWith<$Res> { |  | ||||||
|   factory _$SnAuthDeviceCopyWith(_SnAuthDevice value, $Res Function(_SnAuthDevice) _then) = __$SnAuthDeviceCopyWithImpl; |  | ||||||
| @override @useResult |  | ||||||
| $Res call({ |  | ||||||
|  dynamic label, String userAgent, String deviceId, int platform, List<SnAuthSession> sessions, bool isCurrent |  | ||||||
| }); |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| } |  | ||||||
| /// @nodoc |  | ||||||
| class __$SnAuthDeviceCopyWithImpl<$Res> |  | ||||||
|     implements _$SnAuthDeviceCopyWith<$Res> { |  | ||||||
|   __$SnAuthDeviceCopyWithImpl(this._self, this._then); |  | ||||||
|  |  | ||||||
|   final _SnAuthDevice _self; |  | ||||||
|   final $Res Function(_SnAuthDevice) _then; |  | ||||||
|  |  | ||||||
| /// Create a copy of SnAuthDevice |  | ||||||
| /// with the given fields replaced by the non-null parameter values. |  | ||||||
| @override @pragma('vm:prefer-inline') $Res call({Object? label = freezed,Object? userAgent = null,Object? deviceId = null,Object? platform = null,Object? sessions = null,Object? isCurrent = null,}) { |  | ||||||
|   return _then(_SnAuthDevice( |  | ||||||
| label: freezed == label ? _self.label : label // ignore: cast_nullable_to_non_nullable |  | ||||||
| as dynamic,userAgent: null == userAgent ? _self.userAgent : userAgent // ignore: cast_nullable_to_non_nullable |  | ||||||
| as String,deviceId: null == deviceId ? _self.deviceId : deviceId // ignore: cast_nullable_to_non_nullable |  | ||||||
| as String,platform: null == platform ? _self.platform : platform // ignore: cast_nullable_to_non_nullable |  | ||||||
| as int,sessions: null == sessions ? _self._sessions : sessions // ignore: cast_nullable_to_non_nullable |  | ||||||
| as List<SnAuthSession>,isCurrent: null == isCurrent ? _self.isCurrent : isCurrent // ignore: cast_nullable_to_non_nullable |  | ||||||
| as bool, |  | ||||||
|   )); |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| /// @nodoc | /// @nodoc | ||||||
| mixin _$SnAccountConnection { | mixin _$SnAccountConnection { | ||||||
|  |  | ||||||
|   | |||||||
| @@ -20,7 +20,6 @@ _SnAuthChallenge _$SnAuthChallengeFromJson(Map<String, dynamic> json) => | |||||||
|       stepRemain: (json['step_remain'] as num).toInt(), |       stepRemain: (json['step_remain'] as num).toInt(), | ||||||
|       stepTotal: (json['step_total'] as num).toInt(), |       stepTotal: (json['step_total'] as num).toInt(), | ||||||
|       failedAttempts: (json['failed_attempts'] as num).toInt(), |       failedAttempts: (json['failed_attempts'] as num).toInt(), | ||||||
|       platform: (json['platform'] as num).toInt(), |  | ||||||
|       type: (json['type'] as num).toInt(), |       type: (json['type'] as num).toInt(), | ||||||
|       blacklistFactors: |       blacklistFactors: | ||||||
|           (json['blacklist_factors'] as List<dynamic>) |           (json['blacklist_factors'] as List<dynamic>) | ||||||
| @@ -30,7 +29,6 @@ _SnAuthChallenge _$SnAuthChallengeFromJson(Map<String, dynamic> json) => | |||||||
|       scopes: json['scopes'] as List<dynamic>, |       scopes: json['scopes'] as List<dynamic>, | ||||||
|       ipAddress: json['ip_address'] as String, |       ipAddress: json['ip_address'] as String, | ||||||
|       userAgent: json['user_agent'] as String, |       userAgent: json['user_agent'] as String, | ||||||
|       deviceId: json['device_id'] as String, |  | ||||||
|       nonce: json['nonce'] as String?, |       nonce: json['nonce'] as String?, | ||||||
|       location: json['location'] as String?, |       location: json['location'] as String?, | ||||||
|       accountId: json['account_id'] as String, |       accountId: json['account_id'] as String, | ||||||
| @@ -49,14 +47,12 @@ Map<String, dynamic> _$SnAuthChallengeToJson(_SnAuthChallenge instance) => | |||||||
|       'step_remain': instance.stepRemain, |       'step_remain': instance.stepRemain, | ||||||
|       'step_total': instance.stepTotal, |       'step_total': instance.stepTotal, | ||||||
|       'failed_attempts': instance.failedAttempts, |       'failed_attempts': instance.failedAttempts, | ||||||
|       'platform': instance.platform, |  | ||||||
|       'type': instance.type, |       'type': instance.type, | ||||||
|       'blacklist_factors': instance.blacklistFactors, |       'blacklist_factors': instance.blacklistFactors, | ||||||
|       'audiences': instance.audiences, |       'audiences': instance.audiences, | ||||||
|       'scopes': instance.scopes, |       'scopes': instance.scopes, | ||||||
|       'ip_address': instance.ipAddress, |       'ip_address': instance.ipAddress, | ||||||
|       'user_agent': instance.userAgent, |       'user_agent': instance.userAgent, | ||||||
|       'device_id': instance.deviceId, |  | ||||||
|       'nonce': instance.nonce, |       'nonce': instance.nonce, | ||||||
|       'location': instance.location, |       'location': instance.location, | ||||||
|       'account_id': instance.accountId, |       'account_id': instance.accountId, | ||||||
| @@ -133,29 +129,6 @@ Map<String, dynamic> _$SnAuthFactorToJson(_SnAuthFactor instance) => | |||||||
|       'created_response': instance.createdResponse, |       'created_response': instance.createdResponse, | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
| _SnAuthDevice _$SnAuthDeviceFromJson(Map<String, dynamic> json) => |  | ||||||
|     _SnAuthDevice( |  | ||||||
|       label: json['label'], |  | ||||||
|       userAgent: json['user_agent'] as String, |  | ||||||
|       deviceId: json['device_id'] as String, |  | ||||||
|       platform: (json['platform'] as num).toInt(), |  | ||||||
|       sessions: |  | ||||||
|           (json['sessions'] as List<dynamic>) |  | ||||||
|               .map((e) => SnAuthSession.fromJson(e as Map<String, dynamic>)) |  | ||||||
|               .toList(), |  | ||||||
|       isCurrent: json['is_current'] as bool? ?? false, |  | ||||||
|     ); |  | ||||||
|  |  | ||||||
| Map<String, dynamic> _$SnAuthDeviceToJson(_SnAuthDevice instance) => |  | ||||||
|     <String, dynamic>{ |  | ||||||
|       'label': instance.label, |  | ||||||
|       'user_agent': instance.userAgent, |  | ||||||
|       'device_id': instance.deviceId, |  | ||||||
|       'platform': instance.platform, |  | ||||||
|       'sessions': instance.sessions.map((e) => e.toJson()).toList(), |  | ||||||
|       'is_current': instance.isCurrent, |  | ||||||
|     }; |  | ||||||
|  |  | ||||||
| _SnAccountConnection _$SnAccountConnectionFromJson(Map<String, dynamic> json) => | _SnAccountConnection _$SnAccountConnectionFromJson(Map<String, dynamic> json) => | ||||||
|     _SnAccountConnection( |     _SnAccountConnection( | ||||||
|       id: json['id'] as String, |       id: json['id'] as String, | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| import 'package:freezed_annotation/freezed_annotation.dart'; | import 'package:freezed_annotation/freezed_annotation.dart'; | ||||||
| import 'package:island/models/file.dart'; | import 'package:island/models/file.dart'; | ||||||
| import 'package:island/models/realm.dart'; | import 'package:island/models/realm.dart'; | ||||||
| import 'package:island/models/user.dart'; | import 'package:island/models/account.dart'; | ||||||
|  |  | ||||||
| part 'chat.freezed.dart'; | part 'chat.freezed.dart'; | ||||||
| part 'chat.g.dart'; | part 'chat.g.dart'; | ||||||
| @@ -91,6 +91,7 @@ sealed class SnChatMember with _$SnChatMember { | |||||||
|     required DateTime? breakUntil, |     required DateTime? breakUntil, | ||||||
|     required DateTime? timeoutUntil, |     required DateTime? timeoutUntil, | ||||||
|     required bool isBot, |     required bool isBot, | ||||||
|  |     required SnAccountStatus? status, | ||||||
|     // Frontend data |     // Frontend data | ||||||
|     DateTime? lastTyped, |     DateTime? lastTyped, | ||||||
|   }) = _SnChatMember; |   }) = _SnChatMember; | ||||||
| @@ -103,7 +104,7 @@ sealed class SnChatMember with _$SnChatMember { | |||||||
| sealed class SnChatSummary with _$SnChatSummary { | sealed class SnChatSummary with _$SnChatSummary { | ||||||
|   const factory SnChatSummary({ |   const factory SnChatSummary({ | ||||||
|     required int unreadCount, |     required int unreadCount, | ||||||
|     required SnChatMessage lastMessage, |     required SnChatMessage? lastMessage, | ||||||
|   }) = _SnChatSummary; |   }) = _SnChatSummary; | ||||||
|  |  | ||||||
|   factory SnChatSummary.fromJson(Map<String, dynamic> json) => |   factory SnChatSummary.fromJson(Map<String, dynamic> json) => | ||||||
|   | |||||||
| @@ -1037,7 +1037,7 @@ $SnChatMemberCopyWith<$Res> get sender { | |||||||
| /// @nodoc | /// @nodoc | ||||||
| mixin _$SnChatMember { | mixin _$SnChatMember { | ||||||
|  |  | ||||||
|  DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; String get id; String get chatRoomId; SnChatRoom? get chatRoom; String get accountId; SnAccount get account; String? get nick; int get role; int get notify; DateTime? get joinedAt; DateTime? get breakUntil; DateTime? get timeoutUntil; bool get isBot;// Frontend data |  DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; String get id; String get chatRoomId; SnChatRoom? get chatRoom; String get accountId; SnAccount get account; String? get nick; int get role; int get notify; DateTime? get joinedAt; DateTime? get breakUntil; DateTime? get timeoutUntil; bool get isBot; SnAccountStatus? get status;// Frontend data | ||||||
|  DateTime? get lastTyped; |  DateTime? get lastTyped; | ||||||
| /// Create a copy of SnChatMember | /// Create a copy of SnChatMember | ||||||
| /// with the given fields replaced by the non-null parameter values. | /// with the given fields replaced by the non-null parameter values. | ||||||
| @@ -1051,16 +1051,16 @@ $SnChatMemberCopyWith<SnChatMember> get copyWith => _$SnChatMemberCopyWithImpl<S | |||||||
|  |  | ||||||
| @override | @override | ||||||
| bool operator ==(Object other) { | bool operator ==(Object other) { | ||||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is SnChatMember&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.id, id) || other.id == id)&&(identical(other.chatRoomId, chatRoomId) || other.chatRoomId == chatRoomId)&&(identical(other.chatRoom, chatRoom) || other.chatRoom == chatRoom)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.account, account) || other.account == account)&&(identical(other.nick, nick) || other.nick == nick)&&(identical(other.role, role) || other.role == role)&&(identical(other.notify, notify) || other.notify == notify)&&(identical(other.joinedAt, joinedAt) || other.joinedAt == joinedAt)&&(identical(other.breakUntil, breakUntil) || other.breakUntil == breakUntil)&&(identical(other.timeoutUntil, timeoutUntil) || other.timeoutUntil == timeoutUntil)&&(identical(other.isBot, isBot) || other.isBot == isBot)&&(identical(other.lastTyped, lastTyped) || other.lastTyped == lastTyped)); |   return identical(this, other) || (other.runtimeType == runtimeType&&other is SnChatMember&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.id, id) || other.id == id)&&(identical(other.chatRoomId, chatRoomId) || other.chatRoomId == chatRoomId)&&(identical(other.chatRoom, chatRoom) || other.chatRoom == chatRoom)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.account, account) || other.account == account)&&(identical(other.nick, nick) || other.nick == nick)&&(identical(other.role, role) || other.role == role)&&(identical(other.notify, notify) || other.notify == notify)&&(identical(other.joinedAt, joinedAt) || other.joinedAt == joinedAt)&&(identical(other.breakUntil, breakUntil) || other.breakUntil == breakUntil)&&(identical(other.timeoutUntil, timeoutUntil) || other.timeoutUntil == timeoutUntil)&&(identical(other.isBot, isBot) || other.isBot == isBot)&&(identical(other.status, status) || other.status == status)&&(identical(other.lastTyped, lastTyped) || other.lastTyped == lastTyped)); | ||||||
| } | } | ||||||
|  |  | ||||||
| @JsonKey(includeFromJson: false, includeToJson: false) | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
| @override | @override | ||||||
| int get hashCode => Object.hash(runtimeType,createdAt,updatedAt,deletedAt,id,chatRoomId,chatRoom,accountId,account,nick,role,notify,joinedAt,breakUntil,timeoutUntil,isBot,lastTyped); | int get hashCode => Object.hash(runtimeType,createdAt,updatedAt,deletedAt,id,chatRoomId,chatRoom,accountId,account,nick,role,notify,joinedAt,breakUntil,timeoutUntil,isBot,status,lastTyped); | ||||||
|  |  | ||||||
| @override | @override | ||||||
| String toString() { | String toString() { | ||||||
|   return 'SnChatMember(createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, id: $id, chatRoomId: $chatRoomId, chatRoom: $chatRoom, accountId: $accountId, account: $account, nick: $nick, role: $role, notify: $notify, joinedAt: $joinedAt, breakUntil: $breakUntil, timeoutUntil: $timeoutUntil, isBot: $isBot, lastTyped: $lastTyped)'; |   return 'SnChatMember(createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, id: $id, chatRoomId: $chatRoomId, chatRoom: $chatRoom, accountId: $accountId, account: $account, nick: $nick, role: $role, notify: $notify, joinedAt: $joinedAt, breakUntil: $breakUntil, timeoutUntil: $timeoutUntil, isBot: $isBot, status: $status, lastTyped: $lastTyped)'; | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -1071,11 +1071,11 @@ abstract mixin class $SnChatMemberCopyWith<$Res>  { | |||||||
|   factory $SnChatMemberCopyWith(SnChatMember value, $Res Function(SnChatMember) _then) = _$SnChatMemberCopyWithImpl; |   factory $SnChatMemberCopyWith(SnChatMember value, $Res Function(SnChatMember) _then) = _$SnChatMemberCopyWithImpl; | ||||||
| @useResult | @useResult | ||||||
| $Res call({ | $Res call({ | ||||||
|  DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, String id, String chatRoomId, SnChatRoom? chatRoom, String accountId, SnAccount account, String? nick, int role, int notify, DateTime? joinedAt, DateTime? breakUntil, DateTime? timeoutUntil, bool isBot, DateTime? lastTyped |  DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, String id, String chatRoomId, SnChatRoom? chatRoom, String accountId, SnAccount account, String? nick, int role, int notify, DateTime? joinedAt, DateTime? breakUntil, DateTime? timeoutUntil, bool isBot, SnAccountStatus? status, DateTime? lastTyped | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  |  | ||||||
| $SnChatRoomCopyWith<$Res>? get chatRoom;$SnAccountCopyWith<$Res> get account; | $SnChatRoomCopyWith<$Res>? get chatRoom;$SnAccountCopyWith<$Res> get account;$SnAccountStatusCopyWith<$Res>? get status; | ||||||
|  |  | ||||||
| } | } | ||||||
| /// @nodoc | /// @nodoc | ||||||
| @@ -1088,7 +1088,7 @@ class _$SnChatMemberCopyWithImpl<$Res> | |||||||
|  |  | ||||||
| /// Create a copy of SnChatMember | /// Create a copy of SnChatMember | ||||||
| /// 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? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? id = null,Object? chatRoomId = null,Object? chatRoom = freezed,Object? accountId = null,Object? account = null,Object? nick = freezed,Object? role = null,Object? notify = null,Object? joinedAt = freezed,Object? breakUntil = freezed,Object? timeoutUntil = freezed,Object? isBot = null,Object? lastTyped = freezed,}) { | @pragma('vm:prefer-inline') @override $Res call({Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? id = null,Object? chatRoomId = null,Object? chatRoom = freezed,Object? accountId = null,Object? account = null,Object? nick = freezed,Object? role = null,Object? notify = null,Object? joinedAt = freezed,Object? breakUntil = freezed,Object? timeoutUntil = freezed,Object? isBot = null,Object? status = freezed,Object? lastTyped = freezed,}) { | ||||||
|   return _then(_self.copyWith( |   return _then(_self.copyWith( | ||||||
| createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable | 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 | ||||||
| @@ -1105,7 +1105,8 @@ as int,joinedAt: freezed == joinedAt ? _self.joinedAt : joinedAt // ignore: cast | |||||||
| as DateTime?,breakUntil: freezed == breakUntil ? _self.breakUntil : breakUntil // ignore: cast_nullable_to_non_nullable | as DateTime?,breakUntil: freezed == breakUntil ? _self.breakUntil : breakUntil // ignore: cast_nullable_to_non_nullable | ||||||
| as DateTime?,timeoutUntil: freezed == timeoutUntil ? _self.timeoutUntil : timeoutUntil // ignore: cast_nullable_to_non_nullable | as DateTime?,timeoutUntil: freezed == timeoutUntil ? _self.timeoutUntil : timeoutUntil // ignore: cast_nullable_to_non_nullable | ||||||
| as DateTime?,isBot: null == isBot ? _self.isBot : isBot // ignore: cast_nullable_to_non_nullable | as DateTime?,isBot: null == isBot ? _self.isBot : isBot // ignore: cast_nullable_to_non_nullable | ||||||
| as bool,lastTyped: freezed == lastTyped ? _self.lastTyped : lastTyped // ignore: cast_nullable_to_non_nullable | as bool,status: freezed == status ? _self.status : status // ignore: cast_nullable_to_non_nullable | ||||||
|  | as SnAccountStatus?,lastTyped: freezed == lastTyped ? _self.lastTyped : lastTyped // ignore: cast_nullable_to_non_nullable | ||||||
| as DateTime?, | as DateTime?, | ||||||
|   )); |   )); | ||||||
| } | } | ||||||
| @@ -1130,6 +1131,18 @@ $SnAccountCopyWith<$Res> get account { | |||||||
|   return $SnAccountCopyWith<$Res>(_self.account, (value) { |   return $SnAccountCopyWith<$Res>(_self.account, (value) { | ||||||
|     return _then(_self.copyWith(account: value)); |     return _then(_self.copyWith(account: value)); | ||||||
|   }); |   }); | ||||||
|  | }/// Create a copy of SnChatMember | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
|  | $SnAccountStatusCopyWith<$Res>? get status { | ||||||
|  |     if (_self.status == null) { | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return $SnAccountStatusCopyWith<$Res>(_self.status!, (value) { | ||||||
|  |     return _then(_self.copyWith(status: value)); | ||||||
|  |   }); | ||||||
| } | } | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -1209,10 +1222,10 @@ return $default(_that);case _: | |||||||
| /// } | /// } | ||||||
| /// ``` | /// ``` | ||||||
|  |  | ||||||
| @optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( DateTime createdAt,  DateTime updatedAt,  DateTime? deletedAt,  String id,  String chatRoomId,  SnChatRoom? chatRoom,  String accountId,  SnAccount account,  String? nick,  int role,  int notify,  DateTime? joinedAt,  DateTime? breakUntil,  DateTime? timeoutUntil,  bool isBot,  DateTime? lastTyped)?  $default,{required TResult orElse(),}) {final _that = this; | @optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( DateTime createdAt,  DateTime updatedAt,  DateTime? deletedAt,  String id,  String chatRoomId,  SnChatRoom? chatRoom,  String accountId,  SnAccount account,  String? nick,  int role,  int notify,  DateTime? joinedAt,  DateTime? breakUntil,  DateTime? timeoutUntil,  bool isBot,  SnAccountStatus? status,  DateTime? lastTyped)?  $default,{required TResult orElse(),}) {final _that = this; | ||||||
| switch (_that) { | switch (_that) { | ||||||
| case _SnChatMember() when $default != null: | case _SnChatMember() when $default != null: | ||||||
| return $default(_that.createdAt,_that.updatedAt,_that.deletedAt,_that.id,_that.chatRoomId,_that.chatRoom,_that.accountId,_that.account,_that.nick,_that.role,_that.notify,_that.joinedAt,_that.breakUntil,_that.timeoutUntil,_that.isBot,_that.lastTyped);case _: | return $default(_that.createdAt,_that.updatedAt,_that.deletedAt,_that.id,_that.chatRoomId,_that.chatRoom,_that.accountId,_that.account,_that.nick,_that.role,_that.notify,_that.joinedAt,_that.breakUntil,_that.timeoutUntil,_that.isBot,_that.status,_that.lastTyped);case _: | ||||||
|   return orElse(); |   return orElse(); | ||||||
|  |  | ||||||
| } | } | ||||||
| @@ -1230,10 +1243,10 @@ return $default(_that.createdAt,_that.updatedAt,_that.deletedAt,_that.id,_that.c | |||||||
| /// } | /// } | ||||||
| /// ``` | /// ``` | ||||||
|  |  | ||||||
| @optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( DateTime createdAt,  DateTime updatedAt,  DateTime? deletedAt,  String id,  String chatRoomId,  SnChatRoom? chatRoom,  String accountId,  SnAccount account,  String? nick,  int role,  int notify,  DateTime? joinedAt,  DateTime? breakUntil,  DateTime? timeoutUntil,  bool isBot,  DateTime? lastTyped)  $default,) {final _that = this; | @optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( DateTime createdAt,  DateTime updatedAt,  DateTime? deletedAt,  String id,  String chatRoomId,  SnChatRoom? chatRoom,  String accountId,  SnAccount account,  String? nick,  int role,  int notify,  DateTime? joinedAt,  DateTime? breakUntil,  DateTime? timeoutUntil,  bool isBot,  SnAccountStatus? status,  DateTime? lastTyped)  $default,) {final _that = this; | ||||||
| switch (_that) { | switch (_that) { | ||||||
| case _SnChatMember(): | case _SnChatMember(): | ||||||
| return $default(_that.createdAt,_that.updatedAt,_that.deletedAt,_that.id,_that.chatRoomId,_that.chatRoom,_that.accountId,_that.account,_that.nick,_that.role,_that.notify,_that.joinedAt,_that.breakUntil,_that.timeoutUntil,_that.isBot,_that.lastTyped);} | return $default(_that.createdAt,_that.updatedAt,_that.deletedAt,_that.id,_that.chatRoomId,_that.chatRoom,_that.accountId,_that.account,_that.nick,_that.role,_that.notify,_that.joinedAt,_that.breakUntil,_that.timeoutUntil,_that.isBot,_that.status,_that.lastTyped);} | ||||||
| } | } | ||||||
| /// A variant of `when` that fallback to returning `null` | /// A variant of `when` that fallback to returning `null` | ||||||
| /// | /// | ||||||
| @@ -1247,10 +1260,10 @@ return $default(_that.createdAt,_that.updatedAt,_that.deletedAt,_that.id,_that.c | |||||||
| /// } | /// } | ||||||
| /// ``` | /// ``` | ||||||
|  |  | ||||||
| @optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( DateTime createdAt,  DateTime updatedAt,  DateTime? deletedAt,  String id,  String chatRoomId,  SnChatRoom? chatRoom,  String accountId,  SnAccount account,  String? nick,  int role,  int notify,  DateTime? joinedAt,  DateTime? breakUntil,  DateTime? timeoutUntil,  bool isBot,  DateTime? lastTyped)?  $default,) {final _that = this; | @optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( DateTime createdAt,  DateTime updatedAt,  DateTime? deletedAt,  String id,  String chatRoomId,  SnChatRoom? chatRoom,  String accountId,  SnAccount account,  String? nick,  int role,  int notify,  DateTime? joinedAt,  DateTime? breakUntil,  DateTime? timeoutUntil,  bool isBot,  SnAccountStatus? status,  DateTime? lastTyped)?  $default,) {final _that = this; | ||||||
| switch (_that) { | switch (_that) { | ||||||
| case _SnChatMember() when $default != null: | case _SnChatMember() when $default != null: | ||||||
| return $default(_that.createdAt,_that.updatedAt,_that.deletedAt,_that.id,_that.chatRoomId,_that.chatRoom,_that.accountId,_that.account,_that.nick,_that.role,_that.notify,_that.joinedAt,_that.breakUntil,_that.timeoutUntil,_that.isBot,_that.lastTyped);case _: | return $default(_that.createdAt,_that.updatedAt,_that.deletedAt,_that.id,_that.chatRoomId,_that.chatRoom,_that.accountId,_that.account,_that.nick,_that.role,_that.notify,_that.joinedAt,_that.breakUntil,_that.timeoutUntil,_that.isBot,_that.status,_that.lastTyped);case _: | ||||||
|   return null; |   return null; | ||||||
|  |  | ||||||
| } | } | ||||||
| @@ -1262,7 +1275,7 @@ return $default(_that.createdAt,_that.updatedAt,_that.deletedAt,_that.id,_that.c | |||||||
| @JsonSerializable() | @JsonSerializable() | ||||||
|  |  | ||||||
| class _SnChatMember implements SnChatMember { | class _SnChatMember implements SnChatMember { | ||||||
|   const _SnChatMember({required this.createdAt, required this.updatedAt, required this.deletedAt, required this.id, required this.chatRoomId, required this.chatRoom, required this.accountId, required this.account, required this.nick, required this.role, required this.notify, required this.joinedAt, required this.breakUntil, required this.timeoutUntil, required this.isBot, this.lastTyped}); |   const _SnChatMember({required this.createdAt, required this.updatedAt, required this.deletedAt, required this.id, required this.chatRoomId, required this.chatRoom, required this.accountId, required this.account, required this.nick, required this.role, required this.notify, required this.joinedAt, required this.breakUntil, required this.timeoutUntil, required this.isBot, required this.status, this.lastTyped}); | ||||||
|   factory _SnChatMember.fromJson(Map<String, dynamic> json) => _$SnChatMemberFromJson(json); |   factory _SnChatMember.fromJson(Map<String, dynamic> json) => _$SnChatMemberFromJson(json); | ||||||
|  |  | ||||||
| @override final  DateTime createdAt; | @override final  DateTime createdAt; | ||||||
| @@ -1280,6 +1293,7 @@ class _SnChatMember implements SnChatMember { | |||||||
| @override final  DateTime? breakUntil; | @override final  DateTime? breakUntil; | ||||||
| @override final  DateTime? timeoutUntil; | @override final  DateTime? timeoutUntil; | ||||||
| @override final  bool isBot; | @override final  bool isBot; | ||||||
|  | @override final  SnAccountStatus? status; | ||||||
| // Frontend data | // Frontend data | ||||||
| @override final  DateTime? lastTyped; | @override final  DateTime? lastTyped; | ||||||
|  |  | ||||||
| @@ -1296,16 +1310,16 @@ Map<String, dynamic> toJson() { | |||||||
|  |  | ||||||
| @override | @override | ||||||
| bool operator ==(Object other) { | bool operator ==(Object other) { | ||||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnChatMember&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.id, id) || other.id == id)&&(identical(other.chatRoomId, chatRoomId) || other.chatRoomId == chatRoomId)&&(identical(other.chatRoom, chatRoom) || other.chatRoom == chatRoom)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.account, account) || other.account == account)&&(identical(other.nick, nick) || other.nick == nick)&&(identical(other.role, role) || other.role == role)&&(identical(other.notify, notify) || other.notify == notify)&&(identical(other.joinedAt, joinedAt) || other.joinedAt == joinedAt)&&(identical(other.breakUntil, breakUntil) || other.breakUntil == breakUntil)&&(identical(other.timeoutUntil, timeoutUntil) || other.timeoutUntil == timeoutUntil)&&(identical(other.isBot, isBot) || other.isBot == isBot)&&(identical(other.lastTyped, lastTyped) || other.lastTyped == lastTyped)); |   return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnChatMember&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.id, id) || other.id == id)&&(identical(other.chatRoomId, chatRoomId) || other.chatRoomId == chatRoomId)&&(identical(other.chatRoom, chatRoom) || other.chatRoom == chatRoom)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.account, account) || other.account == account)&&(identical(other.nick, nick) || other.nick == nick)&&(identical(other.role, role) || other.role == role)&&(identical(other.notify, notify) || other.notify == notify)&&(identical(other.joinedAt, joinedAt) || other.joinedAt == joinedAt)&&(identical(other.breakUntil, breakUntil) || other.breakUntil == breakUntil)&&(identical(other.timeoutUntil, timeoutUntil) || other.timeoutUntil == timeoutUntil)&&(identical(other.isBot, isBot) || other.isBot == isBot)&&(identical(other.status, status) || other.status == status)&&(identical(other.lastTyped, lastTyped) || other.lastTyped == lastTyped)); | ||||||
| } | } | ||||||
|  |  | ||||||
| @JsonKey(includeFromJson: false, includeToJson: false) | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
| @override | @override | ||||||
| int get hashCode => Object.hash(runtimeType,createdAt,updatedAt,deletedAt,id,chatRoomId,chatRoom,accountId,account,nick,role,notify,joinedAt,breakUntil,timeoutUntil,isBot,lastTyped); | int get hashCode => Object.hash(runtimeType,createdAt,updatedAt,deletedAt,id,chatRoomId,chatRoom,accountId,account,nick,role,notify,joinedAt,breakUntil,timeoutUntil,isBot,status,lastTyped); | ||||||
|  |  | ||||||
| @override | @override | ||||||
| String toString() { | String toString() { | ||||||
|   return 'SnChatMember(createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, id: $id, chatRoomId: $chatRoomId, chatRoom: $chatRoom, accountId: $accountId, account: $account, nick: $nick, role: $role, notify: $notify, joinedAt: $joinedAt, breakUntil: $breakUntil, timeoutUntil: $timeoutUntil, isBot: $isBot, lastTyped: $lastTyped)'; |   return 'SnChatMember(createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, id: $id, chatRoomId: $chatRoomId, chatRoom: $chatRoom, accountId: $accountId, account: $account, nick: $nick, role: $role, notify: $notify, joinedAt: $joinedAt, breakUntil: $breakUntil, timeoutUntil: $timeoutUntil, isBot: $isBot, status: $status, lastTyped: $lastTyped)'; | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -1316,11 +1330,11 @@ abstract mixin class _$SnChatMemberCopyWith<$Res> implements $SnChatMemberCopyWi | |||||||
|   factory _$SnChatMemberCopyWith(_SnChatMember value, $Res Function(_SnChatMember) _then) = __$SnChatMemberCopyWithImpl; |   factory _$SnChatMemberCopyWith(_SnChatMember value, $Res Function(_SnChatMember) _then) = __$SnChatMemberCopyWithImpl; | ||||||
| @override @useResult | @override @useResult | ||||||
| $Res call({ | $Res call({ | ||||||
|  DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, String id, String chatRoomId, SnChatRoom? chatRoom, String accountId, SnAccount account, String? nick, int role, int notify, DateTime? joinedAt, DateTime? breakUntil, DateTime? timeoutUntil, bool isBot, DateTime? lastTyped |  DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, String id, String chatRoomId, SnChatRoom? chatRoom, String accountId, SnAccount account, String? nick, int role, int notify, DateTime? joinedAt, DateTime? breakUntil, DateTime? timeoutUntil, bool isBot, SnAccountStatus? status, DateTime? lastTyped | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  |  | ||||||
| @override $SnChatRoomCopyWith<$Res>? get chatRoom;@override $SnAccountCopyWith<$Res> get account; | @override $SnChatRoomCopyWith<$Res>? get chatRoom;@override $SnAccountCopyWith<$Res> get account;@override $SnAccountStatusCopyWith<$Res>? get status; | ||||||
|  |  | ||||||
| } | } | ||||||
| /// @nodoc | /// @nodoc | ||||||
| @@ -1333,7 +1347,7 @@ class __$SnChatMemberCopyWithImpl<$Res> | |||||||
|  |  | ||||||
| /// Create a copy of SnChatMember | /// Create a copy of SnChatMember | ||||||
| /// 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? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? id = null,Object? chatRoomId = null,Object? chatRoom = freezed,Object? accountId = null,Object? account = null,Object? nick = freezed,Object? role = null,Object? notify = null,Object? joinedAt = freezed,Object? breakUntil = freezed,Object? timeoutUntil = freezed,Object? isBot = null,Object? lastTyped = freezed,}) { | @override @pragma('vm:prefer-inline') $Res call({Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? id = null,Object? chatRoomId = null,Object? chatRoom = freezed,Object? accountId = null,Object? account = null,Object? nick = freezed,Object? role = null,Object? notify = null,Object? joinedAt = freezed,Object? breakUntil = freezed,Object? timeoutUntil = freezed,Object? isBot = null,Object? status = freezed,Object? lastTyped = freezed,}) { | ||||||
|   return _then(_SnChatMember( |   return _then(_SnChatMember( | ||||||
| createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable | 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 | ||||||
| @@ -1350,7 +1364,8 @@ as int,joinedAt: freezed == joinedAt ? _self.joinedAt : joinedAt // ignore: cast | |||||||
| as DateTime?,breakUntil: freezed == breakUntil ? _self.breakUntil : breakUntil // ignore: cast_nullable_to_non_nullable | as DateTime?,breakUntil: freezed == breakUntil ? _self.breakUntil : breakUntil // ignore: cast_nullable_to_non_nullable | ||||||
| as DateTime?,timeoutUntil: freezed == timeoutUntil ? _self.timeoutUntil : timeoutUntil // ignore: cast_nullable_to_non_nullable | as DateTime?,timeoutUntil: freezed == timeoutUntil ? _self.timeoutUntil : timeoutUntil // ignore: cast_nullable_to_non_nullable | ||||||
| as DateTime?,isBot: null == isBot ? _self.isBot : isBot // ignore: cast_nullable_to_non_nullable | as DateTime?,isBot: null == isBot ? _self.isBot : isBot // ignore: cast_nullable_to_non_nullable | ||||||
| as bool,lastTyped: freezed == lastTyped ? _self.lastTyped : lastTyped // ignore: cast_nullable_to_non_nullable | as bool,status: freezed == status ? _self.status : status // ignore: cast_nullable_to_non_nullable | ||||||
|  | as SnAccountStatus?,lastTyped: freezed == lastTyped ? _self.lastTyped : lastTyped // ignore: cast_nullable_to_non_nullable | ||||||
| as DateTime?, | as DateTime?, | ||||||
|   )); |   )); | ||||||
| } | } | ||||||
| @@ -1376,6 +1391,18 @@ $SnAccountCopyWith<$Res> get account { | |||||||
|   return $SnAccountCopyWith<$Res>(_self.account, (value) { |   return $SnAccountCopyWith<$Res>(_self.account, (value) { | ||||||
|     return _then(_self.copyWith(account: value)); |     return _then(_self.copyWith(account: value)); | ||||||
|   }); |   }); | ||||||
|  | }/// Create a copy of SnChatMember | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
|  | $SnAccountStatusCopyWith<$Res>? get status { | ||||||
|  |     if (_self.status == null) { | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return $SnAccountStatusCopyWith<$Res>(_self.status!, (value) { | ||||||
|  |     return _then(_self.copyWith(status: value)); | ||||||
|  |   }); | ||||||
| } | } | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -1383,7 +1410,7 @@ $SnAccountCopyWith<$Res> get account { | |||||||
| /// @nodoc | /// @nodoc | ||||||
| mixin _$SnChatSummary { | mixin _$SnChatSummary { | ||||||
|  |  | ||||||
|  int get unreadCount; SnChatMessage get lastMessage; |  int get unreadCount; SnChatMessage? get lastMessage; | ||||||
| /// Create a copy of SnChatSummary | /// Create a copy of SnChatSummary | ||||||
| /// 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) | ||||||
| @@ -1416,11 +1443,11 @@ abstract mixin class $SnChatSummaryCopyWith<$Res>  { | |||||||
|   factory $SnChatSummaryCopyWith(SnChatSummary value, $Res Function(SnChatSummary) _then) = _$SnChatSummaryCopyWithImpl; |   factory $SnChatSummaryCopyWith(SnChatSummary value, $Res Function(SnChatSummary) _then) = _$SnChatSummaryCopyWithImpl; | ||||||
| @useResult | @useResult | ||||||
| $Res call({ | $Res call({ | ||||||
|  int unreadCount, SnChatMessage lastMessage |  int unreadCount, SnChatMessage? lastMessage | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  |  | ||||||
| $SnChatMessageCopyWith<$Res> get lastMessage; | $SnChatMessageCopyWith<$Res>? get lastMessage; | ||||||
|  |  | ||||||
| } | } | ||||||
| /// @nodoc | /// @nodoc | ||||||
| @@ -1433,20 +1460,23 @@ class _$SnChatSummaryCopyWithImpl<$Res> | |||||||
|  |  | ||||||
| /// Create a copy of SnChatSummary | /// Create a copy of SnChatSummary | ||||||
| /// 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? unreadCount = null,Object? lastMessage = null,}) { | @pragma('vm:prefer-inline') @override $Res call({Object? unreadCount = null,Object? lastMessage = freezed,}) { | ||||||
|   return _then(_self.copyWith( |   return _then(_self.copyWith( | ||||||
| unreadCount: null == unreadCount ? _self.unreadCount : unreadCount // ignore: cast_nullable_to_non_nullable | unreadCount: null == unreadCount ? _self.unreadCount : unreadCount // ignore: cast_nullable_to_non_nullable | ||||||
| as int,lastMessage: null == lastMessage ? _self.lastMessage : lastMessage // ignore: cast_nullable_to_non_nullable | as int,lastMessage: freezed == lastMessage ? _self.lastMessage : lastMessage // ignore: cast_nullable_to_non_nullable | ||||||
| as SnChatMessage, | as SnChatMessage?, | ||||||
|   )); |   )); | ||||||
| } | } | ||||||
| /// Create a copy of SnChatSummary | /// Create a copy of SnChatSummary | ||||||
| /// 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') | ||||||
| $SnChatMessageCopyWith<$Res> get lastMessage { | $SnChatMessageCopyWith<$Res>? get lastMessage { | ||||||
|    |     if (_self.lastMessage == null) { | ||||||
|   return $SnChatMessageCopyWith<$Res>(_self.lastMessage, (value) { |     return null; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return $SnChatMessageCopyWith<$Res>(_self.lastMessage!, (value) { | ||||||
|     return _then(_self.copyWith(lastMessage: value)); |     return _then(_self.copyWith(lastMessage: value)); | ||||||
|   }); |   }); | ||||||
| } | } | ||||||
| @@ -1528,7 +1558,7 @@ return $default(_that);case _: | |||||||
| /// } | /// } | ||||||
| /// ``` | /// ``` | ||||||
|  |  | ||||||
| @optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( int unreadCount,  SnChatMessage lastMessage)?  $default,{required TResult orElse(),}) {final _that = this; | @optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( int unreadCount,  SnChatMessage? lastMessage)?  $default,{required TResult orElse(),}) {final _that = this; | ||||||
| switch (_that) { | switch (_that) { | ||||||
| case _SnChatSummary() when $default != null: | case _SnChatSummary() when $default != null: | ||||||
| return $default(_that.unreadCount,_that.lastMessage);case _: | return $default(_that.unreadCount,_that.lastMessage);case _: | ||||||
| @@ -1549,7 +1579,7 @@ return $default(_that.unreadCount,_that.lastMessage);case _: | |||||||
| /// } | /// } | ||||||
| /// ``` | /// ``` | ||||||
|  |  | ||||||
| @optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( int unreadCount,  SnChatMessage lastMessage)  $default,) {final _that = this; | @optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( int unreadCount,  SnChatMessage? lastMessage)  $default,) {final _that = this; | ||||||
| switch (_that) { | switch (_that) { | ||||||
| case _SnChatSummary(): | case _SnChatSummary(): | ||||||
| return $default(_that.unreadCount,_that.lastMessage);} | return $default(_that.unreadCount,_that.lastMessage);} | ||||||
| @@ -1566,7 +1596,7 @@ return $default(_that.unreadCount,_that.lastMessage);} | |||||||
| /// } | /// } | ||||||
| /// ``` | /// ``` | ||||||
|  |  | ||||||
| @optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( int unreadCount,  SnChatMessage lastMessage)?  $default,) {final _that = this; | @optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( int unreadCount,  SnChatMessage? lastMessage)?  $default,) {final _that = this; | ||||||
| switch (_that) { | switch (_that) { | ||||||
| case _SnChatSummary() when $default != null: | case _SnChatSummary() when $default != null: | ||||||
| return $default(_that.unreadCount,_that.lastMessage);case _: | return $default(_that.unreadCount,_that.lastMessage);case _: | ||||||
| @@ -1585,7 +1615,7 @@ class _SnChatSummary implements SnChatSummary { | |||||||
|   factory _SnChatSummary.fromJson(Map<String, dynamic> json) => _$SnChatSummaryFromJson(json); |   factory _SnChatSummary.fromJson(Map<String, dynamic> json) => _$SnChatSummaryFromJson(json); | ||||||
|  |  | ||||||
| @override final  int unreadCount; | @override final  int unreadCount; | ||||||
| @override final  SnChatMessage lastMessage; | @override final  SnChatMessage? lastMessage; | ||||||
|  |  | ||||||
| /// Create a copy of SnChatSummary | /// Create a copy of SnChatSummary | ||||||
| /// with the given fields replaced by the non-null parameter values. | /// with the given fields replaced by the non-null parameter values. | ||||||
| @@ -1620,11 +1650,11 @@ abstract mixin class _$SnChatSummaryCopyWith<$Res> implements $SnChatSummaryCopy | |||||||
|   factory _$SnChatSummaryCopyWith(_SnChatSummary value, $Res Function(_SnChatSummary) _then) = __$SnChatSummaryCopyWithImpl; |   factory _$SnChatSummaryCopyWith(_SnChatSummary value, $Res Function(_SnChatSummary) _then) = __$SnChatSummaryCopyWithImpl; | ||||||
| @override @useResult | @override @useResult | ||||||
| $Res call({ | $Res call({ | ||||||
|  int unreadCount, SnChatMessage lastMessage |  int unreadCount, SnChatMessage? lastMessage | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  |  | ||||||
| @override $SnChatMessageCopyWith<$Res> get lastMessage; | @override $SnChatMessageCopyWith<$Res>? get lastMessage; | ||||||
|  |  | ||||||
| } | } | ||||||
| /// @nodoc | /// @nodoc | ||||||
| @@ -1637,11 +1667,11 @@ class __$SnChatSummaryCopyWithImpl<$Res> | |||||||
|  |  | ||||||
| /// Create a copy of SnChatSummary | /// Create a copy of SnChatSummary | ||||||
| /// 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? unreadCount = null,Object? lastMessage = null,}) { | @override @pragma('vm:prefer-inline') $Res call({Object? unreadCount = null,Object? lastMessage = freezed,}) { | ||||||
|   return _then(_SnChatSummary( |   return _then(_SnChatSummary( | ||||||
| unreadCount: null == unreadCount ? _self.unreadCount : unreadCount // ignore: cast_nullable_to_non_nullable | unreadCount: null == unreadCount ? _self.unreadCount : unreadCount // ignore: cast_nullable_to_non_nullable | ||||||
| as int,lastMessage: null == lastMessage ? _self.lastMessage : lastMessage // ignore: cast_nullable_to_non_nullable | as int,lastMessage: freezed == lastMessage ? _self.lastMessage : lastMessage // ignore: cast_nullable_to_non_nullable | ||||||
| as SnChatMessage, | as SnChatMessage?, | ||||||
|   )); |   )); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -1649,9 +1679,12 @@ as SnChatMessage, | |||||||
| /// 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') | ||||||
| $SnChatMessageCopyWith<$Res> get lastMessage { | $SnChatMessageCopyWith<$Res>? get lastMessage { | ||||||
|    |     if (_self.lastMessage == null) { | ||||||
|   return $SnChatMessageCopyWith<$Res>(_self.lastMessage, (value) { |     return null; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return $SnChatMessageCopyWith<$Res>(_self.lastMessage!, (value) { | ||||||
|     return _then(_self.copyWith(lastMessage: value)); |     return _then(_self.copyWith(lastMessage: value)); | ||||||
|   }); |   }); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -177,6 +177,12 @@ _SnChatMember _$SnChatMemberFromJson(Map<String, dynamic> json) => | |||||||
|               ? null |               ? null | ||||||
|               : DateTime.parse(json['timeout_until'] as String), |               : DateTime.parse(json['timeout_until'] as String), | ||||||
|       isBot: json['is_bot'] as bool, |       isBot: json['is_bot'] as bool, | ||||||
|  |       status: | ||||||
|  |           json['status'] == null | ||||||
|  |               ? null | ||||||
|  |               : SnAccountStatus.fromJson( | ||||||
|  |                 json['status'] as Map<String, dynamic>, | ||||||
|  |               ), | ||||||
|       lastTyped: |       lastTyped: | ||||||
|           json['last_typed'] == null |           json['last_typed'] == null | ||||||
|               ? null |               ? null | ||||||
| @@ -200,21 +206,25 @@ Map<String, dynamic> _$SnChatMemberToJson(_SnChatMember instance) => | |||||||
|       'break_until': instance.breakUntil?.toIso8601String(), |       'break_until': instance.breakUntil?.toIso8601String(), | ||||||
|       'timeout_until': instance.timeoutUntil?.toIso8601String(), |       'timeout_until': instance.timeoutUntil?.toIso8601String(), | ||||||
|       'is_bot': instance.isBot, |       'is_bot': instance.isBot, | ||||||
|  |       'status': instance.status?.toJson(), | ||||||
|       'last_typed': instance.lastTyped?.toIso8601String(), |       'last_typed': instance.lastTyped?.toIso8601String(), | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
| _SnChatSummary _$SnChatSummaryFromJson(Map<String, dynamic> json) => | _SnChatSummary _$SnChatSummaryFromJson(Map<String, dynamic> json) => | ||||||
|     _SnChatSummary( |     _SnChatSummary( | ||||||
|       unreadCount: (json['unread_count'] as num).toInt(), |       unreadCount: (json['unread_count'] as num).toInt(), | ||||||
|       lastMessage: SnChatMessage.fromJson( |       lastMessage: | ||||||
|         json['last_message'] as Map<String, dynamic>, |           json['last_message'] == null | ||||||
|       ), |               ? null | ||||||
|  |               : SnChatMessage.fromJson( | ||||||
|  |                 json['last_message'] as Map<String, dynamic>, | ||||||
|  |               ), | ||||||
|     ); |     ); | ||||||
|  |  | ||||||
| Map<String, dynamic> _$SnChatSummaryToJson(_SnChatSummary instance) => | Map<String, dynamic> _$SnChatSummaryToJson(_SnChatSummary instance) => | ||||||
|     <String, dynamic>{ |     <String, dynamic>{ | ||||||
|       'unread_count': instance.unreadCount, |       'unread_count': instance.unreadCount, | ||||||
|       'last_message': instance.lastMessage.toJson(), |       'last_message': instance.lastMessage?.toJson(), | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
| _MessageChange _$MessageChangeFromJson(Map<String, dynamic> json) => | _MessageChange _$MessageChangeFromJson(Map<String, dynamic> json) => | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| import 'package:freezed_annotation/freezed_annotation.dart'; | import 'package:freezed_annotation/freezed_annotation.dart'; | ||||||
| import 'package:island/models/file.dart'; | import 'package:island/models/file.dart'; | ||||||
| import 'package:island/models/user.dart'; | import 'package:island/models/account.dart'; | ||||||
|  |  | ||||||
| part 'custom_app.freezed.dart'; | part 'custom_app.freezed.dart'; | ||||||
| part 'custom_app.g.dart'; | part 'custom_app.g.dart'; | ||||||
|   | |||||||
| @@ -11,8 +11,8 @@ sealed class SnScrappedLink with _$SnScrappedLink { | |||||||
|     required String title, |     required String title, | ||||||
|     required String? description, |     required String? description, | ||||||
|     required String? imageUrl, |     required String? imageUrl, | ||||||
|     required String faviconUrl, |     required String? faviconUrl, | ||||||
|     required String siteName, |     required String? siteName, | ||||||
|     required String? contentType, |     required String? contentType, | ||||||
|     required String? author, |     required String? author, | ||||||
|     required DateTime? publishedDate, |     required DateTime? publishedDate, | ||||||
|   | |||||||
| @@ -15,7 +15,7 @@ T _$identity<T>(T value) => value; | |||||||
| /// @nodoc | /// @nodoc | ||||||
| mixin _$SnScrappedLink { | mixin _$SnScrappedLink { | ||||||
|  |  | ||||||
|  String get type; String get url; String get title; String? get description; String? get imageUrl; String get faviconUrl; String get siteName; String? get contentType; String? get author; DateTime? get publishedDate; |  String get type; String get url; String get title; String? get description; String? get imageUrl; String? get faviconUrl; String? get siteName; String? get contentType; String? get author; DateTime? get publishedDate; | ||||||
| /// Create a copy of SnScrappedLink | /// Create a copy of SnScrappedLink | ||||||
| /// 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) | ||||||
| @@ -48,7 +48,7 @@ abstract mixin class $SnScrappedLinkCopyWith<$Res>  { | |||||||
|   factory $SnScrappedLinkCopyWith(SnScrappedLink value, $Res Function(SnScrappedLink) _then) = _$SnScrappedLinkCopyWithImpl; |   factory $SnScrappedLinkCopyWith(SnScrappedLink value, $Res Function(SnScrappedLink) _then) = _$SnScrappedLinkCopyWithImpl; | ||||||
| @useResult | @useResult | ||||||
| $Res call({ | $Res call({ | ||||||
|  String type, String url, String title, String? description, String? imageUrl, String faviconUrl, String siteName, String? contentType, String? author, DateTime? publishedDate |  String type, String url, String title, String? description, String? imageUrl, String? faviconUrl, String? siteName, String? contentType, String? author, DateTime? publishedDate | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -65,16 +65,16 @@ class _$SnScrappedLinkCopyWithImpl<$Res> | |||||||
|  |  | ||||||
| /// Create a copy of SnScrappedLink | /// Create a copy of SnScrappedLink | ||||||
| /// 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? type = null,Object? url = null,Object? title = null,Object? description = freezed,Object? imageUrl = freezed,Object? faviconUrl = null,Object? siteName = null,Object? contentType = freezed,Object? author = freezed,Object? publishedDate = freezed,}) { | @pragma('vm:prefer-inline') @override $Res call({Object? type = null,Object? url = null,Object? title = null,Object? description = freezed,Object? imageUrl = freezed,Object? faviconUrl = freezed,Object? siteName = freezed,Object? contentType = freezed,Object? author = freezed,Object? publishedDate = freezed,}) { | ||||||
|   return _then(_self.copyWith( |   return _then(_self.copyWith( | ||||||
| type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable | type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable | ||||||
| as String,url: null == url ? _self.url : url // ignore: cast_nullable_to_non_nullable | as String,url: null == url ? _self.url : url // ignore: cast_nullable_to_non_nullable | ||||||
| as String,title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable | as String,title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable | ||||||
| as String,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable | as String,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable | ||||||
| as String?,imageUrl: freezed == imageUrl ? _self.imageUrl : imageUrl // ignore: cast_nullable_to_non_nullable | as String?,imageUrl: freezed == imageUrl ? _self.imageUrl : imageUrl // ignore: cast_nullable_to_non_nullable | ||||||
| as String?,faviconUrl: null == faviconUrl ? _self.faviconUrl : faviconUrl // ignore: cast_nullable_to_non_nullable | as String?,faviconUrl: freezed == faviconUrl ? _self.faviconUrl : faviconUrl // ignore: cast_nullable_to_non_nullable | ||||||
| as String,siteName: null == siteName ? _self.siteName : siteName // ignore: cast_nullable_to_non_nullable | as String?,siteName: freezed == siteName ? _self.siteName : siteName // ignore: cast_nullable_to_non_nullable | ||||||
| as String,contentType: freezed == contentType ? _self.contentType : contentType // ignore: cast_nullable_to_non_nullable | as String?,contentType: freezed == contentType ? _self.contentType : contentType // ignore: cast_nullable_to_non_nullable | ||||||
| as String?,author: freezed == author ? _self.author : author // ignore: cast_nullable_to_non_nullable | as String?,author: freezed == author ? _self.author : author // ignore: cast_nullable_to_non_nullable | ||||||
| as String?,publishedDate: freezed == publishedDate ? _self.publishedDate : publishedDate // ignore: cast_nullable_to_non_nullable | as String?,publishedDate: freezed == publishedDate ? _self.publishedDate : publishedDate // ignore: cast_nullable_to_non_nullable | ||||||
| as DateTime?, | as DateTime?, | ||||||
| @@ -159,7 +159,7 @@ return $default(_that);case _: | |||||||
| /// } | /// } | ||||||
| /// ``` | /// ``` | ||||||
|  |  | ||||||
| @optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String type,  String url,  String title,  String? description,  String? imageUrl,  String faviconUrl,  String siteName,  String? contentType,  String? author,  DateTime? publishedDate)?  $default,{required TResult orElse(),}) {final _that = this; | @optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String type,  String url,  String title,  String? description,  String? imageUrl,  String? faviconUrl,  String? siteName,  String? contentType,  String? author,  DateTime? publishedDate)?  $default,{required TResult orElse(),}) {final _that = this; | ||||||
| switch (_that) { | switch (_that) { | ||||||
| case _SnScrappedLink() when $default != null: | case _SnScrappedLink() when $default != null: | ||||||
| return $default(_that.type,_that.url,_that.title,_that.description,_that.imageUrl,_that.faviconUrl,_that.siteName,_that.contentType,_that.author,_that.publishedDate);case _: | return $default(_that.type,_that.url,_that.title,_that.description,_that.imageUrl,_that.faviconUrl,_that.siteName,_that.contentType,_that.author,_that.publishedDate);case _: | ||||||
| @@ -180,7 +180,7 @@ return $default(_that.type,_that.url,_that.title,_that.description,_that.imageUr | |||||||
| /// } | /// } | ||||||
| /// ``` | /// ``` | ||||||
|  |  | ||||||
| @optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String type,  String url,  String title,  String? description,  String? imageUrl,  String faviconUrl,  String siteName,  String? contentType,  String? author,  DateTime? publishedDate)  $default,) {final _that = this; | @optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String type,  String url,  String title,  String? description,  String? imageUrl,  String? faviconUrl,  String? siteName,  String? contentType,  String? author,  DateTime? publishedDate)  $default,) {final _that = this; | ||||||
| switch (_that) { | switch (_that) { | ||||||
| case _SnScrappedLink(): | case _SnScrappedLink(): | ||||||
| return $default(_that.type,_that.url,_that.title,_that.description,_that.imageUrl,_that.faviconUrl,_that.siteName,_that.contentType,_that.author,_that.publishedDate);} | return $default(_that.type,_that.url,_that.title,_that.description,_that.imageUrl,_that.faviconUrl,_that.siteName,_that.contentType,_that.author,_that.publishedDate);} | ||||||
| @@ -197,7 +197,7 @@ return $default(_that.type,_that.url,_that.title,_that.description,_that.imageUr | |||||||
| /// } | /// } | ||||||
| /// ``` | /// ``` | ||||||
|  |  | ||||||
| @optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String type,  String url,  String title,  String? description,  String? imageUrl,  String faviconUrl,  String siteName,  String? contentType,  String? author,  DateTime? publishedDate)?  $default,) {final _that = this; | @optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String type,  String url,  String title,  String? description,  String? imageUrl,  String? faviconUrl,  String? siteName,  String? contentType,  String? author,  DateTime? publishedDate)?  $default,) {final _that = this; | ||||||
| switch (_that) { | switch (_that) { | ||||||
| case _SnScrappedLink() when $default != null: | case _SnScrappedLink() when $default != null: | ||||||
| return $default(_that.type,_that.url,_that.title,_that.description,_that.imageUrl,_that.faviconUrl,_that.siteName,_that.contentType,_that.author,_that.publishedDate);case _: | return $default(_that.type,_that.url,_that.title,_that.description,_that.imageUrl,_that.faviconUrl,_that.siteName,_that.contentType,_that.author,_that.publishedDate);case _: | ||||||
| @@ -220,8 +220,8 @@ class _SnScrappedLink implements SnScrappedLink { | |||||||
| @override final  String title; | @override final  String title; | ||||||
| @override final  String? description; | @override final  String? description; | ||||||
| @override final  String? imageUrl; | @override final  String? imageUrl; | ||||||
| @override final  String faviconUrl; | @override final  String? faviconUrl; | ||||||
| @override final  String siteName; | @override final  String? siteName; | ||||||
| @override final  String? contentType; | @override final  String? contentType; | ||||||
| @override final  String? author; | @override final  String? author; | ||||||
| @override final  DateTime? publishedDate; | @override final  DateTime? publishedDate; | ||||||
| @@ -259,7 +259,7 @@ abstract mixin class _$SnScrappedLinkCopyWith<$Res> implements $SnScrappedLinkCo | |||||||
|   factory _$SnScrappedLinkCopyWith(_SnScrappedLink value, $Res Function(_SnScrappedLink) _then) = __$SnScrappedLinkCopyWithImpl; |   factory _$SnScrappedLinkCopyWith(_SnScrappedLink value, $Res Function(_SnScrappedLink) _then) = __$SnScrappedLinkCopyWithImpl; | ||||||
| @override @useResult | @override @useResult | ||||||
| $Res call({ | $Res call({ | ||||||
|  String type, String url, String title, String? description, String? imageUrl, String faviconUrl, String siteName, String? contentType, String? author, DateTime? publishedDate |  String type, String url, String title, String? description, String? imageUrl, String? faviconUrl, String? siteName, String? contentType, String? author, DateTime? publishedDate | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -276,16 +276,16 @@ class __$SnScrappedLinkCopyWithImpl<$Res> | |||||||
|  |  | ||||||
| /// Create a copy of SnScrappedLink | /// Create a copy of SnScrappedLink | ||||||
| /// 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? type = null,Object? url = null,Object? title = null,Object? description = freezed,Object? imageUrl = freezed,Object? faviconUrl = null,Object? siteName = null,Object? contentType = freezed,Object? author = freezed,Object? publishedDate = freezed,}) { | @override @pragma('vm:prefer-inline') $Res call({Object? type = null,Object? url = null,Object? title = null,Object? description = freezed,Object? imageUrl = freezed,Object? faviconUrl = freezed,Object? siteName = freezed,Object? contentType = freezed,Object? author = freezed,Object? publishedDate = freezed,}) { | ||||||
|   return _then(_SnScrappedLink( |   return _then(_SnScrappedLink( | ||||||
| type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable | type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable | ||||||
| as String,url: null == url ? _self.url : url // ignore: cast_nullable_to_non_nullable | as String,url: null == url ? _self.url : url // ignore: cast_nullable_to_non_nullable | ||||||
| as String,title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable | as String,title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable | ||||||
| as String,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable | as String,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable | ||||||
| as String?,imageUrl: freezed == imageUrl ? _self.imageUrl : imageUrl // ignore: cast_nullable_to_non_nullable | as String?,imageUrl: freezed == imageUrl ? _self.imageUrl : imageUrl // ignore: cast_nullable_to_non_nullable | ||||||
| as String?,faviconUrl: null == faviconUrl ? _self.faviconUrl : faviconUrl // ignore: cast_nullable_to_non_nullable | as String?,faviconUrl: freezed == faviconUrl ? _self.faviconUrl : faviconUrl // ignore: cast_nullable_to_non_nullable | ||||||
| as String,siteName: null == siteName ? _self.siteName : siteName // ignore: cast_nullable_to_non_nullable | as String?,siteName: freezed == siteName ? _self.siteName : siteName // ignore: cast_nullable_to_non_nullable | ||||||
| as String,contentType: freezed == contentType ? _self.contentType : contentType // ignore: cast_nullable_to_non_nullable | as String?,contentType: freezed == contentType ? _self.contentType : contentType // ignore: cast_nullable_to_non_nullable | ||||||
| as String?,author: freezed == author ? _self.author : author // ignore: cast_nullable_to_non_nullable | as String?,author: freezed == author ? _self.author : author // ignore: cast_nullable_to_non_nullable | ||||||
| as String?,publishedDate: freezed == publishedDate ? _self.publishedDate : publishedDate // ignore: cast_nullable_to_non_nullable | as String?,publishedDate: freezed == publishedDate ? _self.publishedDate : publishedDate // ignore: cast_nullable_to_non_nullable | ||||||
| as DateTime?, | as DateTime?, | ||||||
|   | |||||||
| @@ -13,8 +13,8 @@ _SnScrappedLink _$SnScrappedLinkFromJson(Map<String, dynamic> json) => | |||||||
|       title: json['title'] as String, |       title: json['title'] as String, | ||||||
|       description: json['description'] as String?, |       description: json['description'] as String?, | ||||||
|       imageUrl: json['image_url'] as String?, |       imageUrl: json['image_url'] as String?, | ||||||
|       faviconUrl: json['favicon_url'] as String, |       faviconUrl: json['favicon_url'] as String?, | ||||||
|       siteName: json['site_name'] as String, |       siteName: json['site_name'] as String?, | ||||||
|       contentType: json['content_type'] as String?, |       contentType: json['content_type'] as String?, | ||||||
|       author: json['author'] as String?, |       author: json['author'] as String?, | ||||||
|       publishedDate: |       publishedDate: | ||||||
|   | |||||||
| @@ -8,7 +8,7 @@ part 'poll.g.dart'; | |||||||
| sealed class SnPollWithStats with _$SnPollWithStats { | sealed class SnPollWithStats with _$SnPollWithStats { | ||||||
|   const factory SnPollWithStats({ |   const factory SnPollWithStats({ | ||||||
|     required Map<String, dynamic>? userAnswer, |     required Map<String, dynamic>? userAnswer, | ||||||
|     required Map<String, dynamic> stats, |     @Default({}) Map<String, dynamic> stats, | ||||||
|     required String id, |     required String id, | ||||||
|     required List<SnPollQuestion> questions, |     required List<SnPollQuestion> questions, | ||||||
|     String? title, |     String? title, | ||||||
|   | |||||||
| @@ -213,7 +213,7 @@ return $default(_that.userAnswer,_that.stats,_that.id,_that.questions,_that.titl | |||||||
| @JsonSerializable() | @JsonSerializable() | ||||||
|  |  | ||||||
| class _SnPollWithStats implements SnPollWithStats { | class _SnPollWithStats implements SnPollWithStats { | ||||||
|   const _SnPollWithStats({required final  Map<String, dynamic>? userAnswer, required final  Map<String, dynamic> stats, required this.id, required final  List<SnPollQuestion> questions, this.title, this.description, this.endedAt, required this.publisherId, required this.createdAt, required this.updatedAt, this.deletedAt}): _userAnswer = userAnswer,_stats = stats,_questions = questions; |   const _SnPollWithStats({required final  Map<String, dynamic>? userAnswer, final  Map<String, dynamic> stats = const {}, required this.id, required final  List<SnPollQuestion> questions, this.title, this.description, this.endedAt, required this.publisherId, required this.createdAt, required this.updatedAt, this.deletedAt}): _userAnswer = userAnswer,_stats = stats,_questions = questions; | ||||||
|   factory _SnPollWithStats.fromJson(Map<String, dynamic> json) => _$SnPollWithStatsFromJson(json); |   factory _SnPollWithStats.fromJson(Map<String, dynamic> json) => _$SnPollWithStatsFromJson(json); | ||||||
|  |  | ||||||
|  final  Map<String, dynamic>? _userAnswer; |  final  Map<String, dynamic>? _userAnswer; | ||||||
| @@ -226,7 +226,7 @@ class _SnPollWithStats implements SnPollWithStats { | |||||||
| } | } | ||||||
|  |  | ||||||
|  final  Map<String, dynamic> _stats; |  final  Map<String, dynamic> _stats; | ||||||
| @override Map<String, dynamic> get stats { | @override@JsonKey() Map<String, dynamic> get stats { | ||||||
|   if (_stats is EqualUnmodifiableMapView) return _stats; |   if (_stats is EqualUnmodifiableMapView) return _stats; | ||||||
|   // ignore: implicit_dynamic_type |   // ignore: implicit_dynamic_type | ||||||
|   return EqualUnmodifiableMapView(_stats); |   return EqualUnmodifiableMapView(_stats); | ||||||
|   | |||||||
| @@ -9,7 +9,7 @@ part of 'poll.dart'; | |||||||
| _SnPollWithStats _$SnPollWithStatsFromJson(Map<String, dynamic> json) => | _SnPollWithStats _$SnPollWithStatsFromJson(Map<String, dynamic> json) => | ||||||
|     _SnPollWithStats( |     _SnPollWithStats( | ||||||
|       userAnswer: json['user_answer'] as Map<String, dynamic>?, |       userAnswer: json['user_answer'] as Map<String, dynamic>?, | ||||||
|       stats: json['stats'] as Map<String, dynamic>, |       stats: json['stats'] as Map<String, dynamic>? ?? const {}, | ||||||
|       id: json['id'] as String, |       id: json['id'] as String, | ||||||
|       questions: |       questions: | ||||||
|           (json['questions'] as List<dynamic>) |           (json['questions'] as List<dynamic>) | ||||||
|   | |||||||
| @@ -3,6 +3,7 @@ import 'package:island/models/file.dart'; | |||||||
| import 'package:island/models/post_category.dart'; | import 'package:island/models/post_category.dart'; | ||||||
| import 'package:island/models/post_tag.dart'; | import 'package:island/models/post_tag.dart'; | ||||||
| import 'package:island/models/publisher.dart'; | import 'package:island/models/publisher.dart'; | ||||||
|  | import 'package:island/models/realm.dart'; | ||||||
|  |  | ||||||
| part 'post.freezed.dart'; | part 'post.freezed.dart'; | ||||||
| part 'post.g.dart'; | part 'post.g.dart'; | ||||||
| @@ -18,6 +19,7 @@ sealed class SnPost with _$SnPost { | |||||||
|     @Default(null) DateTime? publishedAt, |     @Default(null) DateTime? publishedAt, | ||||||
|     @Default(0) int visibility, |     @Default(0) int visibility, | ||||||
|     String? content, |     String? content, | ||||||
|  |     String? slug, | ||||||
|     @Default(0) int type, |     @Default(0) int type, | ||||||
|     Map<String, dynamic>? meta, |     Map<String, dynamic>? meta, | ||||||
|     @Default(0) int viewsUnique, |     @Default(0) int viewsUnique, | ||||||
| @@ -31,6 +33,8 @@ sealed class SnPost with _$SnPost { | |||||||
|     SnPost? repliedPost, |     SnPost? repliedPost, | ||||||
|     String? forwardedPostId, |     String? forwardedPostId, | ||||||
|     SnPost? forwardedPost, |     SnPost? forwardedPost, | ||||||
|  |     String? realmId, | ||||||
|  |     SnRealm? realm, | ||||||
|     @Default([]) List<SnCloudFile> attachments, |     @Default([]) List<SnCloudFile> attachments, | ||||||
|     required SnPublisher publisher, |     required SnPublisher publisher, | ||||||
|     @Default({}) Map<String, int> reactionsCount, |     @Default({}) Map<String, int> reactionsCount, | ||||||
|   | |||||||
| @@ -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; int get type; Map<String, dynamic>? get meta; int get viewsUnique; int get viewsTotal; int get upvotes; int get downvotes; int get repliesCount; String? get threadedPostId; SnPost? get threadedPost; String? get repliedPostId; SnPost? get repliedPost; String? get forwardedPostId; SnPost? get forwardedPost; 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; int get viewsUnique; int get viewsTotal; int get upvotes; int get downvotes; int get repliesCount; 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.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.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)&&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.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.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,type,const DeepCollectionEquality().hash(meta),viewsUnique,viewsTotal,upvotes,downvotes,repliesCount,threadedPostId,threadedPost,repliedPostId,repliedPost,forwardedPostId,forwardedPost,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),viewsUnique,viewsTotal,upvotes,downvotes,repliesCount,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, type: $type, meta: $meta, viewsUnique: $viewsUnique, viewsTotal: $viewsTotal, upvotes: $upvotes, downvotes: $downvotes, repliesCount: $repliesCount, threadedPostId: $threadedPostId, threadedPost: $threadedPost, repliedPostId: $repliedPostId, repliedPost: $repliedPost, forwardedPostId: $forwardedPostId, forwardedPost: $forwardedPost, 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, viewsUnique: $viewsUnique, viewsTotal: $viewsTotal, upvotes: $upvotes, downvotes: $downvotes, repliesCount: $repliesCount, 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, int type, Map<String, dynamic>? meta, int viewsUnique, int viewsTotal, int upvotes, int downvotes, int repliesCount, String? threadedPostId, SnPost? threadedPost, String? repliedPostId, SnPost? repliedPost, String? forwardedPostId, SnPost? forwardedPost, 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, int viewsUnique, int viewsTotal, int upvotes, int downvotes, int repliesCount, 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;$SnPublisherCopyWith<$Res> get publisher; | $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? type = null,Object? meta = freezed,Object? viewsUnique = null,Object? viewsTotal = null,Object? upvotes = null,Object? downvotes = null,Object? repliesCount = null,Object? threadedPostId = freezed,Object? threadedPost = freezed,Object? repliedPostId = freezed,Object? repliedPost = freezed,Object? forwardedPostId = freezed,Object? forwardedPost = 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? viewsUnique = null,Object? viewsTotal = null,Object? upvotes = null,Object? downvotes = null,Object? repliesCount = null,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 | ||||||
| @@ -75,6 +75,7 @@ as String?,editedAt: freezed == editedAt ? _self.editedAt : editedAt // ignore: | |||||||
| as DateTime?,publishedAt: freezed == publishedAt ? _self.publishedAt : publishedAt // ignore: cast_nullable_to_non_nullable | as DateTime?,publishedAt: freezed == publishedAt ? _self.publishedAt : publishedAt // ignore: cast_nullable_to_non_nullable | ||||||
| as DateTime?,visibility: null == visibility ? _self.visibility : visibility // ignore: cast_nullable_to_non_nullable | as DateTime?,visibility: null == visibility ? _self.visibility : visibility // ignore: cast_nullable_to_non_nullable | ||||||
| as int,content: freezed == content ? _self.content : content // ignore: cast_nullable_to_non_nullable | as int,content: freezed == content ? _self.content : content // 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>?,viewsUnique: null == viewsUnique ? _self.viewsUnique : viewsUnique // ignore: cast_nullable_to_non_nullable | ||||||
| @@ -88,7 +89,9 @@ as SnPost?,repliedPostId: freezed == repliedPostId ? _self.repliedPostId : repli | |||||||
| as String?,repliedPost: freezed == repliedPost ? _self.repliedPost : repliedPost // ignore: cast_nullable_to_non_nullable | as String?,repliedPost: freezed == repliedPost ? _self.repliedPost : repliedPost // ignore: cast_nullable_to_non_nullable | ||||||
| as SnPost?,forwardedPostId: freezed == forwardedPostId ? _self.forwardedPostId : forwardedPostId // ignore: cast_nullable_to_non_nullable | as SnPost?,forwardedPostId: freezed == forwardedPostId ? _self.forwardedPostId : forwardedPostId // ignore: cast_nullable_to_non_nullable | ||||||
| as String?,forwardedPost: freezed == forwardedPost ? _self.forwardedPost : forwardedPost // ignore: cast_nullable_to_non_nullable | as String?,forwardedPost: freezed == forwardedPost ? _self.forwardedPost : forwardedPost // ignore: cast_nullable_to_non_nullable | ||||||
| as SnPost?,attachments: null == attachments ? _self.attachments : attachments // ignore: cast_nullable_to_non_nullable | as SnPost?,realmId: freezed == realmId ? _self.realmId : realmId // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String?,realm: freezed == realm ? _self.realm : realm // ignore: cast_nullable_to_non_nullable | ||||||
|  | as SnRealm?,attachments: null == attachments ? _self.attachments : attachments // ignore: cast_nullable_to_non_nullable | ||||||
| as List<SnCloudFile>,publisher: null == publisher ? _self.publisher : publisher // ignore: cast_nullable_to_non_nullable | as List<SnCloudFile>,publisher: null == publisher ? _self.publisher : publisher // ignore: cast_nullable_to_non_nullable | ||||||
| as SnPublisher,reactionsCount: null == reactionsCount ? _self.reactionsCount : reactionsCount // ignore: cast_nullable_to_non_nullable | as SnPublisher,reactionsCount: null == reactionsCount ? _self.reactionsCount : reactionsCount // ignore: cast_nullable_to_non_nullable | ||||||
| as Map<String, int>,reactionsMade: null == reactionsMade ? _self.reactionsMade : reactionsMade // ignore: cast_nullable_to_non_nullable | as Map<String, int>,reactionsMade: null == reactionsMade ? _self.reactionsMade : reactionsMade // ignore: cast_nullable_to_non_nullable | ||||||
| @@ -143,6 +146,18 @@ $SnPostCopyWith<$Res>? get forwardedPost { | |||||||
| /// 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') | ||||||
|  | $SnRealmCopyWith<$Res>? get realm { | ||||||
|  |     if (_self.realm == null) { | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return $SnRealmCopyWith<$Res>(_self.realm!, (value) { | ||||||
|  |     return _then(_self.copyWith(realm: value)); | ||||||
|  |   }); | ||||||
|  | }/// Create a copy of SnPost | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
| $SnPublisherCopyWith<$Res> get publisher { | $SnPublisherCopyWith<$Res> get publisher { | ||||||
|    |    | ||||||
|   return $SnPublisherCopyWith<$Res>(_self.publisher, (value) { |   return $SnPublisherCopyWith<$Res>(_self.publisher, (value) { | ||||||
| @@ -227,10 +242,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,  int type,  Map<String, dynamic>? meta,  int viewsUnique,  int viewsTotal,  int upvotes,  int downvotes,  int repliesCount,  String? threadedPostId,  SnPost? threadedPost,  String? repliedPostId,  SnPost? repliedPost,  String? forwardedPostId,  SnPost? forwardedPost,  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,  int viewsUnique,  int viewsTotal,  int upvotes,  int downvotes,  int repliesCount,  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.type,_that.meta,_that.viewsUnique,_that.viewsTotal,_that.upvotes,_that.downvotes,_that.repliesCount,_that.threadedPostId,_that.threadedPost,_that.repliedPostId,_that.repliedPost,_that.forwardedPostId,_that.forwardedPost,_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.viewsUnique,_that.viewsTotal,_that.upvotes,_that.downvotes,_that.repliesCount,_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(); | ||||||
|  |  | ||||||
| } | } | ||||||
| @@ -248,10 +263,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,  int type,  Map<String, dynamic>? meta,  int viewsUnique,  int viewsTotal,  int upvotes,  int downvotes,  int repliesCount,  String? threadedPostId,  SnPost? threadedPost,  String? repliedPostId,  SnPost? repliedPost,  String? forwardedPostId,  SnPost? forwardedPost,  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,  int viewsUnique,  int viewsTotal,  int upvotes,  int downvotes,  int repliesCount,  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.type,_that.meta,_that.viewsUnique,_that.viewsTotal,_that.upvotes,_that.downvotes,_that.repliesCount,_that.threadedPostId,_that.threadedPost,_that.repliedPostId,_that.repliedPost,_that.forwardedPostId,_that.forwardedPost,_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.viewsUnique,_that.viewsTotal,_that.upvotes,_that.downvotes,_that.repliesCount,_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` | ||||||
| /// | /// | ||||||
| @@ -265,10 +280,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,  int type,  Map<String, dynamic>? meta,  int viewsUnique,  int viewsTotal,  int upvotes,  int downvotes,  int repliesCount,  String? threadedPostId,  SnPost? threadedPost,  String? repliedPostId,  SnPost? repliedPost,  String? forwardedPostId,  SnPost? forwardedPost,  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,  int viewsUnique,  int viewsTotal,  int upvotes,  int downvotes,  int repliesCount,  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.type,_that.meta,_that.viewsUnique,_that.viewsTotal,_that.upvotes,_that.downvotes,_that.repliesCount,_that.threadedPostId,_that.threadedPost,_that.repliedPostId,_that.repliedPost,_that.forwardedPostId,_that.forwardedPost,_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.viewsUnique,_that.viewsTotal,_that.upvotes,_that.downvotes,_that.repliesCount,_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; | ||||||
|  |  | ||||||
| } | } | ||||||
| @@ -280,7 +295,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.type = 0, final  Map<String, dynamic>? meta, this.viewsUnique = 0, this.viewsTotal = 0, this.upvotes = 0, this.downvotes = 0, this.repliesCount = 0, this.threadedPostId, this.threadedPost, this.repliedPostId, this.repliedPost, this.forwardedPostId, this.forwardedPost, 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.viewsUnique = 0, this.viewsTotal = 0, this.upvotes = 0, this.downvotes = 0, this.repliesCount = 0, 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; | ||||||
| @@ -291,6 +306,7 @@ class _SnPost implements SnPost { | |||||||
| @override@JsonKey() final  DateTime? publishedAt; | @override@JsonKey() final  DateTime? publishedAt; | ||||||
| @override@JsonKey() final  int visibility; | @override@JsonKey() final  int visibility; | ||||||
| @override final  String? content; | @override final  String? content; | ||||||
|  | @override final  String? slug; | ||||||
| @override@JsonKey() final  int type; | @override@JsonKey() final  int type; | ||||||
|  final  Map<String, dynamic>? _meta; |  final  Map<String, dynamic>? _meta; | ||||||
| @override Map<String, dynamic>? get meta { | @override Map<String, dynamic>? get meta { | ||||||
| @@ -312,6 +328,8 @@ class _SnPost implements SnPost { | |||||||
| @override final  SnPost? repliedPost; | @override final  SnPost? repliedPost; | ||||||
| @override final  String? forwardedPostId; | @override final  String? forwardedPostId; | ||||||
| @override final  SnPost? forwardedPost; | @override final  SnPost? forwardedPost; | ||||||
|  | @override final  String? realmId; | ||||||
|  | @override final  SnRealm? realm; | ||||||
|  final  List<SnCloudFile> _attachments; |  final  List<SnCloudFile> _attachments; | ||||||
| @override@JsonKey() List<SnCloudFile> get attachments { | @override@JsonKey() List<SnCloudFile> get attachments { | ||||||
|   if (_attachments is EqualUnmodifiableListView) return _attachments; |   if (_attachments is EqualUnmodifiableListView) return _attachments; | ||||||
| @@ -380,16 +398,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.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.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)&&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.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.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,type,const DeepCollectionEquality().hash(_meta),viewsUnique,viewsTotal,upvotes,downvotes,repliesCount,threadedPostId,threadedPost,repliedPostId,repliedPost,forwardedPostId,forwardedPost,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),viewsUnique,viewsTotal,upvotes,downvotes,repliesCount,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, type: $type, meta: $meta, viewsUnique: $viewsUnique, viewsTotal: $viewsTotal, upvotes: $upvotes, downvotes: $downvotes, repliesCount: $repliesCount, threadedPostId: $threadedPostId, threadedPost: $threadedPost, repliedPostId: $repliedPostId, repliedPost: $repliedPost, forwardedPostId: $forwardedPostId, forwardedPost: $forwardedPost, 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, viewsUnique: $viewsUnique, viewsTotal: $viewsTotal, upvotes: $upvotes, downvotes: $downvotes, repliesCount: $repliesCount, 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)'; | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -400,11 +418,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, int type, Map<String, dynamic>? meta, int viewsUnique, int viewsTotal, int upvotes, int downvotes, int repliesCount, String? threadedPostId, SnPost? threadedPost, String? repliedPostId, SnPost? repliedPost, String? forwardedPostId, SnPost? forwardedPost, 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, int viewsUnique, int viewsTotal, int upvotes, int downvotes, int repliesCount, 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 $SnPublisherCopyWith<$Res> get publisher; | @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 | ||||||
| @@ -417,7 +435,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? type = null,Object? meta = freezed,Object? viewsUnique = null,Object? viewsTotal = null,Object? upvotes = null,Object? downvotes = null,Object? repliesCount = null,Object? threadedPostId = freezed,Object? threadedPost = freezed,Object? repliedPostId = freezed,Object? repliedPost = freezed,Object? forwardedPostId = freezed,Object? forwardedPost = 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? viewsUnique = null,Object? viewsTotal = null,Object? upvotes = null,Object? downvotes = null,Object? repliesCount = null,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 | ||||||
| @@ -427,6 +445,7 @@ as String?,editedAt: freezed == editedAt ? _self.editedAt : editedAt // ignore: | |||||||
| as DateTime?,publishedAt: freezed == publishedAt ? _self.publishedAt : publishedAt // ignore: cast_nullable_to_non_nullable | as DateTime?,publishedAt: freezed == publishedAt ? _self.publishedAt : publishedAt // ignore: cast_nullable_to_non_nullable | ||||||
| as DateTime?,visibility: null == visibility ? _self.visibility : visibility // ignore: cast_nullable_to_non_nullable | as DateTime?,visibility: null == visibility ? _self.visibility : visibility // ignore: cast_nullable_to_non_nullable | ||||||
| as int,content: freezed == content ? _self.content : content // ignore: cast_nullable_to_non_nullable | as int,content: freezed == content ? _self.content : content // 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>?,viewsUnique: null == viewsUnique ? _self.viewsUnique : viewsUnique // ignore: cast_nullable_to_non_nullable | ||||||
| @@ -440,7 +459,9 @@ as SnPost?,repliedPostId: freezed == repliedPostId ? _self.repliedPostId : repli | |||||||
| as String?,repliedPost: freezed == repliedPost ? _self.repliedPost : repliedPost // ignore: cast_nullable_to_non_nullable | as String?,repliedPost: freezed == repliedPost ? _self.repliedPost : repliedPost // ignore: cast_nullable_to_non_nullable | ||||||
| as SnPost?,forwardedPostId: freezed == forwardedPostId ? _self.forwardedPostId : forwardedPostId // ignore: cast_nullable_to_non_nullable | as SnPost?,forwardedPostId: freezed == forwardedPostId ? _self.forwardedPostId : forwardedPostId // ignore: cast_nullable_to_non_nullable | ||||||
| as String?,forwardedPost: freezed == forwardedPost ? _self.forwardedPost : forwardedPost // ignore: cast_nullable_to_non_nullable | as String?,forwardedPost: freezed == forwardedPost ? _self.forwardedPost : forwardedPost // ignore: cast_nullable_to_non_nullable | ||||||
| as SnPost?,attachments: null == attachments ? _self._attachments : attachments // ignore: cast_nullable_to_non_nullable | as SnPost?,realmId: freezed == realmId ? _self.realmId : realmId // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String?,realm: freezed == realm ? _self.realm : realm // ignore: cast_nullable_to_non_nullable | ||||||
|  | as SnRealm?,attachments: null == attachments ? _self._attachments : attachments // ignore: cast_nullable_to_non_nullable | ||||||
| as List<SnCloudFile>,publisher: null == publisher ? _self.publisher : publisher // ignore: cast_nullable_to_non_nullable | as List<SnCloudFile>,publisher: null == publisher ? _self.publisher : publisher // ignore: cast_nullable_to_non_nullable | ||||||
| as SnPublisher,reactionsCount: null == reactionsCount ? _self._reactionsCount : reactionsCount // ignore: cast_nullable_to_non_nullable | as SnPublisher,reactionsCount: null == reactionsCount ? _self._reactionsCount : reactionsCount // ignore: cast_nullable_to_non_nullable | ||||||
| as Map<String, int>,reactionsMade: null == reactionsMade ? _self._reactionsMade : reactionsMade // ignore: cast_nullable_to_non_nullable | as Map<String, int>,reactionsMade: null == reactionsMade ? _self._reactionsMade : reactionsMade // ignore: cast_nullable_to_non_nullable | ||||||
| @@ -496,6 +517,18 @@ $SnPostCopyWith<$Res>? get forwardedPost { | |||||||
| /// 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') | ||||||
|  | $SnRealmCopyWith<$Res>? get realm { | ||||||
|  |     if (_self.realm == null) { | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return $SnRealmCopyWith<$Res>(_self.realm!, (value) { | ||||||
|  |     return _then(_self.copyWith(realm: value)); | ||||||
|  |   }); | ||||||
|  | }/// Create a copy of SnPost | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
| $SnPublisherCopyWith<$Res> get publisher { | $SnPublisherCopyWith<$Res> get publisher { | ||||||
|    |    | ||||||
|   return $SnPublisherCopyWith<$Res>(_self.publisher, (value) { |   return $SnPublisherCopyWith<$Res>(_self.publisher, (value) { | ||||||
|   | |||||||
| @@ -21,6 +21,7 @@ _SnPost _$SnPostFromJson(Map<String, dynamic> json) => _SnPost( | |||||||
|           : DateTime.parse(json['published_at'] as String), |           : DateTime.parse(json['published_at'] as String), | ||||||
|   visibility: (json['visibility'] as num?)?.toInt() ?? 0, |   visibility: (json['visibility'] as num?)?.toInt() ?? 0, | ||||||
|   content: json['content'] as String?, |   content: json['content'] 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>?, | ||||||
|   viewsUnique: (json['views_unique'] as num?)?.toInt() ?? 0, |   viewsUnique: (json['views_unique'] as num?)?.toInt() ?? 0, | ||||||
| @@ -43,6 +44,11 @@ _SnPost _$SnPostFromJson(Map<String, dynamic> json) => _SnPost( | |||||||
|       json['forwarded_post'] == null |       json['forwarded_post'] == null | ||||||
|           ? null |           ? null | ||||||
|           : SnPost.fromJson(json['forwarded_post'] as Map<String, dynamic>), |           : SnPost.fromJson(json['forwarded_post'] as Map<String, dynamic>), | ||||||
|  |   realmId: json['realm_id'] as String?, | ||||||
|  |   realm: | ||||||
|  |       json['realm'] == null | ||||||
|  |           ? null | ||||||
|  |           : SnRealm.fromJson(json['realm'] as Map<String, dynamic>), | ||||||
|   attachments: |   attachments: | ||||||
|       (json['attachments'] as List<dynamic>?) |       (json['attachments'] as List<dynamic>?) | ||||||
|           ?.map((e) => SnCloudFile.fromJson(e as Map<String, dynamic>)) |           ?.map((e) => SnCloudFile.fromJson(e as Map<String, dynamic>)) | ||||||
| @@ -95,6 +101,7 @@ Map<String, dynamic> _$SnPostToJson(_SnPost instance) => <String, dynamic>{ | |||||||
|   'published_at': instance.publishedAt?.toIso8601String(), |   'published_at': instance.publishedAt?.toIso8601String(), | ||||||
|   'visibility': instance.visibility, |   'visibility': instance.visibility, | ||||||
|   'content': instance.content, |   'content': instance.content, | ||||||
|  |   'slug': instance.slug, | ||||||
|   'type': instance.type, |   'type': instance.type, | ||||||
|   'meta': instance.meta, |   'meta': instance.meta, | ||||||
|   'views_unique': instance.viewsUnique, |   'views_unique': instance.viewsUnique, | ||||||
| @@ -108,6 +115,8 @@ Map<String, dynamic> _$SnPostToJson(_SnPost instance) => <String, dynamic>{ | |||||||
|   'replied_post': instance.repliedPost?.toJson(), |   'replied_post': instance.repliedPost?.toJson(), | ||||||
|   'forwarded_post_id': instance.forwardedPostId, |   'forwarded_post_id': instance.forwardedPostId, | ||||||
|   'forwarded_post': instance.forwardedPost?.toJson(), |   'forwarded_post': instance.forwardedPost?.toJson(), | ||||||
|  |   'realm_id': instance.realmId, | ||||||
|  |   'realm': instance.realm?.toJson(), | ||||||
|   'attachments': instance.attachments.map((e) => e.toJson()).toList(), |   'attachments': instance.attachments.map((e) => e.toJson()).toList(), | ||||||
|   'publisher': instance.publisher.toJson(), |   'publisher': instance.publisher.toJson(), | ||||||
|   'reactions_count': instance.reactionsCount, |   'reactions_count': instance.reactionsCount, | ||||||
|   | |||||||
| @@ -15,6 +15,7 @@ sealed class SnPostCategory with _$SnPostCategory { | |||||||
|     required String slug, |     required String slug, | ||||||
|     String? name, |     String? name, | ||||||
|     @Default([]) List<SnPost> posts, |     @Default([]) List<SnPost> posts, | ||||||
|  |     @Default(0) int usage, | ||||||
|   }) = _SnPostCategory; |   }) = _SnPostCategory; | ||||||
|  |  | ||||||
|   factory SnPostCategory.fromJson(Map<String, dynamic> json) => |   factory SnPostCategory.fromJson(Map<String, dynamic> json) => | ||||||
|   | |||||||
| @@ -15,7 +15,7 @@ T _$identity<T>(T value) => value; | |||||||
| /// @nodoc | /// @nodoc | ||||||
| mixin _$SnPostCategory { | mixin _$SnPostCategory { | ||||||
|  |  | ||||||
|  String get id; String get slug; String? get name; List<SnPost> get posts; |  String get id; String get slug; String? get name; List<SnPost> get posts; int get usage; | ||||||
| /// Create a copy of SnPostCategory | /// Create a copy of SnPostCategory | ||||||
| /// 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 @@ $SnPostCategoryCopyWith<SnPostCategory> get copyWith => _$SnPostCategoryCopyWith | |||||||
|  |  | ||||||
| @override | @override | ||||||
| bool operator ==(Object other) { | bool operator ==(Object other) { | ||||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is SnPostCategory&&(identical(other.id, id) || other.id == id)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.name, name) || other.name == name)&&const DeepCollectionEquality().equals(other.posts, posts)); |   return identical(this, other) || (other.runtimeType == runtimeType&&other is SnPostCategory&&(identical(other.id, id) || other.id == id)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.name, name) || other.name == name)&&const DeepCollectionEquality().equals(other.posts, posts)&&(identical(other.usage, usage) || other.usage == usage)); | ||||||
| } | } | ||||||
|  |  | ||||||
| @JsonKey(includeFromJson: false, includeToJson: false) | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
| @override | @override | ||||||
| int get hashCode => Object.hash(runtimeType,id,slug,name,const DeepCollectionEquality().hash(posts)); | int get hashCode => Object.hash(runtimeType,id,slug,name,const DeepCollectionEquality().hash(posts),usage); | ||||||
|  |  | ||||||
| @override | @override | ||||||
| String toString() { | String toString() { | ||||||
|   return 'SnPostCategory(id: $id, slug: $slug, name: $name, posts: $posts)'; |   return 'SnPostCategory(id: $id, slug: $slug, name: $name, posts: $posts, usage: $usage)'; | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -48,7 +48,7 @@ abstract mixin class $SnPostCategoryCopyWith<$Res>  { | |||||||
|   factory $SnPostCategoryCopyWith(SnPostCategory value, $Res Function(SnPostCategory) _then) = _$SnPostCategoryCopyWithImpl; |   factory $SnPostCategoryCopyWith(SnPostCategory value, $Res Function(SnPostCategory) _then) = _$SnPostCategoryCopyWithImpl; | ||||||
| @useResult | @useResult | ||||||
| $Res call({ | $Res call({ | ||||||
|  String id, String slug, String? name, List<SnPost> posts |  String id, String slug, String? name, List<SnPost> posts, int usage | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -65,13 +65,14 @@ class _$SnPostCategoryCopyWithImpl<$Res> | |||||||
|  |  | ||||||
| /// Create a copy of SnPostCategory | /// Create a copy of SnPostCategory | ||||||
| /// 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? slug = null,Object? name = freezed,Object? posts = null,}) { | @pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? slug = null,Object? name = freezed,Object? posts = null,Object? usage = 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,slug: null == slug ? _self.slug : slug // ignore: cast_nullable_to_non_nullable | as String,slug: null == slug ? _self.slug : slug // ignore: cast_nullable_to_non_nullable | ||||||
| as String,name: freezed == name ? _self.name : name // ignore: cast_nullable_to_non_nullable | as String,name: freezed == name ? _self.name : name // ignore: cast_nullable_to_non_nullable | ||||||
| as String?,posts: null == posts ? _self.posts : posts // ignore: cast_nullable_to_non_nullable | as String?,posts: null == posts ? _self.posts : posts // ignore: cast_nullable_to_non_nullable | ||||||
| as List<SnPost>, | as List<SnPost>,usage: null == usage ? _self.usage : usage // ignore: cast_nullable_to_non_nullable | ||||||
|  | as int, | ||||||
|   )); |   )); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -153,10 +154,10 @@ return $default(_that);case _: | |||||||
| /// } | /// } | ||||||
| /// ``` | /// ``` | ||||||
|  |  | ||||||
| @optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id,  String slug,  String? name,  List<SnPost> posts)?  $default,{required TResult orElse(),}) {final _that = this; | @optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id,  String slug,  String? name,  List<SnPost> posts,  int usage)?  $default,{required TResult orElse(),}) {final _that = this; | ||||||
| switch (_that) { | switch (_that) { | ||||||
| case _SnPostCategory() when $default != null: | case _SnPostCategory() when $default != null: | ||||||
| return $default(_that.id,_that.slug,_that.name,_that.posts);case _: | return $default(_that.id,_that.slug,_that.name,_that.posts,_that.usage);case _: | ||||||
|   return orElse(); |   return orElse(); | ||||||
|  |  | ||||||
| } | } | ||||||
| @@ -174,10 +175,10 @@ return $default(_that.id,_that.slug,_that.name,_that.posts);case _: | |||||||
| /// } | /// } | ||||||
| /// ``` | /// ``` | ||||||
|  |  | ||||||
| @optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id,  String slug,  String? name,  List<SnPost> posts)  $default,) {final _that = this; | @optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id,  String slug,  String? name,  List<SnPost> posts,  int usage)  $default,) {final _that = this; | ||||||
| switch (_that) { | switch (_that) { | ||||||
| case _SnPostCategory(): | case _SnPostCategory(): | ||||||
| return $default(_that.id,_that.slug,_that.name,_that.posts);} | return $default(_that.id,_that.slug,_that.name,_that.posts,_that.usage);} | ||||||
| } | } | ||||||
| /// A variant of `when` that fallback to returning `null` | /// A variant of `when` that fallback to returning `null` | ||||||
| /// | /// | ||||||
| @@ -191,10 +192,10 @@ return $default(_that.id,_that.slug,_that.name,_that.posts);} | |||||||
| /// } | /// } | ||||||
| /// ``` | /// ``` | ||||||
|  |  | ||||||
| @optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id,  String slug,  String? name,  List<SnPost> posts)?  $default,) {final _that = this; | @optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id,  String slug,  String? name,  List<SnPost> posts,  int usage)?  $default,) {final _that = this; | ||||||
| switch (_that) { | switch (_that) { | ||||||
| case _SnPostCategory() when $default != null: | case _SnPostCategory() when $default != null: | ||||||
| return $default(_that.id,_that.slug,_that.name,_that.posts);case _: | return $default(_that.id,_that.slug,_that.name,_that.posts,_that.usage);case _: | ||||||
|   return null; |   return null; | ||||||
|  |  | ||||||
| } | } | ||||||
| @@ -206,7 +207,7 @@ return $default(_that.id,_that.slug,_that.name,_that.posts);case _: | |||||||
| @JsonSerializable() | @JsonSerializable() | ||||||
|  |  | ||||||
| class _SnPostCategory extends SnPostCategory { | class _SnPostCategory extends SnPostCategory { | ||||||
|   const _SnPostCategory({required this.id, required this.slug, this.name, final  List<SnPost> posts = const []}): _posts = posts,super._(); |   const _SnPostCategory({required this.id, required this.slug, this.name, final  List<SnPost> posts = const [], this.usage = 0}): _posts = posts,super._(); | ||||||
|   factory _SnPostCategory.fromJson(Map<String, dynamic> json) => _$SnPostCategoryFromJson(json); |   factory _SnPostCategory.fromJson(Map<String, dynamic> json) => _$SnPostCategoryFromJson(json); | ||||||
|  |  | ||||||
| @override final  String id; | @override final  String id; | ||||||
| @@ -219,6 +220,7 @@ class _SnPostCategory extends SnPostCategory { | |||||||
|   return EqualUnmodifiableListView(_posts); |   return EqualUnmodifiableListView(_posts); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @override@JsonKey() final  int usage; | ||||||
|  |  | ||||||
| /// Create a copy of SnPostCategory | /// Create a copy of SnPostCategory | ||||||
| /// with the given fields replaced by the non-null parameter values. | /// with the given fields replaced by the non-null parameter values. | ||||||
| @@ -233,16 +235,16 @@ Map<String, dynamic> toJson() { | |||||||
|  |  | ||||||
| @override | @override | ||||||
| bool operator ==(Object other) { | bool operator ==(Object other) { | ||||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnPostCategory&&(identical(other.id, id) || other.id == id)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.name, name) || other.name == name)&&const DeepCollectionEquality().equals(other._posts, _posts)); |   return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnPostCategory&&(identical(other.id, id) || other.id == id)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.name, name) || other.name == name)&&const DeepCollectionEquality().equals(other._posts, _posts)&&(identical(other.usage, usage) || other.usage == usage)); | ||||||
| } | } | ||||||
|  |  | ||||||
| @JsonKey(includeFromJson: false, includeToJson: false) | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
| @override | @override | ||||||
| int get hashCode => Object.hash(runtimeType,id,slug,name,const DeepCollectionEquality().hash(_posts)); | int get hashCode => Object.hash(runtimeType,id,slug,name,const DeepCollectionEquality().hash(_posts),usage); | ||||||
|  |  | ||||||
| @override | @override | ||||||
| String toString() { | String toString() { | ||||||
|   return 'SnPostCategory(id: $id, slug: $slug, name: $name, posts: $posts)'; |   return 'SnPostCategory(id: $id, slug: $slug, name: $name, posts: $posts, usage: $usage)'; | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -253,7 +255,7 @@ abstract mixin class _$SnPostCategoryCopyWith<$Res> implements $SnPostCategoryCo | |||||||
|   factory _$SnPostCategoryCopyWith(_SnPostCategory value, $Res Function(_SnPostCategory) _then) = __$SnPostCategoryCopyWithImpl; |   factory _$SnPostCategoryCopyWith(_SnPostCategory value, $Res Function(_SnPostCategory) _then) = __$SnPostCategoryCopyWithImpl; | ||||||
| @override @useResult | @override @useResult | ||||||
| $Res call({ | $Res call({ | ||||||
|  String id, String slug, String? name, List<SnPost> posts |  String id, String slug, String? name, List<SnPost> posts, int usage | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -270,13 +272,14 @@ class __$SnPostCategoryCopyWithImpl<$Res> | |||||||
|  |  | ||||||
| /// Create a copy of SnPostCategory | /// Create a copy of SnPostCategory | ||||||
| /// 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? slug = null,Object? name = freezed,Object? posts = null,}) { | @override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? slug = null,Object? name = freezed,Object? posts = null,Object? usage = null,}) { | ||||||
|   return _then(_SnPostCategory( |   return _then(_SnPostCategory( | ||||||
| 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,slug: null == slug ? _self.slug : slug // ignore: cast_nullable_to_non_nullable | as String,slug: null == slug ? _self.slug : slug // ignore: cast_nullable_to_non_nullable | ||||||
| as String,name: freezed == name ? _self.name : name // ignore: cast_nullable_to_non_nullable | as String,name: freezed == name ? _self.name : name // ignore: cast_nullable_to_non_nullable | ||||||
| as String?,posts: null == posts ? _self._posts : posts // ignore: cast_nullable_to_non_nullable | as String?,posts: null == posts ? _self._posts : posts // ignore: cast_nullable_to_non_nullable | ||||||
| as List<SnPost>, | as List<SnPost>,usage: null == usage ? _self.usage : usage // ignore: cast_nullable_to_non_nullable | ||||||
|  | as int, | ||||||
|   )); |   )); | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -16,6 +16,7 @@ _SnPostCategory _$SnPostCategoryFromJson(Map<String, dynamic> json) => | |||||||
|               ?.map((e) => SnPost.fromJson(e as Map<String, dynamic>)) |               ?.map((e) => SnPost.fromJson(e as Map<String, dynamic>)) | ||||||
|               .toList() ?? |               .toList() ?? | ||||||
|           const [], |           const [], | ||||||
|  |       usage: (json['usage'] as num?)?.toInt() ?? 0, | ||||||
|     ); |     ); | ||||||
|  |  | ||||||
| Map<String, dynamic> _$SnPostCategoryToJson(_SnPostCategory instance) => | Map<String, dynamic> _$SnPostCategoryToJson(_SnPostCategory instance) => | ||||||
| @@ -24,4 +25,5 @@ Map<String, dynamic> _$SnPostCategoryToJson(_SnPostCategory instance) => | |||||||
|       'slug': instance.slug, |       'slug': instance.slug, | ||||||
|       'name': instance.name, |       'name': instance.name, | ||||||
|       'posts': instance.posts.map((e) => e.toJson()).toList(), |       'posts': instance.posts.map((e) => e.toJson()).toList(), | ||||||
|  |       'usage': instance.usage, | ||||||
|     }; |     }; | ||||||
|   | |||||||
| @@ -11,6 +11,7 @@ sealed class SnPostTag with _$SnPostTag { | |||||||
|     required String slug, |     required String slug, | ||||||
|     String? name, |     String? name, | ||||||
|     @Default([]) List<SnPost> posts, |     @Default([]) List<SnPost> posts, | ||||||
|  |     @Default(0) int usage, | ||||||
|   }) = _SnPostTag; |   }) = _SnPostTag; | ||||||
|  |  | ||||||
|   factory SnPostTag.fromJson(Map<String, dynamic> json) => |   factory SnPostTag.fromJson(Map<String, dynamic> json) => | ||||||
|   | |||||||
| @@ -15,7 +15,7 @@ T _$identity<T>(T value) => value; | |||||||
| /// @nodoc | /// @nodoc | ||||||
| mixin _$SnPostTag { | mixin _$SnPostTag { | ||||||
|  |  | ||||||
|  String get id; String get slug; String? get name; List<SnPost> get posts; |  String get id; String get slug; String? get name; List<SnPost> get posts; int get usage; | ||||||
| /// Create a copy of SnPostTag | /// Create a copy of SnPostTag | ||||||
| /// 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 @@ $SnPostTagCopyWith<SnPostTag> get copyWith => _$SnPostTagCopyWithImpl<SnPostTag> | |||||||
|  |  | ||||||
| @override | @override | ||||||
| bool operator ==(Object other) { | bool operator ==(Object other) { | ||||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is SnPostTag&&(identical(other.id, id) || other.id == id)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.name, name) || other.name == name)&&const DeepCollectionEquality().equals(other.posts, posts)); |   return identical(this, other) || (other.runtimeType == runtimeType&&other is SnPostTag&&(identical(other.id, id) || other.id == id)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.name, name) || other.name == name)&&const DeepCollectionEquality().equals(other.posts, posts)&&(identical(other.usage, usage) || other.usage == usage)); | ||||||
| } | } | ||||||
|  |  | ||||||
| @JsonKey(includeFromJson: false, includeToJson: false) | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
| @override | @override | ||||||
| int get hashCode => Object.hash(runtimeType,id,slug,name,const DeepCollectionEquality().hash(posts)); | int get hashCode => Object.hash(runtimeType,id,slug,name,const DeepCollectionEquality().hash(posts),usage); | ||||||
|  |  | ||||||
| @override | @override | ||||||
| String toString() { | String toString() { | ||||||
|   return 'SnPostTag(id: $id, slug: $slug, name: $name, posts: $posts)'; |   return 'SnPostTag(id: $id, slug: $slug, name: $name, posts: $posts, usage: $usage)'; | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -48,7 +48,7 @@ abstract mixin class $SnPostTagCopyWith<$Res>  { | |||||||
|   factory $SnPostTagCopyWith(SnPostTag value, $Res Function(SnPostTag) _then) = _$SnPostTagCopyWithImpl; |   factory $SnPostTagCopyWith(SnPostTag value, $Res Function(SnPostTag) _then) = _$SnPostTagCopyWithImpl; | ||||||
| @useResult | @useResult | ||||||
| $Res call({ | $Res call({ | ||||||
|  String id, String slug, String? name, List<SnPost> posts |  String id, String slug, String? name, List<SnPost> posts, int usage | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -65,13 +65,14 @@ class _$SnPostTagCopyWithImpl<$Res> | |||||||
|  |  | ||||||
| /// Create a copy of SnPostTag | /// Create a copy of SnPostTag | ||||||
| /// 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? slug = null,Object? name = freezed,Object? posts = null,}) { | @pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? slug = null,Object? name = freezed,Object? posts = null,Object? usage = 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,slug: null == slug ? _self.slug : slug // ignore: cast_nullable_to_non_nullable | as String,slug: null == slug ? _self.slug : slug // ignore: cast_nullable_to_non_nullable | ||||||
| as String,name: freezed == name ? _self.name : name // ignore: cast_nullable_to_non_nullable | as String,name: freezed == name ? _self.name : name // ignore: cast_nullable_to_non_nullable | ||||||
| as String?,posts: null == posts ? _self.posts : posts // ignore: cast_nullable_to_non_nullable | as String?,posts: null == posts ? _self.posts : posts // ignore: cast_nullable_to_non_nullable | ||||||
| as List<SnPost>, | as List<SnPost>,usage: null == usage ? _self.usage : usage // ignore: cast_nullable_to_non_nullable | ||||||
|  | as int, | ||||||
|   )); |   )); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -153,10 +154,10 @@ return $default(_that);case _: | |||||||
| /// } | /// } | ||||||
| /// ``` | /// ``` | ||||||
|  |  | ||||||
| @optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id,  String slug,  String? name,  List<SnPost> posts)?  $default,{required TResult orElse(),}) {final _that = this; | @optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id,  String slug,  String? name,  List<SnPost> posts,  int usage)?  $default,{required TResult orElse(),}) {final _that = this; | ||||||
| switch (_that) { | switch (_that) { | ||||||
| case _SnPostTag() when $default != null: | case _SnPostTag() when $default != null: | ||||||
| return $default(_that.id,_that.slug,_that.name,_that.posts);case _: | return $default(_that.id,_that.slug,_that.name,_that.posts,_that.usage);case _: | ||||||
|   return orElse(); |   return orElse(); | ||||||
|  |  | ||||||
| } | } | ||||||
| @@ -174,10 +175,10 @@ return $default(_that.id,_that.slug,_that.name,_that.posts);case _: | |||||||
| /// } | /// } | ||||||
| /// ``` | /// ``` | ||||||
|  |  | ||||||
| @optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id,  String slug,  String? name,  List<SnPost> posts)  $default,) {final _that = this; | @optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id,  String slug,  String? name,  List<SnPost> posts,  int usage)  $default,) {final _that = this; | ||||||
| switch (_that) { | switch (_that) { | ||||||
| case _SnPostTag(): | case _SnPostTag(): | ||||||
| return $default(_that.id,_that.slug,_that.name,_that.posts);} | return $default(_that.id,_that.slug,_that.name,_that.posts,_that.usage);} | ||||||
| } | } | ||||||
| /// A variant of `when` that fallback to returning `null` | /// A variant of `when` that fallback to returning `null` | ||||||
| /// | /// | ||||||
| @@ -191,10 +192,10 @@ return $default(_that.id,_that.slug,_that.name,_that.posts);} | |||||||
| /// } | /// } | ||||||
| /// ``` | /// ``` | ||||||
|  |  | ||||||
| @optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id,  String slug,  String? name,  List<SnPost> posts)?  $default,) {final _that = this; | @optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id,  String slug,  String? name,  List<SnPost> posts,  int usage)?  $default,) {final _that = this; | ||||||
| switch (_that) { | switch (_that) { | ||||||
| case _SnPostTag() when $default != null: | case _SnPostTag() when $default != null: | ||||||
| return $default(_that.id,_that.slug,_that.name,_that.posts);case _: | return $default(_that.id,_that.slug,_that.name,_that.posts,_that.usage);case _: | ||||||
|   return null; |   return null; | ||||||
|  |  | ||||||
| } | } | ||||||
| @@ -206,7 +207,7 @@ return $default(_that.id,_that.slug,_that.name,_that.posts);case _: | |||||||
| @JsonSerializable() | @JsonSerializable() | ||||||
|  |  | ||||||
| class _SnPostTag implements SnPostTag { | class _SnPostTag implements SnPostTag { | ||||||
|   const _SnPostTag({required this.id, required this.slug, this.name, final  List<SnPost> posts = const []}): _posts = posts; |   const _SnPostTag({required this.id, required this.slug, this.name, final  List<SnPost> posts = const [], this.usage = 0}): _posts = posts; | ||||||
|   factory _SnPostTag.fromJson(Map<String, dynamic> json) => _$SnPostTagFromJson(json); |   factory _SnPostTag.fromJson(Map<String, dynamic> json) => _$SnPostTagFromJson(json); | ||||||
|  |  | ||||||
| @override final  String id; | @override final  String id; | ||||||
| @@ -219,6 +220,7 @@ class _SnPostTag implements SnPostTag { | |||||||
|   return EqualUnmodifiableListView(_posts); |   return EqualUnmodifiableListView(_posts); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @override@JsonKey() final  int usage; | ||||||
|  |  | ||||||
| /// Create a copy of SnPostTag | /// Create a copy of SnPostTag | ||||||
| /// with the given fields replaced by the non-null parameter values. | /// with the given fields replaced by the non-null parameter values. | ||||||
| @@ -233,16 +235,16 @@ Map<String, dynamic> toJson() { | |||||||
|  |  | ||||||
| @override | @override | ||||||
| bool operator ==(Object other) { | bool operator ==(Object other) { | ||||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnPostTag&&(identical(other.id, id) || other.id == id)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.name, name) || other.name == name)&&const DeepCollectionEquality().equals(other._posts, _posts)); |   return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnPostTag&&(identical(other.id, id) || other.id == id)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.name, name) || other.name == name)&&const DeepCollectionEquality().equals(other._posts, _posts)&&(identical(other.usage, usage) || other.usage == usage)); | ||||||
| } | } | ||||||
|  |  | ||||||
| @JsonKey(includeFromJson: false, includeToJson: false) | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
| @override | @override | ||||||
| int get hashCode => Object.hash(runtimeType,id,slug,name,const DeepCollectionEquality().hash(_posts)); | int get hashCode => Object.hash(runtimeType,id,slug,name,const DeepCollectionEquality().hash(_posts),usage); | ||||||
|  |  | ||||||
| @override | @override | ||||||
| String toString() { | String toString() { | ||||||
|   return 'SnPostTag(id: $id, slug: $slug, name: $name, posts: $posts)'; |   return 'SnPostTag(id: $id, slug: $slug, name: $name, posts: $posts, usage: $usage)'; | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -253,7 +255,7 @@ abstract mixin class _$SnPostTagCopyWith<$Res> implements $SnPostTagCopyWith<$Re | |||||||
|   factory _$SnPostTagCopyWith(_SnPostTag value, $Res Function(_SnPostTag) _then) = __$SnPostTagCopyWithImpl; |   factory _$SnPostTagCopyWith(_SnPostTag value, $Res Function(_SnPostTag) _then) = __$SnPostTagCopyWithImpl; | ||||||
| @override @useResult | @override @useResult | ||||||
| $Res call({ | $Res call({ | ||||||
|  String id, String slug, String? name, List<SnPost> posts |  String id, String slug, String? name, List<SnPost> posts, int usage | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -270,13 +272,14 @@ class __$SnPostTagCopyWithImpl<$Res> | |||||||
|  |  | ||||||
| /// Create a copy of SnPostTag | /// Create a copy of SnPostTag | ||||||
| /// 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? slug = null,Object? name = freezed,Object? posts = null,}) { | @override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? slug = null,Object? name = freezed,Object? posts = null,Object? usage = null,}) { | ||||||
|   return _then(_SnPostTag( |   return _then(_SnPostTag( | ||||||
| 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,slug: null == slug ? _self.slug : slug // ignore: cast_nullable_to_non_nullable | as String,slug: null == slug ? _self.slug : slug // ignore: cast_nullable_to_non_nullable | ||||||
| as String,name: freezed == name ? _self.name : name // ignore: cast_nullable_to_non_nullable | as String,name: freezed == name ? _self.name : name // ignore: cast_nullable_to_non_nullable | ||||||
| as String?,posts: null == posts ? _self._posts : posts // ignore: cast_nullable_to_non_nullable | as String?,posts: null == posts ? _self._posts : posts // ignore: cast_nullable_to_non_nullable | ||||||
| as List<SnPost>, | as List<SnPost>,usage: null == usage ? _self.usage : usage // ignore: cast_nullable_to_non_nullable | ||||||
|  | as int, | ||||||
|   )); |   )); | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -15,6 +15,7 @@ _SnPostTag _$SnPostTagFromJson(Map<String, dynamic> json) => _SnPostTag( | |||||||
|           ?.map((e) => SnPost.fromJson(e as Map<String, dynamic>)) |           ?.map((e) => SnPost.fromJson(e as Map<String, dynamic>)) | ||||||
|           .toList() ?? |           .toList() ?? | ||||||
|       const [], |       const [], | ||||||
|  |   usage: (json['usage'] as num?)?.toInt() ?? 0, | ||||||
| ); | ); | ||||||
|  |  | ||||||
| Map<String, dynamic> _$SnPostTagToJson(_SnPostTag instance) => | Map<String, dynamic> _$SnPostTagToJson(_SnPostTag instance) => | ||||||
| @@ -23,4 +24,5 @@ Map<String, dynamic> _$SnPostTagToJson(_SnPostTag instance) => | |||||||
|       'slug': instance.slug, |       'slug': instance.slug, | ||||||
|       'name': instance.name, |       'name': instance.name, | ||||||
|       'posts': instance.posts.map((e) => e.toJson()).toList(), |       'posts': instance.posts.map((e) => e.toJson()).toList(), | ||||||
|  |       'usage': instance.usage, | ||||||
|     }; |     }; | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| import 'package:freezed_annotation/freezed_annotation.dart'; | import 'package:freezed_annotation/freezed_annotation.dart'; | ||||||
| import 'package:island/models/file.dart'; | import 'package:island/models/file.dart'; | ||||||
| import 'package:island/models/user.dart'; | import 'package:island/models/account.dart'; | ||||||
|  |  | ||||||
| part 'publisher.freezed.dart'; | part 'publisher.freezed.dart'; | ||||||
| part 'publisher.g.dart'; | part 'publisher.g.dart'; | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| import 'package:freezed_annotation/freezed_annotation.dart'; | import 'package:freezed_annotation/freezed_annotation.dart'; | ||||||
| import 'package:island/models/file.dart'; | import 'package:island/models/file.dart'; | ||||||
| import 'package:island/models/user.dart'; | import 'package:island/models/account.dart'; | ||||||
|  |  | ||||||
| part 'realm.freezed.dart'; | part 'realm.freezed.dart'; | ||||||
| part 'realm.g.dart'; | part 'realm.g.dart'; | ||||||
| @@ -40,6 +40,7 @@ sealed class SnRealmMember with _$SnRealmMember { | |||||||
|     required DateTime createdAt, |     required DateTime createdAt, | ||||||
|     required DateTime updatedAt, |     required DateTime updatedAt, | ||||||
|     required DateTime? deletedAt, |     required DateTime? deletedAt, | ||||||
|  |     required SnAccountStatus? status, | ||||||
|   }) = _SnRealmMember; |   }) = _SnRealmMember; | ||||||
|  |  | ||||||
|   factory SnRealmMember.fromJson(Map<String, dynamic> json) => |   factory SnRealmMember.fromJson(Map<String, dynamic> json) => | ||||||
|   | |||||||
| @@ -359,7 +359,7 @@ $SnCloudFileCopyWith<$Res>? get background { | |||||||
| /// @nodoc | /// @nodoc | ||||||
| mixin _$SnRealmMember { | mixin _$SnRealmMember { | ||||||
|  |  | ||||||
|  String get realmId; SnRealm? get realm; String get accountId; SnAccount? get account; int get role; DateTime? get joinedAt; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; |  String get realmId; SnRealm? get realm; String get accountId; SnAccount? get account; int get role; DateTime? get joinedAt; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; SnAccountStatus? get status; | ||||||
| /// Create a copy of SnRealmMember | /// Create a copy of SnRealmMember | ||||||
| /// 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) | ||||||
| @@ -372,16 +372,16 @@ $SnRealmMemberCopyWith<SnRealmMember> get copyWith => _$SnRealmMemberCopyWithImp | |||||||
|  |  | ||||||
| @override | @override | ||||||
| bool operator ==(Object other) { | bool operator ==(Object other) { | ||||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is SnRealmMember&&(identical(other.realmId, realmId) || other.realmId == realmId)&&(identical(other.realm, realm) || other.realm == realm)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.account, account) || other.account == account)&&(identical(other.role, role) || other.role == role)&&(identical(other.joinedAt, joinedAt) || other.joinedAt == joinedAt)&&(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 SnRealmMember&&(identical(other.realmId, realmId) || other.realmId == realmId)&&(identical(other.realm, realm) || other.realm == realm)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.account, account) || other.account == account)&&(identical(other.role, role) || other.role == role)&&(identical(other.joinedAt, joinedAt) || other.joinedAt == joinedAt)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.status, status) || other.status == status)); | ||||||
| } | } | ||||||
|  |  | ||||||
| @JsonKey(includeFromJson: false, includeToJson: false) | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
| @override | @override | ||||||
| int get hashCode => Object.hash(runtimeType,realmId,realm,accountId,account,role,joinedAt,createdAt,updatedAt,deletedAt); | int get hashCode => Object.hash(runtimeType,realmId,realm,accountId,account,role,joinedAt,createdAt,updatedAt,deletedAt,status); | ||||||
|  |  | ||||||
| @override | @override | ||||||
| String toString() { | String toString() { | ||||||
|   return 'SnRealmMember(realmId: $realmId, realm: $realm, accountId: $accountId, account: $account, role: $role, joinedAt: $joinedAt, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; |   return 'SnRealmMember(realmId: $realmId, realm: $realm, accountId: $accountId, account: $account, role: $role, joinedAt: $joinedAt, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, status: $status)'; | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -392,11 +392,11 @@ abstract mixin class $SnRealmMemberCopyWith<$Res>  { | |||||||
|   factory $SnRealmMemberCopyWith(SnRealmMember value, $Res Function(SnRealmMember) _then) = _$SnRealmMemberCopyWithImpl; |   factory $SnRealmMemberCopyWith(SnRealmMember value, $Res Function(SnRealmMember) _then) = _$SnRealmMemberCopyWithImpl; | ||||||
| @useResult | @useResult | ||||||
| $Res call({ | $Res call({ | ||||||
|  String realmId, SnRealm? realm, String accountId, SnAccount? account, int role, DateTime? joinedAt, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt |  String realmId, SnRealm? realm, String accountId, SnAccount? account, int role, DateTime? joinedAt, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, SnAccountStatus? status | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  |  | ||||||
| $SnRealmCopyWith<$Res>? get realm;$SnAccountCopyWith<$Res>? get account; | $SnRealmCopyWith<$Res>? get realm;$SnAccountCopyWith<$Res>? get account;$SnAccountStatusCopyWith<$Res>? get status; | ||||||
|  |  | ||||||
| } | } | ||||||
| /// @nodoc | /// @nodoc | ||||||
| @@ -409,7 +409,7 @@ class _$SnRealmMemberCopyWithImpl<$Res> | |||||||
|  |  | ||||||
| /// Create a copy of SnRealmMember | /// Create a copy of SnRealmMember | ||||||
| /// 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? realmId = null,Object? realm = freezed,Object? accountId = null,Object? account = freezed,Object? role = null,Object? joinedAt = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) { | @pragma('vm:prefer-inline') @override $Res call({Object? realmId = null,Object? realm = freezed,Object? accountId = null,Object? account = freezed,Object? role = null,Object? joinedAt = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? status = freezed,}) { | ||||||
|   return _then(_self.copyWith( |   return _then(_self.copyWith( | ||||||
| realmId: null == realmId ? _self.realmId : realmId // ignore: cast_nullable_to_non_nullable | realmId: null == realmId ? _self.realmId : realmId // ignore: cast_nullable_to_non_nullable | ||||||
| as String,realm: freezed == realm ? _self.realm : realm // ignore: cast_nullable_to_non_nullable | as String,realm: freezed == realm ? _self.realm : realm // ignore: cast_nullable_to_non_nullable | ||||||
| @@ -420,7 +420,8 @@ as int,joinedAt: freezed == joinedAt ? _self.joinedAt : joinedAt // ignore: cast | |||||||
| as DateTime?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable | as DateTime?,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?,status: freezed == status ? _self.status : status // ignore: cast_nullable_to_non_nullable | ||||||
|  | as SnAccountStatus?, | ||||||
|   )); |   )); | ||||||
| } | } | ||||||
| /// Create a copy of SnRealmMember | /// Create a copy of SnRealmMember | ||||||
| @@ -447,6 +448,18 @@ $SnAccountCopyWith<$Res>? get account { | |||||||
|   return $SnAccountCopyWith<$Res>(_self.account!, (value) { |   return $SnAccountCopyWith<$Res>(_self.account!, (value) { | ||||||
|     return _then(_self.copyWith(account: value)); |     return _then(_self.copyWith(account: value)); | ||||||
|   }); |   }); | ||||||
|  | }/// Create a copy of SnRealmMember | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
|  | $SnAccountStatusCopyWith<$Res>? get status { | ||||||
|  |     if (_self.status == null) { | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return $SnAccountStatusCopyWith<$Res>(_self.status!, (value) { | ||||||
|  |     return _then(_self.copyWith(status: value)); | ||||||
|  |   }); | ||||||
| } | } | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -526,10 +539,10 @@ return $default(_that);case _: | |||||||
| /// } | /// } | ||||||
| /// ``` | /// ``` | ||||||
|  |  | ||||||
| @optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String realmId,  SnRealm? realm,  String accountId,  SnAccount? account,  int role,  DateTime? joinedAt,  DateTime createdAt,  DateTime updatedAt,  DateTime? deletedAt)?  $default,{required TResult orElse(),}) {final _that = this; | @optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String realmId,  SnRealm? realm,  String accountId,  SnAccount? account,  int role,  DateTime? joinedAt,  DateTime createdAt,  DateTime updatedAt,  DateTime? deletedAt,  SnAccountStatus? status)?  $default,{required TResult orElse(),}) {final _that = this; | ||||||
| switch (_that) { | switch (_that) { | ||||||
| case _SnRealmMember() when $default != null: | case _SnRealmMember() when $default != null: | ||||||
| return $default(_that.realmId,_that.realm,_that.accountId,_that.account,_that.role,_that.joinedAt,_that.createdAt,_that.updatedAt,_that.deletedAt);case _: | return $default(_that.realmId,_that.realm,_that.accountId,_that.account,_that.role,_that.joinedAt,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.status);case _: | ||||||
|   return orElse(); |   return orElse(); | ||||||
|  |  | ||||||
| } | } | ||||||
| @@ -547,10 +560,10 @@ return $default(_that.realmId,_that.realm,_that.accountId,_that.account,_that.ro | |||||||
| /// } | /// } | ||||||
| /// ``` | /// ``` | ||||||
|  |  | ||||||
| @optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String realmId,  SnRealm? realm,  String accountId,  SnAccount? account,  int role,  DateTime? joinedAt,  DateTime createdAt,  DateTime updatedAt,  DateTime? deletedAt)  $default,) {final _that = this; | @optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String realmId,  SnRealm? realm,  String accountId,  SnAccount? account,  int role,  DateTime? joinedAt,  DateTime createdAt,  DateTime updatedAt,  DateTime? deletedAt,  SnAccountStatus? status)  $default,) {final _that = this; | ||||||
| switch (_that) { | switch (_that) { | ||||||
| case _SnRealmMember(): | case _SnRealmMember(): | ||||||
| return $default(_that.realmId,_that.realm,_that.accountId,_that.account,_that.role,_that.joinedAt,_that.createdAt,_that.updatedAt,_that.deletedAt);} | return $default(_that.realmId,_that.realm,_that.accountId,_that.account,_that.role,_that.joinedAt,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.status);} | ||||||
| } | } | ||||||
| /// A variant of `when` that fallback to returning `null` | /// A variant of `when` that fallback to returning `null` | ||||||
| /// | /// | ||||||
| @@ -564,10 +577,10 @@ return $default(_that.realmId,_that.realm,_that.accountId,_that.account,_that.ro | |||||||
| /// } | /// } | ||||||
| /// ``` | /// ``` | ||||||
|  |  | ||||||
| @optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String realmId,  SnRealm? realm,  String accountId,  SnAccount? account,  int role,  DateTime? joinedAt,  DateTime createdAt,  DateTime updatedAt,  DateTime? deletedAt)?  $default,) {final _that = this; | @optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String realmId,  SnRealm? realm,  String accountId,  SnAccount? account,  int role,  DateTime? joinedAt,  DateTime createdAt,  DateTime updatedAt,  DateTime? deletedAt,  SnAccountStatus? status)?  $default,) {final _that = this; | ||||||
| switch (_that) { | switch (_that) { | ||||||
| case _SnRealmMember() when $default != null: | case _SnRealmMember() when $default != null: | ||||||
| return $default(_that.realmId,_that.realm,_that.accountId,_that.account,_that.role,_that.joinedAt,_that.createdAt,_that.updatedAt,_that.deletedAt);case _: | return $default(_that.realmId,_that.realm,_that.accountId,_that.account,_that.role,_that.joinedAt,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.status);case _: | ||||||
|   return null; |   return null; | ||||||
|  |  | ||||||
| } | } | ||||||
| @@ -579,7 +592,7 @@ return $default(_that.realmId,_that.realm,_that.accountId,_that.account,_that.ro | |||||||
| @JsonSerializable() | @JsonSerializable() | ||||||
|  |  | ||||||
| class _SnRealmMember implements SnRealmMember { | class _SnRealmMember implements SnRealmMember { | ||||||
|   const _SnRealmMember({required this.realmId, required this.realm, required this.accountId, required this.account, required this.role, required this.joinedAt, required this.createdAt, required this.updatedAt, required this.deletedAt}); |   const _SnRealmMember({required this.realmId, required this.realm, required this.accountId, required this.account, required this.role, required this.joinedAt, required this.createdAt, required this.updatedAt, required this.deletedAt, required this.status}); | ||||||
|   factory _SnRealmMember.fromJson(Map<String, dynamic> json) => _$SnRealmMemberFromJson(json); |   factory _SnRealmMember.fromJson(Map<String, dynamic> json) => _$SnRealmMemberFromJson(json); | ||||||
|  |  | ||||||
| @override final  String realmId; | @override final  String realmId; | ||||||
| @@ -591,6 +604,7 @@ class _SnRealmMember implements SnRealmMember { | |||||||
| @override final  DateTime createdAt; | @override final  DateTime createdAt; | ||||||
| @override final  DateTime updatedAt; | @override final  DateTime updatedAt; | ||||||
| @override final  DateTime? deletedAt; | @override final  DateTime? deletedAt; | ||||||
|  | @override final  SnAccountStatus? status; | ||||||
|  |  | ||||||
| /// Create a copy of SnRealmMember | /// Create a copy of SnRealmMember | ||||||
| /// with the given fields replaced by the non-null parameter values. | /// with the given fields replaced by the non-null parameter values. | ||||||
| @@ -605,16 +619,16 @@ Map<String, dynamic> toJson() { | |||||||
|  |  | ||||||
| @override | @override | ||||||
| bool operator ==(Object other) { | bool operator ==(Object other) { | ||||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnRealmMember&&(identical(other.realmId, realmId) || other.realmId == realmId)&&(identical(other.realm, realm) || other.realm == realm)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.account, account) || other.account == account)&&(identical(other.role, role) || other.role == role)&&(identical(other.joinedAt, joinedAt) || other.joinedAt == joinedAt)&&(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 _SnRealmMember&&(identical(other.realmId, realmId) || other.realmId == realmId)&&(identical(other.realm, realm) || other.realm == realm)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.account, account) || other.account == account)&&(identical(other.role, role) || other.role == role)&&(identical(other.joinedAt, joinedAt) || other.joinedAt == joinedAt)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.status, status) || other.status == status)); | ||||||
| } | } | ||||||
|  |  | ||||||
| @JsonKey(includeFromJson: false, includeToJson: false) | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
| @override | @override | ||||||
| int get hashCode => Object.hash(runtimeType,realmId,realm,accountId,account,role,joinedAt,createdAt,updatedAt,deletedAt); | int get hashCode => Object.hash(runtimeType,realmId,realm,accountId,account,role,joinedAt,createdAt,updatedAt,deletedAt,status); | ||||||
|  |  | ||||||
| @override | @override | ||||||
| String toString() { | String toString() { | ||||||
|   return 'SnRealmMember(realmId: $realmId, realm: $realm, accountId: $accountId, account: $account, role: $role, joinedAt: $joinedAt, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; |   return 'SnRealmMember(realmId: $realmId, realm: $realm, accountId: $accountId, account: $account, role: $role, joinedAt: $joinedAt, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, status: $status)'; | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -625,11 +639,11 @@ abstract mixin class _$SnRealmMemberCopyWith<$Res> implements $SnRealmMemberCopy | |||||||
|   factory _$SnRealmMemberCopyWith(_SnRealmMember value, $Res Function(_SnRealmMember) _then) = __$SnRealmMemberCopyWithImpl; |   factory _$SnRealmMemberCopyWith(_SnRealmMember value, $Res Function(_SnRealmMember) _then) = __$SnRealmMemberCopyWithImpl; | ||||||
| @override @useResult | @override @useResult | ||||||
| $Res call({ | $Res call({ | ||||||
|  String realmId, SnRealm? realm, String accountId, SnAccount? account, int role, DateTime? joinedAt, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt |  String realmId, SnRealm? realm, String accountId, SnAccount? account, int role, DateTime? joinedAt, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, SnAccountStatus? status | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  |  | ||||||
| @override $SnRealmCopyWith<$Res>? get realm;@override $SnAccountCopyWith<$Res>? get account; | @override $SnRealmCopyWith<$Res>? get realm;@override $SnAccountCopyWith<$Res>? get account;@override $SnAccountStatusCopyWith<$Res>? get status; | ||||||
|  |  | ||||||
| } | } | ||||||
| /// @nodoc | /// @nodoc | ||||||
| @@ -642,7 +656,7 @@ class __$SnRealmMemberCopyWithImpl<$Res> | |||||||
|  |  | ||||||
| /// Create a copy of SnRealmMember | /// Create a copy of SnRealmMember | ||||||
| /// 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? realmId = null,Object? realm = freezed,Object? accountId = null,Object? account = freezed,Object? role = null,Object? joinedAt = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) { | @override @pragma('vm:prefer-inline') $Res call({Object? realmId = null,Object? realm = freezed,Object? accountId = null,Object? account = freezed,Object? role = null,Object? joinedAt = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? status = freezed,}) { | ||||||
|   return _then(_SnRealmMember( |   return _then(_SnRealmMember( | ||||||
| realmId: null == realmId ? _self.realmId : realmId // ignore: cast_nullable_to_non_nullable | realmId: null == realmId ? _self.realmId : realmId // ignore: cast_nullable_to_non_nullable | ||||||
| as String,realm: freezed == realm ? _self.realm : realm // ignore: cast_nullable_to_non_nullable | as String,realm: freezed == realm ? _self.realm : realm // ignore: cast_nullable_to_non_nullable | ||||||
| @@ -653,7 +667,8 @@ as int,joinedAt: freezed == joinedAt ? _self.joinedAt : joinedAt // ignore: cast | |||||||
| as DateTime?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable | as DateTime?,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?,status: freezed == status ? _self.status : status // ignore: cast_nullable_to_non_nullable | ||||||
|  | as SnAccountStatus?, | ||||||
|   )); |   )); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -681,6 +696,18 @@ $SnAccountCopyWith<$Res>? get account { | |||||||
|   return $SnAccountCopyWith<$Res>(_self.account!, (value) { |   return $SnAccountCopyWith<$Res>(_self.account!, (value) { | ||||||
|     return _then(_self.copyWith(account: value)); |     return _then(_self.copyWith(account: value)); | ||||||
|   }); |   }); | ||||||
|  | }/// Create a copy of SnRealmMember | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
|  | $SnAccountStatusCopyWith<$Res>? get status { | ||||||
|  |     if (_self.status == null) { | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return $SnAccountStatusCopyWith<$Res>(_self.status!, (value) { | ||||||
|  |     return _then(_self.copyWith(status: value)); | ||||||
|  |   }); | ||||||
| } | } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -75,6 +75,12 @@ _SnRealmMember _$SnRealmMemberFromJson(Map<String, dynamic> json) => | |||||||
|           json['deleted_at'] == null |           json['deleted_at'] == null | ||||||
|               ? null |               ? null | ||||||
|               : DateTime.parse(json['deleted_at'] as String), |               : DateTime.parse(json['deleted_at'] as String), | ||||||
|  |       status: | ||||||
|  |           json['status'] == null | ||||||
|  |               ? null | ||||||
|  |               : SnAccountStatus.fromJson( | ||||||
|  |                 json['status'] as Map<String, dynamic>, | ||||||
|  |               ), | ||||||
|     ); |     ); | ||||||
|  |  | ||||||
| Map<String, dynamic> _$SnRealmMemberToJson(_SnRealmMember instance) => | Map<String, dynamic> _$SnRealmMemberToJson(_SnRealmMember instance) => | ||||||
| @@ -88,4 +94,5 @@ Map<String, dynamic> _$SnRealmMemberToJson(_SnRealmMember instance) => | |||||||
|       '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(), | ||||||
|  |       'status': instance.status?.toJson(), | ||||||
|     }; |     }; | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| import 'package:freezed_annotation/freezed_annotation.dart'; | import 'package:freezed_annotation/freezed_annotation.dart'; | ||||||
|  |  | ||||||
| import 'package:island/models/user.dart'; | import 'package:island/models/account.dart'; | ||||||
|  |  | ||||||
| part 'relationship.freezed.dart'; | part 'relationship.freezed.dart'; | ||||||
| part 'relationship.g.dart'; | part 'relationship.g.dart'; | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| import 'package:freezed_annotation/freezed_annotation.dart'; | import 'package:freezed_annotation/freezed_annotation.dart'; | ||||||
| import 'package:island/models/user.dart'; | import 'package:island/models/account.dart'; | ||||||
|  |  | ||||||
| part 'wallet.freezed.dart'; | part 'wallet.freezed.dart'; | ||||||
| part 'wallet.g.dart'; | part 'wallet.g.dart'; | ||||||
|   | |||||||
| @@ -23,6 +23,8 @@ 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'; | ||||||
| const kAppEnterToSend = 'app_enter_to_send'; | const kAppEnterToSend = 'app_enter_to_send'; | ||||||
|  | const kFeaturedPostsCollapsedId = | ||||||
|  |     'featured_posts_collapsed_id'; // Key for storing the ID of the collapsed featured post | ||||||
|  |  | ||||||
| const Map<String, FilterQuality> kImageQualityLevel = { | const Map<String, FilterQuality> kImageQualityLevel = { | ||||||
|   'settingsImageQualityLowest': FilterQuality.none, |   'settingsImageQualityLowest': FilterQuality.none, | ||||||
|   | |||||||
| @@ -1,8 +1,14 @@ | |||||||
|  | import 'dart:convert'; | ||||||
| import 'dart:developer'; | import 'dart:developer'; | ||||||
|  |  | ||||||
|  | import 'package:dio/dio.dart'; | ||||||
|  | import 'package:easy_localization/easy_localization.dart'; | ||||||
|  | import 'package:firebase_analytics/firebase_analytics.dart'; | ||||||
|  | import 'package:flutter/foundation.dart'; | ||||||
|  | import 'package:flutter_platform_alert/flutter_platform_alert.dart'; | ||||||
| import 'package:flutter_riverpod/flutter_riverpod.dart'; | import 'package:flutter_riverpod/flutter_riverpod.dart'; | ||||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
| import 'package:island/models/user.dart'; | import 'package:island/models/account.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'; | ||||||
|  |  | ||||||
| @@ -12,12 +18,56 @@ class UserInfoNotifier extends StateNotifier<AsyncValue<SnAccount?>> { | |||||||
|   UserInfoNotifier(this._ref) : super(const AsyncValue.data(null)); |   UserInfoNotifier(this._ref) : super(const AsyncValue.data(null)); | ||||||
|  |  | ||||||
|   Future<void> fetchUser() async { |   Future<void> fetchUser() async { | ||||||
|  |     final token = _ref.watch(tokenProvider); | ||||||
|  |     if (token == null) { | ||||||
|  |       log('[UserInfo] No token found, not going to fetch...'); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|     try { |     try { | ||||||
|       final client = _ref.read(apiClientProvider); |       final client = _ref.read(apiClientProvider); | ||||||
|       final response = await client.get('/id/accounts/me'); |       final response = await client.get('/id/accounts/me'); | ||||||
|       final user = SnAccount.fromJson(response.data); |       final user = SnAccount.fromJson(response.data); | ||||||
|       state = AsyncValue.data(user); |       state = AsyncValue.data(user); | ||||||
|  |       FirebaseAnalytics.instance.setUserId(id: user.id); | ||||||
|     } catch (error, stackTrace) { |     } catch (error, stackTrace) { | ||||||
|  |       if (!kIsWeb) { | ||||||
|  |         if (error is DioException) { | ||||||
|  |           FlutterPlatformAlert.showCustomAlert( | ||||||
|  |             windowTitle: 'failedToLoadUserInfo'.tr(), | ||||||
|  |             text: [ | ||||||
|  |               (error.response?.statusCode == 401 | ||||||
|  |                       ? 'failedToLoadUserInfoUnauthorized' | ||||||
|  |                       : 'failedToLoadUserInfoNetwork') | ||||||
|  |                   .tr() | ||||||
|  |                   .trim(), | ||||||
|  |               '${error.response!.statusCode}\n${error.response?.headers}', | ||||||
|  |               jsonEncode(error.response?.data), | ||||||
|  |             ].join('\n\n'), | ||||||
|  |             iconStyle: IconStyle.error, | ||||||
|  |             neutralButtonTitle: 'retry'.tr(), | ||||||
|  |             negativeButtonTitle: 'okay'.tr(), | ||||||
|  |           ).then((value) { | ||||||
|  |             if (value == CustomButton.neutralButton) { | ||||||
|  |               fetchUser(); | ||||||
|  |             } | ||||||
|  |           }); | ||||||
|  |         } | ||||||
|  |         FlutterPlatformAlert.showCustomAlert( | ||||||
|  |           windowTitle: 'failedToLoadUserInfo'.tr(), | ||||||
|  |           text: | ||||||
|  |               [ | ||||||
|  |                 'failedToLoadUserInfoNetwork'.tr(), | ||||||
|  |                 error.toString(), | ||||||
|  |               ].join('\n\n').trim(), | ||||||
|  |           iconStyle: IconStyle.error, | ||||||
|  |           neutralButtonTitle: 'retry'.tr(), | ||||||
|  |           negativeButtonTitle: 'okay'.tr(), | ||||||
|  |         ).then((value) { | ||||||
|  |           if (value == CustomButton.neutralButton) { | ||||||
|  |             fetchUser(); | ||||||
|  |           } | ||||||
|  |         }); | ||||||
|  |       } | ||||||
|       log( |       log( | ||||||
|         "[UserInfo] Failed to fetch user info...", |         "[UserInfo] Failed to fetch user info...", | ||||||
|         name: 'UserInfoNotifier', |         name: 'UserInfoNotifier', | ||||||
| @@ -33,6 +83,7 @@ class UserInfoNotifier extends StateNotifier<AsyncValue<SnAccount?>> { | |||||||
|     final prefs = _ref.read(sharedPreferencesProvider); |     final prefs = _ref.read(sharedPreferencesProvider); | ||||||
|     await prefs.remove(kTokenPairStoreKey); |     await prefs.remove(kTokenPairStoreKey); | ||||||
|     _ref.invalidate(tokenProvider); |     _ref.invalidate(tokenProvider); | ||||||
|  |     FirebaseAnalytics.instance.setUserId(id: null); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,12 +1,18 @@ | |||||||
|  | import 'dart:io' show Platform; | ||||||
|  | import 'package:animations/animations.dart'; | ||||||
|  | import 'package:firebase_analytics/firebase_analytics.dart'; | ||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:flutter/foundation.dart' show kIsWeb; | ||||||
| import 'package:go_router/go_router.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/screens/about.dart'; | import 'package:island/screens/about.dart'; | ||||||
|  | import 'package:island/screens/account/credits.dart'; | ||||||
| import 'package:island/screens/developers/apps.dart'; | import 'package:island/screens/developers/apps.dart'; | ||||||
| import 'package:island/screens/developers/edit_app.dart'; | import 'package:island/screens/developers/edit_app.dart'; | ||||||
| import 'package:island/screens/developers/new_app.dart'; | import 'package:island/screens/developers/new_app.dart'; | ||||||
| import 'package:island/screens/developers/hub.dart'; | import 'package:island/screens/developers/hub.dart'; | ||||||
| import 'package:island/screens/discovery/articles.dart'; | import 'package:island/screens/discovery/articles.dart'; | ||||||
|  | import 'package:island/screens/posts/post_categories_list.dart'; | ||||||
| import 'package:island/screens/posts/post_category_detail.dart'; | import 'package:island/screens/posts/post_category_detail.dart'; | ||||||
| import 'package:island/screens/posts/post_search.dart'; | import 'package:island/screens/posts/post_search.dart'; | ||||||
| import 'package:island/widgets/app_wrapper.dart'; | import 'package:island/widgets/app_wrapper.dart'; | ||||||
| @@ -18,9 +24,9 @@ import 'package:island/screens/notification.dart'; | |||||||
| import 'package:island/screens/wallet.dart'; | import 'package:island/screens/wallet.dart'; | ||||||
| import 'package:island/screens/account/relationship.dart'; | import 'package:island/screens/account/relationship.dart'; | ||||||
| import 'package:island/screens/account/profile.dart'; | import 'package:island/screens/account/profile.dart'; | ||||||
| import 'package:island/screens/account/me/update.dart'; | import 'package:island/screens/account/me/profile_update.dart'; | ||||||
| import 'package:island/screens/account/leveling.dart'; | import 'package:island/screens/account/leveling.dart'; | ||||||
| import 'package:island/screens/account/me/settings.dart'; | import 'package:island/screens/account/me/account_settings.dart'; | ||||||
| import 'package:island/screens/chat/chat.dart'; | import 'package:island/screens/chat/chat.dart'; | ||||||
| import 'package:island/screens/chat/room.dart'; | import 'package:island/screens/chat/room.dart'; | ||||||
| import 'package:island/screens/chat/room_detail.dart'; | import 'package:island/screens/chat/room_detail.dart'; | ||||||
| @@ -48,17 +54,43 @@ import 'package:island/screens/account/event_calendar.dart'; | |||||||
| import 'package:island/screens/discovery/realms.dart'; | import 'package:island/screens/discovery/realms.dart'; | ||||||
| import 'package:island/screens/reports/report_detail.dart'; | import 'package:island/screens/reports/report_detail.dart'; | ||||||
| import 'package:island/screens/reports/report_list.dart'; | import 'package:island/screens/reports/report_list.dart'; | ||||||
|  | import 'package:island/widgets/post/post_shuffle.dart'; | ||||||
|  |  | ||||||
| // Shell route keys for nested navigation | // Shell route keys for nested navigation | ||||||
| final rootNavigatorKey = GlobalKey<NavigatorState>(); | final rootNavigatorKey = GlobalKey<NavigatorState>(); | ||||||
| final _shellNavigatorKey = GlobalKey<NavigatorState>(); | final _shellNavigatorKey = GlobalKey<NavigatorState>(); | ||||||
| final _tabsShellKey = GlobalKey<NavigatorState>(); | final _tabsShellKey = GlobalKey<NavigatorState>(); | ||||||
|  |  | ||||||
|  | Widget _tabPagesTransitionBuilder( | ||||||
|  |   BuildContext context, | ||||||
|  |   Animation<double> animation, | ||||||
|  |   Animation<double> secondaryAnimation, | ||||||
|  |   Widget child, | ||||||
|  | ) { | ||||||
|  |   return FadeThroughTransition( | ||||||
|  |     animation: animation, | ||||||
|  |     secondaryAnimation: secondaryAnimation, | ||||||
|  |     fillColor: Theme.of(context).colorScheme.surface, | ||||||
|  |     child: child, | ||||||
|  |   ); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool get _supportsAnalytics => | ||||||
|  |     kIsWeb || | ||||||
|  |     Platform.isAndroid || | ||||||
|  |     Platform.isIOS || | ||||||
|  |     Platform.isMacOS || | ||||||
|  |     Platform.isWindows; | ||||||
|  |  | ||||||
| // Provider for the router | // Provider for the router | ||||||
| final routerProvider = Provider<GoRouter>((ref) { | final routerProvider = Provider<GoRouter>((ref) { | ||||||
|   return GoRouter( |   return GoRouter( | ||||||
|     navigatorKey: rootNavigatorKey, |     navigatorKey: rootNavigatorKey, | ||||||
|     initialLocation: '/', |     initialLocation: '/', | ||||||
|  |     observers: [ | ||||||
|  |       if (_supportsAnalytics) | ||||||
|  |         FirebaseAnalyticsObserver(analytics: FirebaseAnalytics.instance), | ||||||
|  |     ], | ||||||
|     routes: [ |     routes: [ | ||||||
|       ShellRoute( |       ShellRoute( | ||||||
|         navigatorKey: _shellNavigatorKey, |         navigatorKey: _shellNavigatorKey, | ||||||
| @@ -334,7 +366,12 @@ final routerProvider = Provider<GoRouter>((ref) { | |||||||
|               GoRoute( |               GoRoute( | ||||||
|                 name: 'explore', |                 name: 'explore', | ||||||
|                 path: '/', |                 path: '/', | ||||||
|                 builder: (context, state) => const ExploreScreen(), |                 pageBuilder: | ||||||
|  |                     (context, state) => CustomTransitionPage( | ||||||
|  |                       key: const ValueKey('explore'), | ||||||
|  |                       child: const ExploreScreen(), | ||||||
|  |                       transitionsBuilder: _tabPagesTransitionBuilder, | ||||||
|  |                     ), | ||||||
|               ), |               ), | ||||||
|               GoRoute( |               GoRoute( | ||||||
|                 name: 'postSearch', |                 name: 'postSearch', | ||||||
| @@ -342,12 +379,14 @@ final routerProvider = Provider<GoRouter>((ref) { | |||||||
|                 builder: (context, state) => const PostSearchScreen(), |                 builder: (context, state) => const PostSearchScreen(), | ||||||
|               ), |               ), | ||||||
|               GoRoute( |               GoRoute( | ||||||
|                 name: 'postDetail', |                 name: 'postShuffle', | ||||||
|                 path: '/posts/:id', |                 path: '/posts/shuffle', | ||||||
|                 builder: (context, state) { |                 builder: (context, state) => const PostShuffleScreen(), | ||||||
|                   final id = state.pathParameters['id']!; |               ), | ||||||
|                   return PostDetailScreen(id: id); |               GoRoute( | ||||||
|                 }, |                 name: 'postCategories', | ||||||
|  |                 path: '/posts/categories', | ||||||
|  |                 builder: (context, state) => const PostCategoriesListScreen(), | ||||||
|               ), |               ), | ||||||
|               GoRoute( |               GoRoute( | ||||||
|                 name: 'postCategoryDetail', |                 name: 'postCategoryDetail', | ||||||
| @@ -357,6 +396,11 @@ final routerProvider = Provider<GoRouter>((ref) { | |||||||
|                   return PostCategoryDetailScreen(slug: slug, isCategory: true); |                   return PostCategoryDetailScreen(slug: slug, isCategory: true); | ||||||
|                 }, |                 }, | ||||||
|               ), |               ), | ||||||
|  |               GoRoute( | ||||||
|  |                 name: 'postTags', | ||||||
|  |                 path: '/posts/tags', | ||||||
|  |                 builder: (context, state) => const PostTagsListScreen(), | ||||||
|  |               ), | ||||||
|               GoRoute( |               GoRoute( | ||||||
|                 name: 'postTagDetail', |                 name: 'postTagDetail', | ||||||
|                 path: '/posts/tags/:slug', |                 path: '/posts/tags/:slug', | ||||||
| @@ -368,6 +412,14 @@ final routerProvider = Provider<GoRouter>((ref) { | |||||||
|                   ); |                   ); | ||||||
|                 }, |                 }, | ||||||
|               ), |               ), | ||||||
|  |               GoRoute( | ||||||
|  |                 name: 'postDetail', | ||||||
|  |                 path: '/posts/:id', | ||||||
|  |                 builder: (context, state) { | ||||||
|  |                   final id = state.pathParameters['id']!; | ||||||
|  |                   return PostDetailScreen(id: id); | ||||||
|  |                 }, | ||||||
|  |               ), | ||||||
|               GoRoute( |               GoRoute( | ||||||
|                 name: 'publisherProfile', |                 name: 'publisherProfile', | ||||||
|                 path: '/publishers/:name', |                 path: '/publishers/:name', | ||||||
| @@ -384,8 +436,12 @@ final routerProvider = Provider<GoRouter>((ref) { | |||||||
|  |  | ||||||
|               // Chat tab |               // Chat tab | ||||||
|               ShellRoute( |               ShellRoute( | ||||||
|                 builder: |                 pageBuilder: | ||||||
|                     (context, state, child) => ChatShellScreen(child: child), |                     (context, state, child) => CustomTransitionPage( | ||||||
|  |                       key: const ValueKey('chat'), | ||||||
|  |                       child: ChatShellScreen(child: child), | ||||||
|  |                       transitionsBuilder: _tabPagesTransitionBuilder, | ||||||
|  |                     ), | ||||||
|                 routes: [ |                 routes: [ | ||||||
|                   GoRoute( |                   GoRoute( | ||||||
|                     name: 'chatList', |                     name: 'chatList', | ||||||
| @@ -428,7 +484,12 @@ final routerProvider = Provider<GoRouter>((ref) { | |||||||
|               GoRoute( |               GoRoute( | ||||||
|                 name: 'realmList', |                 name: 'realmList', | ||||||
|                 path: '/realms', |                 path: '/realms', | ||||||
|                 builder: (context, state) => const RealmListScreen(), |                 pageBuilder: | ||||||
|  |                     (context, state) => CustomTransitionPage( | ||||||
|  |                       key: const ValueKey('realms'), | ||||||
|  |                       child: const RealmListScreen(), | ||||||
|  |                       transitionsBuilder: _tabPagesTransitionBuilder, | ||||||
|  |                     ), | ||||||
|                 routes: [ |                 routes: [ | ||||||
|                   GoRoute( |                   GoRoute( | ||||||
|                     name: 'realmNew', |                     name: 'realmNew', | ||||||
| @@ -456,8 +517,12 @@ final routerProvider = Provider<GoRouter>((ref) { | |||||||
|  |  | ||||||
|               // Account tab |               // Account tab | ||||||
|               ShellRoute( |               ShellRoute( | ||||||
|                 builder: |                 pageBuilder: | ||||||
|                     (context, state, child) => AccountShellScreen(child: child), |                     (context, state, child) => CustomTransitionPage( | ||||||
|  |                       key: const ValueKey('account'), | ||||||
|  |                       child: AccountShellScreen(child: child), | ||||||
|  |                       transitionsBuilder: _tabPagesTransitionBuilder, | ||||||
|  |                     ), | ||||||
|                 routes: [ |                 routes: [ | ||||||
|                   GoRoute( |                   GoRoute( | ||||||
|                     name: 'account', |                     name: 'account', | ||||||
| @@ -491,6 +556,11 @@ final routerProvider = Provider<GoRouter>((ref) { | |||||||
|                     path: '/account/wallet', |                     path: '/account/wallet', | ||||||
|                     builder: (context, state) => const WalletScreen(), |                     builder: (context, state) => const WalletScreen(), | ||||||
|                   ), |                   ), | ||||||
|  |                   GoRoute( | ||||||
|  |                     name: 'socialCredits', | ||||||
|  |                     path: '/account/credits', | ||||||
|  |                     builder: (context, state) => const SocialCreditsScreen(), | ||||||
|  |                   ), | ||||||
|                   GoRoute( |                   GoRoute( | ||||||
|                     name: 'relationships', |                     name: 'relationships', | ||||||
|                     path: '/account/relationships', |                     path: '/account/relationships', | ||||||
|   | |||||||
| @@ -178,7 +178,8 @@ class _AboutScreenState extends ConsumerState<AboutScreen> { | |||||||
|                                 context, |                                 context, | ||||||
|                                 icon: Symbols.label, |                                 icon: Symbols.label, | ||||||
|                                 label: 'aboutDeviceName'.tr(), |                                 label: 'aboutDeviceName'.tr(), | ||||||
|                                 value: _deviceInfo?.data['name'], |                                 value: | ||||||
|  |                                     _deviceInfo?.data['name'] ?? 'unknown'.tr(), | ||||||
|                               ), |                               ), | ||||||
|                               _buildInfoItem( |                               _buildInfoItem( | ||||||
|                                 context, |                                 context, | ||||||
|   | |||||||
| @@ -3,12 +3,14 @@ import 'package:flutter/material.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'; | ||||||
|  | import 'package:island/pods/network.dart'; | ||||||
| import 'package:island/pods/userinfo.dart'; | import 'package:island/pods/userinfo.dart'; | ||||||
| import 'package:island/screens/notification.dart'; | import 'package:island/screens/notification.dart'; | ||||||
| import 'package:island/services/responsive.dart'; | import 'package:island/services/responsive.dart'; | ||||||
| import 'package:island/widgets/account/account_name.dart'; | import 'package:island/widgets/account/account_name.dart'; | ||||||
| import 'package:island/widgets/account/status.dart'; | import 'package:island/widgets/account/status.dart'; | ||||||
| import 'package:island/widgets/account/leveling_progress.dart'; | import 'package:island/widgets/account/leveling_progress.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/content/cloud_files.dart'; | import 'package:island/widgets/content/cloud_files.dart'; | ||||||
| import 'package:island/widgets/debug_sheet.dart'; | import 'package:island/widgets/debug_sheet.dart'; | ||||||
| @@ -234,6 +236,16 @@ class AccountScreen extends HookConsumerWidget { | |||||||
|                 context.pushNamed('stickerMarketplace'); |                 context.pushNamed('stickerMarketplace'); | ||||||
|               }, |               }, | ||||||
|             ), |             ), | ||||||
|  |             ListTile( | ||||||
|  |               minTileHeight: 48, | ||||||
|  |               leading: const Icon(Symbols.star), | ||||||
|  |               trailing: const Icon(Symbols.chevron_right), | ||||||
|  |               contentPadding: EdgeInsets.symmetric(horizontal: 24), | ||||||
|  |               title: Text('credits').tr(), | ||||||
|  |               onTap: () { | ||||||
|  |                 context.pushNamed('socialCredits'); | ||||||
|  |               }, | ||||||
|  |             ), | ||||||
|             ListTile( |             ListTile( | ||||||
|               minTileHeight: 48, |               minTileHeight: 48, | ||||||
|               title: Text('abuseReport').tr(), |               title: Text('abuseReport').tr(), | ||||||
| @@ -303,7 +315,12 @@ class AccountScreen extends HookConsumerWidget { | |||||||
|               trailing: const Icon(Symbols.chevron_right), |               trailing: const Icon(Symbols.chevron_right), | ||||||
|               contentPadding: EdgeInsets.symmetric(horizontal: 24), |               contentPadding: EdgeInsets.symmetric(horizontal: 24), | ||||||
|               title: Text('logout').tr(), |               title: Text('logout').tr(), | ||||||
|               onTap: () { |               onTap: () async { | ||||||
|  |                 final apiClient = ref.watch(apiClientProvider); | ||||||
|  |                 showLoadingModal(context); | ||||||
|  |                 await apiClient.delete('/id/accounts/me/sessions/current'); | ||||||
|  |                 if (!context.mounted) return; | ||||||
|  |                 hideLoadingModal(context); | ||||||
|                 final userNotifier = ref.read(userInfoProvider.notifier); |                 final userNotifier = ref.read(userInfoProvider.notifier); | ||||||
|                 userNotifier.logOut(); |                 userNotifier.logOut(); | ||||||
|               }, |               }, | ||||||
| @@ -382,6 +399,15 @@ class _UnauthorizedAccountScreen extends StatelessWidget { | |||||||
|                       }, |                       }, | ||||||
|                       child: Text('about').tr(), |                       child: Text('about').tr(), | ||||||
|                     ), |                     ), | ||||||
|  |                     TextButton( | ||||||
|  |                       child: Text('debugOptions').tr(), | ||||||
|  |                       onPressed: () { | ||||||
|  |                         showModalBottomSheet( | ||||||
|  |                           context: context, | ||||||
|  |                           builder: (context) => DebugSheet(), | ||||||
|  |                         ); | ||||||
|  |                       }, | ||||||
|  |                     ), | ||||||
|                     TextButton( |                     TextButton( | ||||||
|                       onPressed: () { |                       onPressed: () { | ||||||
|                         context.pushNamed('settings'); |                         context.pushNamed('settings'); | ||||||
|   | |||||||
							
								
								
									
										152
									
								
								lib/screens/account/credits.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										152
									
								
								lib/screens/account/credits.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,152 @@ | |||||||
|  | import 'package:easy_localization/easy_localization.dart'; | ||||||
|  | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:gap/gap.dart'; | ||||||
|  | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
|  | import 'package:island/models/account.dart'; | ||||||
|  | import 'package:island/pods/network.dart'; | ||||||
|  | import 'package:island/widgets/app_scaffold.dart'; | ||||||
|  | import 'package:material_symbols_icons/material_symbols_icons.dart'; | ||||||
|  | import 'package:riverpod_annotation/riverpod_annotation.dart'; | ||||||
|  | import 'package:riverpod_paging_utils/riverpod_paging_utils.dart'; | ||||||
|  | import 'package:styled_widget/styled_widget.dart'; | ||||||
|  |  | ||||||
|  | part 'credits.g.dart'; | ||||||
|  |  | ||||||
|  | @riverpod | ||||||
|  | Future<double> socialCredits(Ref ref) async { | ||||||
|  |   final client = ref.watch(apiClientProvider); | ||||||
|  |   final response = await client.get('/id/accounts/me/credits'); | ||||||
|  |   if (response.statusCode != 200) { | ||||||
|  |     throw Exception('Failed to load social credits'); | ||||||
|  |   } | ||||||
|  |   return response.data?.toDouble() ?? 0.0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @riverpod | ||||||
|  | class SocialCreditHistoryNotifier extends _$SocialCreditHistoryNotifier | ||||||
|  |     with CursorPagingNotifierMixin<SnSocialCreditRecord> { | ||||||
|  |   static const int _pageSize = 20; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Future<CursorPagingData<SnSocialCreditRecord>> build() => fetch(cursor: null); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Future<CursorPagingData<SnSocialCreditRecord>> fetch({ | ||||||
|  |     required String? cursor, | ||||||
|  |   }) async { | ||||||
|  |     final client = ref.read(apiClientProvider); | ||||||
|  |     final offset = cursor == null ? 0 : int.parse(cursor); | ||||||
|  |  | ||||||
|  |     final queryParams = {'offset': offset, 'take': _pageSize}; | ||||||
|  |  | ||||||
|  |     final response = await client.get( | ||||||
|  |       '/id/accounts/me/credits/history', | ||||||
|  |       queryParameters: queryParams, | ||||||
|  |     ); | ||||||
|  |     final total = int.parse(response.headers.value('X-Total') ?? '0'); | ||||||
|  |     final List<dynamic> data = response.data; | ||||||
|  |     final records = | ||||||
|  |         data.map((json) => SnSocialCreditRecord.fromJson(json)).toList(); | ||||||
|  |  | ||||||
|  |     final hasMore = offset + records.length < total; | ||||||
|  |     final nextCursor = hasMore ? (offset + records.length).toString() : null; | ||||||
|  |  | ||||||
|  |     return CursorPagingData( | ||||||
|  |       items: records, | ||||||
|  |       hasMore: hasMore, | ||||||
|  |       nextCursor: nextCursor, | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class SocialCreditsScreen extends HookConsumerWidget { | ||||||
|  |   const SocialCreditsScreen({super.key}); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|  |     final socialCredits = ref.watch(socialCreditsProvider); | ||||||
|  |  | ||||||
|  |     return AppScaffold( | ||||||
|  |       appBar: AppBar(title: Text('socialCredits').tr()), | ||||||
|  |       body: Column( | ||||||
|  |         children: [ | ||||||
|  |           Card( | ||||||
|  |             margin: EdgeInsets.only(left: 16, right: 16, top: 8), | ||||||
|  |             child: socialCredits | ||||||
|  |                 .when( | ||||||
|  |                   data: | ||||||
|  |                       (credits) => Stack( | ||||||
|  |                         children: [ | ||||||
|  |                           Column( | ||||||
|  |                             crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|  |                             children: [ | ||||||
|  |                               Text( | ||||||
|  |                                 credits < 100 | ||||||
|  |                                     ? 'socialCreditsLevelPoor'.tr() | ||||||
|  |                                     : credits < 150 | ||||||
|  |                                     ? 'socialCreditsLevelNormal'.tr() | ||||||
|  |                                     : credits < 200 | ||||||
|  |                                     ? 'socialCreditsLevelGood'.tr() | ||||||
|  |                                     : 'socialCreditsLevelExcellent'.tr(), | ||||||
|  |                               ).tr().bold().fontSize(20), | ||||||
|  |                               Text( | ||||||
|  |                                 '${credits.toStringAsFixed(2)} pts', | ||||||
|  |                               ).fontSize(14), | ||||||
|  |                               const Gap(8), | ||||||
|  |                               LinearProgressIndicator(value: credits / 200), | ||||||
|  |                             ], | ||||||
|  |                           ), | ||||||
|  |                           Positioned( | ||||||
|  |                             right: 0, | ||||||
|  |                             top: 0, | ||||||
|  |                             child: IconButton( | ||||||
|  |                               onPressed: () {}, | ||||||
|  |                               icon: const Icon(Symbols.info), | ||||||
|  |                               tooltip: 'socialCreditsDescription'.tr(), | ||||||
|  |                             ), | ||||||
|  |                           ), | ||||||
|  |                         ], | ||||||
|  |                       ), | ||||||
|  |                   error: (_, _) => Text('Error loading credits'), | ||||||
|  |                   loading: () => const LinearProgressIndicator(), | ||||||
|  |                 ) | ||||||
|  |                 .padding(horizontal: 20, vertical: 16), | ||||||
|  |           ), | ||||||
|  |           Expanded( | ||||||
|  |             child: PagingHelperView( | ||||||
|  |               provider: socialCreditHistoryNotifierProvider, | ||||||
|  |               futureRefreshable: socialCreditHistoryNotifierProvider.future, | ||||||
|  |               notifierRefreshable: socialCreditHistoryNotifierProvider.notifier, | ||||||
|  |               contentBuilder: | ||||||
|  |                   (data, widgetCount, endItemView) => ListView.builder( | ||||||
|  |                     padding: EdgeInsets.zero, | ||||||
|  |                     itemCount: widgetCount, | ||||||
|  |                     itemBuilder: (context, index) { | ||||||
|  |                       if (index == widgetCount - 1) { | ||||||
|  |                         return endItemView; | ||||||
|  |                       } | ||||||
|  |                       final record = data.items[index]; | ||||||
|  |                       return ListTile( | ||||||
|  |                         contentPadding: EdgeInsets.symmetric(horizontal: 24), | ||||||
|  |                         title: Text(record.reason), | ||||||
|  |                         subtitle: Text( | ||||||
|  |                           DateFormat.yMMMd().format(record.createdAt), | ||||||
|  |                         ), | ||||||
|  |                         trailing: Text( | ||||||
|  |                           record.delta > 0 | ||||||
|  |                               ? '+${record.delta}' | ||||||
|  |                               : '${record.delta}', | ||||||
|  |                           style: TextStyle( | ||||||
|  |                             color: record.delta > 0 ? Colors.green : Colors.red, | ||||||
|  |                           ), | ||||||
|  |                         ), | ||||||
|  |                       ); | ||||||
|  |                     }, | ||||||
|  |                   ), | ||||||
|  |             ), | ||||||
|  |           ), | ||||||
|  |         ], | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										49
									
								
								lib/screens/account/credits.g.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								lib/screens/account/credits.g.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,49 @@ | |||||||
|  | // GENERATED CODE - DO NOT MODIFY BY HAND | ||||||
|  |  | ||||||
|  | part of 'credits.dart'; | ||||||
|  |  | ||||||
|  | // ************************************************************************** | ||||||
|  | // RiverpodGenerator | ||||||
|  | // ************************************************************************** | ||||||
|  |  | ||||||
|  | String _$socialCreditsHash() => r'2599844e892127ee4d315caced5c10e4dbaea142'; | ||||||
|  |  | ||||||
|  | /// See also [socialCredits]. | ||||||
|  | @ProviderFor(socialCredits) | ||||||
|  | final socialCreditsProvider = AutoDisposeFutureProvider<double>.internal( | ||||||
|  |   socialCredits, | ||||||
|  |   name: r'socialCreditsProvider', | ||||||
|  |   debugGetCreateSourceHash: | ||||||
|  |       const bool.fromEnvironment('dart.vm.product') | ||||||
|  |           ? null | ||||||
|  |           : _$socialCreditsHash, | ||||||
|  |   dependencies: null, | ||||||
|  |   allTransitiveDependencies: null, | ||||||
|  | ); | ||||||
|  |  | ||||||
|  | @Deprecated('Will be removed in 3.0. Use Ref instead') | ||||||
|  | // ignore: unused_element | ||||||
|  | typedef SocialCreditsRef = AutoDisposeFutureProviderRef<double>; | ||||||
|  | String _$socialCreditHistoryNotifierHash() => | ||||||
|  |     r'950db020754160f835c64cedf3fa2175e61e4d64'; | ||||||
|  |  | ||||||
|  | /// See also [SocialCreditHistoryNotifier]. | ||||||
|  | @ProviderFor(SocialCreditHistoryNotifier) | ||||||
|  | final socialCreditHistoryNotifierProvider = AutoDisposeAsyncNotifierProvider< | ||||||
|  |   SocialCreditHistoryNotifier, | ||||||
|  |   CursorPagingData<SnSocialCreditRecord> | ||||||
|  | >.internal( | ||||||
|  |   SocialCreditHistoryNotifier.new, | ||||||
|  |   name: r'socialCreditHistoryNotifierProvider', | ||||||
|  |   debugGetCreateSourceHash: | ||||||
|  |       const bool.fromEnvironment('dart.vm.product') | ||||||
|  |           ? null | ||||||
|  |           : _$socialCreditHistoryNotifierHash, | ||||||
|  |   dependencies: null, | ||||||
|  |   allTransitiveDependencies: null, | ||||||
|  | ); | ||||||
|  |  | ||||||
|  | typedef _$SocialCreditHistoryNotifier = | ||||||
|  |     AutoDisposeAsyncNotifier<CursorPagingData<SnSocialCreditRecord>>; | ||||||
|  | // 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 | ||||||
| @@ -6,6 +6,7 @@ import 'package:flutter/material.dart'; | |||||||
| import 'package:gap/gap.dart'; | import 'package:gap/gap.dart'; | ||||||
| import 'package:google_fonts/google_fonts.dart'; | import 'package:google_fonts/google_fonts.dart'; | ||||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
|  | import 'package:island/models/account.dart'; | ||||||
| import 'package:island/models/wallet.dart'; | import 'package:island/models/wallet.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'; | ||||||
| @@ -19,6 +20,7 @@ import 'package:island/widgets/payment/payment_overlay.dart'; | |||||||
| import 'package:easy_localization/easy_localization.dart'; | import 'package:easy_localization/easy_localization.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:styled_widget/styled_widget.dart'; | import 'package:styled_widget/styled_widget.dart'; | ||||||
|  |  | ||||||
| part 'leveling.g.dart'; | part 'leveling.g.dart'; | ||||||
| @@ -35,13 +37,49 @@ Future<SnWalletSubscription?> accountStellarSubscription(Ref ref) async { | |||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @riverpod | ||||||
|  | class LevelingHistoryNotifier extends _$LevelingHistoryNotifier | ||||||
|  |     with CursorPagingNotifierMixin<SnExperienceRecord> { | ||||||
|  |   static const int _pageSize = 20; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Future<CursorPagingData<SnExperienceRecord>> build() => fetch(cursor: null); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Future<CursorPagingData<SnExperienceRecord>> fetch({ | ||||||
|  |     required String? cursor, | ||||||
|  |   }) async { | ||||||
|  |     final client = ref.read(apiClientProvider); | ||||||
|  |     final offset = cursor == null ? 0 : int.parse(cursor); | ||||||
|  |  | ||||||
|  |     final queryParams = {'offset': offset, 'take': _pageSize}; | ||||||
|  |  | ||||||
|  |     final response = await client.get( | ||||||
|  |       '/id/accounts/me/leveling', | ||||||
|  |       queryParameters: queryParams, | ||||||
|  |     ); | ||||||
|  |     final total = int.parse(response.headers.value('X-Total') ?? '0'); | ||||||
|  |     final List<dynamic> data = response.data; | ||||||
|  |     final records = | ||||||
|  |         data.map((json) => SnExperienceRecord.fromJson(json)).toList(); | ||||||
|  |  | ||||||
|  |     final hasMore = offset + records.length < total; | ||||||
|  |     final nextCursor = hasMore ? (offset + records.length).toString() : null; | ||||||
|  |  | ||||||
|  |     return CursorPagingData( | ||||||
|  |       items: records, | ||||||
|  |       hasMore: hasMore, | ||||||
|  |       nextCursor: nextCursor, | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
| class LevelingScreen extends HookConsumerWidget { | class LevelingScreen extends HookConsumerWidget { | ||||||
|   const LevelingScreen({super.key}); |   const LevelingScreen({super.key}); | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context, WidgetRef ref) { |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|     final user = ref.watch(userInfoProvider); |     final user = ref.watch(userInfoProvider); | ||||||
|     final stellarSubscription = ref.watch(accountStellarSubscriptionProvider); |  | ||||||
|  |  | ||||||
|     if (user.value == null) { |     if (user.value == null) { | ||||||
|       return AppScaffold( |       return AppScaffold( | ||||||
| @@ -50,47 +88,150 @@ class LevelingScreen extends HookConsumerWidget { | |||||||
|       ); |       ); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     final currentLevel = user.value!.profile.level; |     return DefaultTabController( | ||||||
|     final currentExp = user.value!.profile.experience; |       length: 2, | ||||||
|     final progress = user.value!.profile.levelingProgress; |       child: AppScaffold( | ||||||
|  |         appBar: AppBar( | ||||||
|  |           title: Text('levelingProgress'.tr()), | ||||||
|  |           bottom: TabBar( | ||||||
|  |             tabs: [ | ||||||
|  |               Tab(text: 'leveling'.tr()), | ||||||
|  |               Tab(text: 'stellarProgram'.tr()), | ||||||
|  |             ], | ||||||
|  |           ), | ||||||
|  |         ), | ||||||
|  |         body: TabBarView( | ||||||
|  |           children: [ | ||||||
|  |             _buildLevelingTab(context, ref, user.value!), | ||||||
|  |             _buildStellarProgramTab(context, ref), | ||||||
|  |           ], | ||||||
|  |         ), | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|     return AppScaffold( |   Widget _buildLevelingTab( | ||||||
|       appBar: AppBar(title: Text('levelingProgress'.tr())), |     BuildContext context, | ||||||
|       body: SingleChildScrollView( |     WidgetRef ref, | ||||||
|         padding: getTabbedPadding(context, horizontal: 20, vertical: 20), |     SnAccount user, | ||||||
|         child: Center( |   ) { | ||||||
|           child: ConstrainedBox( |     final currentLevel = user.profile.level; | ||||||
|             constraints: const BoxConstraints(maxWidth: 480), |     final currentExp = user.profile.experience; | ||||||
|             child: Column( |     final progress = user.profile.levelingProgress; | ||||||
|               crossAxisAlignment: CrossAxisAlignment.stretch, |  | ||||||
|               children: [ |  | ||||||
|                 // Current Progress Card |  | ||||||
|                 LevelingProgressCard( |  | ||||||
|                   level: currentLevel, |  | ||||||
|                   experience: currentExp, |  | ||||||
|                   progress: progress, |  | ||||||
|                 ), |  | ||||||
|                 const Gap(24), |  | ||||||
|  |  | ||||||
|                 // Level Stairs Graph |     return Center( | ||||||
|                 Text( |       child: Container( | ||||||
|                   'levelProgress'.tr(), |         padding: const EdgeInsets.symmetric(horizontal: 20), | ||||||
|                   style: Theme.of(context).textTheme.headlineSmall?.copyWith( |         constraints: const BoxConstraints(maxWidth: 480), | ||||||
|                     fontWeight: FontWeight.bold, |         child: CustomScrollView( | ||||||
|                   ), |           slivers: [ | ||||||
|                 ), |             const SliverGap(20), | ||||||
|                 const Gap(16), |  | ||||||
|  |  | ||||||
|                 // Stairs visualization with fixed height and horizontal scroll |             // Current Progress Card | ||||||
|                 _buildLevelStairs(context, currentLevel), |             SliverToBoxAdapter( | ||||||
|  |               child: LevelingProgressCard( | ||||||
|                 const Gap(24), |                 level: currentLevel, | ||||||
|  |                 experience: currentExp, | ||||||
|                 // Membership section |                 progress: progress, | ||||||
|                 _buildMembershipSection(context, ref, stellarSubscription), |               ), | ||||||
|                 const Gap(16), |  | ||||||
|               ], |  | ||||||
|             ), |             ), | ||||||
|  |             const SliverGap(24), | ||||||
|  |  | ||||||
|  |             // Level Stairs Graph | ||||||
|  |             SliverToBoxAdapter( | ||||||
|  |               child: Text( | ||||||
|  |                 'levelProgress'.tr(), | ||||||
|  |                 style: Theme.of(context).textTheme.headlineSmall?.copyWith( | ||||||
|  |                   fontWeight: FontWeight.bold, | ||||||
|  |                 ), | ||||||
|  |               ), | ||||||
|  |             ), | ||||||
|  |             const SliverGap(16), | ||||||
|  |  | ||||||
|  |             // Stairs visualization with fixed height and horizontal scroll | ||||||
|  |             SliverToBoxAdapter(child: _buildLevelStairs(context, currentLevel)), | ||||||
|  |             const SliverGap(24), | ||||||
|  |  | ||||||
|  |             // Leveling History | ||||||
|  |             SliverToBoxAdapter( | ||||||
|  |               child: Text( | ||||||
|  |                 'levelingHistory'.tr(), | ||||||
|  |                 style: Theme.of(context).textTheme.headlineSmall?.copyWith( | ||||||
|  |                   fontWeight: FontWeight.bold, | ||||||
|  |                 ), | ||||||
|  |               ), | ||||||
|  |             ), | ||||||
|  |             const SliverGap(8), | ||||||
|  |             PagingHelperSliverView( | ||||||
|  |               provider: levelingHistoryNotifierProvider, | ||||||
|  |               futureRefreshable: levelingHistoryNotifierProvider.future, | ||||||
|  |               notifierRefreshable: levelingHistoryNotifierProvider.notifier, | ||||||
|  |               contentBuilder: | ||||||
|  |                   (data, widgetCount, endItemView) => SliverList.builder( | ||||||
|  |                     itemCount: widgetCount, | ||||||
|  |                     itemBuilder: (context, index) { | ||||||
|  |                       if (index == widgetCount - 1) { | ||||||
|  |                         return endItemView; | ||||||
|  |                       } | ||||||
|  |                       final record = data.items[index]; | ||||||
|  |                       return ListTile( | ||||||
|  |                         title: Column( | ||||||
|  |                           mainAxisSize: MainAxisSize.min, | ||||||
|  |                           crossAxisAlignment: CrossAxisAlignment.stretch, | ||||||
|  |                           children: [ | ||||||
|  |                             Text(record.reason), | ||||||
|  |                             Row( | ||||||
|  |                               spacing: 4, | ||||||
|  |                               children: [ | ||||||
|  |                                 Text( | ||||||
|  |                                   record.createdAt.formatRelative(context), | ||||||
|  |                                 ).fontSize(13), | ||||||
|  |                                 Text('·').fontSize(13).bold(), | ||||||
|  |                                 Text( | ||||||
|  |                                   record.createdAt.formatSystem(), | ||||||
|  |                                 ).fontSize(13), | ||||||
|  |                               ], | ||||||
|  |                             ).opacity(0.8), | ||||||
|  |                           ], | ||||||
|  |                         ), | ||||||
|  |                         subtitle: Row( | ||||||
|  |                           spacing: 8, | ||||||
|  |                           children: [ | ||||||
|  |                             Text( | ||||||
|  |                               '${record.delta > 0 ? '+' : ''}${record.delta} EXP', | ||||||
|  |                             ), | ||||||
|  |                             if (record.bonusMultiplier != 1.0) | ||||||
|  |                               Text('x${record.bonusMultiplier}'), | ||||||
|  |                           ], | ||||||
|  |                         ), | ||||||
|  |                         minTileHeight: 56, | ||||||
|  |                         contentPadding: EdgeInsets.symmetric(horizontal: 4), | ||||||
|  |                       ); | ||||||
|  |                     }, | ||||||
|  |                   ), | ||||||
|  |             ), | ||||||
|  |  | ||||||
|  |             SliverGap(getTabbedPadding(context, vertical: 20).vertical), | ||||||
|  |           ], | ||||||
|  |         ), | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   Widget _buildStellarProgramTab(BuildContext context, WidgetRef ref) { | ||||||
|  |     final stellarSubscription = ref.watch(accountStellarSubscriptionProvider); | ||||||
|  |  | ||||||
|  |     return SingleChildScrollView( | ||||||
|  |       padding: getTabbedPadding(context, horizontal: 20, vertical: 20), | ||||||
|  |       child: Center( | ||||||
|  |         child: ConstrainedBox( | ||||||
|  |           constraints: const BoxConstraints(maxWidth: 480), | ||||||
|  |           child: Column( | ||||||
|  |             crossAxisAlignment: CrossAxisAlignment.stretch, | ||||||
|  |             children: [ | ||||||
|  |               _buildMembershipSection(context, ref, stellarSubscription), | ||||||
|  |               const Gap(16), | ||||||
|  |             ], | ||||||
|           ), |           ), | ||||||
|         ), |         ), | ||||||
|       ), |       ), | ||||||
|   | |||||||
| @@ -27,5 +27,26 @@ final accountStellarSubscriptionProvider = | |||||||
| // ignore: unused_element | // ignore: unused_element | ||||||
| typedef AccountStellarSubscriptionRef = | typedef AccountStellarSubscriptionRef = | ||||||
|     AutoDisposeFutureProviderRef<SnWalletSubscription?>; |     AutoDisposeFutureProviderRef<SnWalletSubscription?>; | ||||||
|  | String _$levelingHistoryNotifierHash() => | ||||||
|  |     r'e795f9b7911c9e50f15c095ea237cb0e87bf1e89'; | ||||||
|  |  | ||||||
|  | /// See also [LevelingHistoryNotifier]. | ||||||
|  | @ProviderFor(LevelingHistoryNotifier) | ||||||
|  | final levelingHistoryNotifierProvider = AutoDisposeAsyncNotifierProvider< | ||||||
|  |   LevelingHistoryNotifier, | ||||||
|  |   CursorPagingData<SnExperienceRecord> | ||||||
|  | >.internal( | ||||||
|  |   LevelingHistoryNotifier.new, | ||||||
|  |   name: r'levelingHistoryNotifierProvider', | ||||||
|  |   debugGetCreateSourceHash: | ||||||
|  |       const bool.fromEnvironment('dart.vm.product') | ||||||
|  |           ? null | ||||||
|  |           : _$levelingHistoryNotifierHash, | ||||||
|  |   dependencies: null, | ||||||
|  |   allTransitiveDependencies: null, | ||||||
|  | ); | ||||||
|  |  | ||||||
|  | typedef _$LevelingHistoryNotifier = | ||||||
|  |     AutoDisposeAsyncNotifier<CursorPagingData<SnExperienceRecord>>; | ||||||
| // 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,7 +6,7 @@ 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'; | ||||||
| import 'package:island/models/auth.dart'; | import 'package:island/models/auth.dart'; | ||||||
| import 'package:island/models/user.dart'; | import 'package:island/models/account.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/account/me/settings_auth_factors.dart'; | import 'package:island/screens/account/me/settings_auth_factors.dart'; | ||||||
| @@ -15,7 +15,7 @@ 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/services/responsive.dart'; | ||||||
| import 'package:island/widgets/account/account_session_sheet.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'; | ||||||
| import 'package:island/widgets/response.dart'; | import 'package:island/widgets/response.dart'; | ||||||
| @@ -23,7 +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'; | ||||||
| 
 | 
 | ||||||
| part 'settings.g.dart'; | part 'account_settings.g.dart'; | ||||||
| 
 | 
 | ||||||
| @riverpod | @riverpod | ||||||
| Future<List<SnAuthFactor>> authFactors(Ref ref) async { | Future<List<SnAuthFactor>> authFactors(Ref ref) async { | ||||||
| @@ -1,6 +1,6 @@ | |||||||
| // GENERATED CODE - DO NOT MODIFY BY HAND | // GENERATED CODE - DO NOT MODIFY BY HAND | ||||||
| 
 | 
 | ||||||
| part of 'settings.dart'; | part of 'account_settings.dart'; | ||||||
| 
 | 
 | ||||||
| // ************************************************************************** | // ************************************************************************** | ||||||
| // RiverpodGenerator | // RiverpodGenerator | ||||||
| @@ -1,3 +1,4 @@ | |||||||
|  | import 'package:collection/collection.dart'; | ||||||
| import 'package:croppy/croppy.dart' hide cropImage; | import 'package:croppy/croppy.dart' hide cropImage; | ||||||
| import 'package:dropdown_button2/dropdown_button2.dart'; | import 'package:dropdown_button2/dropdown_button2.dart'; | ||||||
| import 'package:easy_localization/easy_localization.dart'; | import 'package:easy_localization/easy_localization.dart'; | ||||||
| @@ -7,7 +8,7 @@ import 'package:gap/gap.dart'; | |||||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
| import 'package:image_picker/image_picker.dart'; | import 'package:image_picker/image_picker.dart'; | ||||||
| import 'package:island/models/file.dart'; | import 'package:island/models/file.dart'; | ||||||
| import 'package:island/models/user.dart'; | import 'package:island/models/account.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/pods/userinfo.dart'; | import 'package:island/pods/userinfo.dart'; | ||||||
| @@ -168,11 +169,18 @@ class UpdateProfileScreen extends HookConsumerWidget { | |||||||
|             'location': locationController.text, |             'location': locationController.text, | ||||||
|             'time_zone': timeZoneController.text, |             'time_zone': timeZoneController.text, | ||||||
|             'birthday': birthday.value?.toUtc().toIso8601String(), |             'birthday': birthday.value?.toUtc().toIso8601String(), | ||||||
|             'links': links.value, |             'links': | ||||||
|  |                 links.value | ||||||
|  |                     .where((e) => e.name.isNotEmpty && e.url.isNotEmpty) | ||||||
|  |                     .toList(), | ||||||
|           }, |           }, | ||||||
|         ); |         ); | ||||||
|         final userNotifier = ref.read(userInfoProvider.notifier); |         final userNotifier = ref.read(userInfoProvider.notifier); | ||||||
|         userNotifier.fetchUser(); |         userNotifier.fetchUser(); | ||||||
|  |         links.value = | ||||||
|  |             links.value | ||||||
|  |                 .where((e) => e.name.isNotEmpty && e.url.isNotEmpty) | ||||||
|  |                 .toList(); | ||||||
|       } catch (err) { |       } catch (err) { | ||||||
|         showErrorAlert(err); |         showErrorAlert(err); | ||||||
|       } finally { |       } finally { | ||||||
| @@ -568,6 +576,7 @@ class UpdateProfileScreen extends HookConsumerWidget { | |||||||
|                     children: [ |                     children: [ | ||||||
|                       for (var i = 0; i < links.value.length; i++) |                       for (var i = 0; i < links.value.length; i++) | ||||||
|                         Row( |                         Row( | ||||||
|  |                           key: ValueKey(links.value[i].hashCode), | ||||||
|                           crossAxisAlignment: CrossAxisAlignment.end, |                           crossAxisAlignment: CrossAxisAlignment.end, | ||||||
|                           children: [ |                           children: [ | ||||||
|                             Expanded( |                             Expanded( | ||||||
| @@ -610,8 +619,10 @@ class UpdateProfileScreen extends HookConsumerWidget { | |||||||
|                             IconButton( |                             IconButton( | ||||||
|                               icon: const Icon(Symbols.delete), |                               icon: const Icon(Symbols.delete), | ||||||
|                               onPressed: () { |                               onPressed: () { | ||||||
|                                 links.value = List.from(links.value) |                                 links.value = | ||||||
|                                   ..removeAt(i); |                                     links.value | ||||||
|  |                                         .whereIndexed((idx, _) => idx != i) | ||||||
|  |                                         .toList(); | ||||||
|                               }, |                               }, | ||||||
|                             ), |                             ), | ||||||
|                           ], |                           ], | ||||||
| @@ -6,7 +6,7 @@ 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/network.dart'; | import 'package:island/pods/network.dart'; | ||||||
| import 'package:island/screens/account/me/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/services/text.dart'; | ||||||
| import 'package:island/services/time.dart'; | import 'package:island/services/time.dart'; | ||||||
| @@ -166,7 +166,7 @@ class AccountConnectionNewSheet extends HookConsumerWidget { | |||||||
|               webAuthenticationOptions: WebAuthenticationOptions( |               webAuthenticationOptions: WebAuthenticationOptions( | ||||||
|                 clientId: 'dev.solsynth.solarpass', |                 clientId: 'dev.solsynth.solarpass', | ||||||
|                 redirectUri: Uri.parse( |                 redirectUri: Uri.parse( | ||||||
|                   'https://nt.solian.app/auth/callback/apple', |                   'https://id.solian.app/auth/callback/apple', | ||||||
|                 ), |                 ), | ||||||
|               ), |               ), | ||||||
|             ); |             ); | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ 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:gap/gap.dart'; | ||||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
| import 'package:island/models/user.dart'; | import 'package:island/models/account.dart'; | ||||||
| import 'package:island/pods/network.dart'; | import 'package:island/pods/network.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'; | ||||||
|   | |||||||
| @@ -2,12 +2,13 @@ import 'package:dio/dio.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_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'; | ||||||
| import 'package:island/models/chat.dart'; | import 'package:island/models/chat.dart'; | ||||||
| import 'package:island/models/relationship.dart'; | import 'package:island/models/relationship.dart'; | ||||||
| import 'package:island/models/user.dart'; | import 'package:island/models/account.dart'; | ||||||
| import 'package:island/pods/config.dart'; | import 'package:island/pods/config.dart'; | ||||||
| import 'package:island/pods/event_calendar.dart'; | import 'package:island/pods/event_calendar.dart'; | ||||||
| import 'package:island/pods/network.dart'; | import 'package:island/pods/network.dart'; | ||||||
| @@ -262,6 +263,10 @@ class AccountProfileScreen extends HookConsumerWidget { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     final user = ref.watch(userInfoProvider); |     final user = ref.watch(userInfoProvider); | ||||||
|  |     final isCurrentUser = useMemoized( | ||||||
|  |       () => user.value?.id == account.value?.id, | ||||||
|  |       [user, account], | ||||||
|  |     ); | ||||||
|  |  | ||||||
|     Widget accountBasicInfo(SnAccount data) => Padding( |     Widget accountBasicInfo(SnAccount data) => Padding( | ||||||
|       padding: const EdgeInsets.fromLTRB(24, 24, 24, 8), |       padding: const EdgeInsets.fromLTRB(24, 24, 24, 8), | ||||||
| @@ -589,7 +594,7 @@ class AccountProfileScreen extends HookConsumerWidget { | |||||||
|                           child: CustomScrollView( |                           child: CustomScrollView( | ||||||
|                             slivers: [ |                             slivers: [ | ||||||
|                               SliverGap(24), |                               SliverGap(24), | ||||||
|                               if (user.value != null) |                               if (user.value != null && !isCurrentUser) | ||||||
|                                 SliverToBoxAdapter(child: accountAction(data)), |                                 SliverToBoxAdapter(child: accountAction(data)), | ||||||
|                               SliverToBoxAdapter( |                               SliverToBoxAdapter( | ||||||
|                                 child: Card( |                                 child: Card( | ||||||
| @@ -686,7 +691,7 @@ class AccountProfileScreen extends HookConsumerWidget { | |||||||
|                             data, |                             data, | ||||||
|                           ).padding(horizontal: 4), |                           ).padding(horizontal: 4), | ||||||
|                         ), |                         ), | ||||||
|                         if (user.value != null) |                         if (user.value != null && !isCurrentUser) | ||||||
|                           SliverToBoxAdapter( |                           SliverToBoxAdapter( | ||||||
|                             child: accountAction(data).padding(horizontal: 4), |                             child: accountAction(data).padding(horizontal: 4), | ||||||
|                           ), |                           ), | ||||||
|   | |||||||
| @@ -6,7 +6,7 @@ import 'package:flutter_hooks/flutter_hooks.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/pods/network.dart'; | import 'package:island/pods/network.dart'; | ||||||
| import 'package:island/screens/account/me/update.dart'; | import 'package:island/screens/account/me/profile_update.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'; | ||||||
| import 'package:material_symbols_icons/symbols.dart'; | import 'package:material_symbols_icons/symbols.dart'; | ||||||
|   | |||||||
| @@ -42,6 +42,22 @@ final Map<int, (String, String, IconData)> kFactorTypes = { | |||||||
|   4: ('authFactorPin', 'authFactorPinDescription', Symbols.nest_secure_alarm), |   4: ('authFactorPin', 'authFactorPinDescription', Symbols.nest_secure_alarm), | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | Future<String?> getDeviceName() async { | ||||||
|  |   if (kIsWeb) return null; | ||||||
|  |   String? name; | ||||||
|  |   if (Platform.isIOS) { | ||||||
|  |     final deviceInfo = await DeviceInfoPlugin().iosInfo; | ||||||
|  |     name = deviceInfo.name; | ||||||
|  |   } else if (Platform.isAndroid) { | ||||||
|  |     final deviceInfo = await DeviceInfoPlugin().androidInfo; | ||||||
|  |     name = deviceInfo.name; | ||||||
|  |   } else if (Platform.isWindows) { | ||||||
|  |     final deviceInfo = await DeviceInfoPlugin().windowsInfo; | ||||||
|  |     name = deviceInfo.computerName; | ||||||
|  |   } | ||||||
|  |   return name; | ||||||
|  | } | ||||||
|  |  | ||||||
| class LoginScreen extends HookConsumerWidget { | class LoginScreen extends HookConsumerWidget { | ||||||
|   const LoginScreen({super.key}); |   const LoginScreen({super.key}); | ||||||
|  |  | ||||||
| @@ -198,28 +214,6 @@ class _LoginCheckScreen extends HookConsumerWidget { | |||||||
|         wsNotifier.connect(); |         wsNotifier.connect(); | ||||||
|         if (context.mounted) Navigator.pop(context, true); |         if (context.mounted) Navigator.pop(context, true); | ||||||
|       }); |       }); | ||||||
|  |  | ||||||
|       // Update the sessions' device name is available |  | ||||||
|       if (!kIsWeb) { |  | ||||||
|         String? name; |  | ||||||
|         if (Platform.isIOS) { |  | ||||||
|           final deviceInfo = await DeviceInfoPlugin().iosInfo; |  | ||||||
|           name = deviceInfo.name; |  | ||||||
|         } else if (Platform.isAndroid) { |  | ||||||
|           final deviceInfo = await DeviceInfoPlugin().androidInfo; |  | ||||||
|           name = deviceInfo.name; |  | ||||||
|         } else if (Platform.isWindows) { |  | ||||||
|           final deviceInfo = await DeviceInfoPlugin().windowsInfo; |  | ||||||
|           name = deviceInfo.computerName; |  | ||||||
|         } |  | ||||||
|         if (name != null) { |  | ||||||
|           final client = ref.watch(apiClientProvider); |  | ||||||
|           await client.patch( |  | ||||||
|             '/id/accounts/me/sessions/current/label', |  | ||||||
|             data: jsonEncode(name), |  | ||||||
|           ); |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     useEffect(() { |     useEffect(() { | ||||||
| @@ -578,6 +572,7 @@ class _LoginLookupScreen extends HookConsumerWidget { | |||||||
|           data: { |           data: { | ||||||
|             'account': uname, |             'account': uname, | ||||||
|             'device_id': await getUdid(), |             'device_id': await getUdid(), | ||||||
|  |             'device_name': await getDeviceName(), | ||||||
|             'platform': |             'platform': | ||||||
|                 kIsWeb |                 kIsWeb | ||||||
|                     ? 1 |                     ? 1 | ||||||
| @@ -628,6 +623,7 @@ class _LoginLookupScreen extends HookConsumerWidget { | |||||||
|             'identity_token': credential.identityToken!, |             'identity_token': credential.identityToken!, | ||||||
|             'authorization_code': credential.authorizationCode, |             'authorization_code': credential.authorizationCode, | ||||||
|             'device_id': await getUdid(), |             'device_id': await getUdid(), | ||||||
|  |             'device_name': await getDeviceName(), | ||||||
|           }, |           }, | ||||||
|         ); |         ); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -23,7 +23,7 @@ import 'package:island/widgets/alert.dart'; | |||||||
| import 'package:island/widgets/app_scaffold.dart'; | import 'package:island/widgets/app_scaffold.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'; | ||||||
| import 'package:island/widgets/realms/selection_dropdown.dart'; | import 'package:island/widgets/realm/realm_selection_dropdown.dart'; | ||||||
| import 'package:island/widgets/response.dart'; | import 'package:island/widgets/response.dart'; | ||||||
| import 'package:island/screens/tabs.dart'; | import 'package:island/screens/tabs.dart'; | ||||||
| import 'package:material_symbols_icons/symbols.dart'; | import 'package:material_symbols_icons/symbols.dart'; | ||||||
| @@ -79,31 +79,38 @@ class ChatRoomListTile extends HookConsumerWidget { | |||||||
|                     color: Theme.of(context).colorScheme.primary, |                     color: Theme.of(context).colorScheme.primary, | ||||||
|                   ), |                   ), | ||||||
|                 ), |                 ), | ||||||
|               Row( |               if (data.lastMessage == null) | ||||||
|                 children: [ |                 Text(room.description ?? 'descriptionNone'.tr(), maxLines: 1) | ||||||
|                   Text( |               else | ||||||
|                     '${data.lastMessage.sender.account.name}: ', |                 Row( | ||||||
|                     style: Theme.of(context).textTheme.bodySmall, |                   spacing: 4, | ||||||
|                   ), |                   children: [ | ||||||
|                   Expanded( |                     Badge( | ||||||
|                     child: Text( |                       label: Text(data.lastMessage!.sender.account.nick), | ||||||
|                       (data.lastMessage.content?.isNotEmpty ?? false) |                       textColor: Theme.of(context).colorScheme.onPrimary, | ||||||
|                           ? data.lastMessage.content! |                       backgroundColor: Theme.of(context).colorScheme.primary, | ||||||
|                           : 'messageNone'.tr(), |  | ||||||
|                       maxLines: 1, |  | ||||||
|                       overflow: TextOverflow.ellipsis, |  | ||||||
|                       style: Theme.of(context).textTheme.bodySmall, |  | ||||||
|                     ), |                     ), | ||||||
|                   ), |                     Expanded( | ||||||
|                   Align( |                       child: Text( | ||||||
|                     alignment: Alignment.centerRight, |                         (data.lastMessage!.content?.isNotEmpty ?? false) | ||||||
|                     child: Text( |                             ? data.lastMessage!.content! | ||||||
|                       RelativeTime(context).format(data.lastMessage.createdAt), |                             : 'messageNone'.tr(), | ||||||
|                       style: Theme.of(context).textTheme.bodySmall, |                         maxLines: 1, | ||||||
|  |                         overflow: TextOverflow.ellipsis, | ||||||
|  |                         style: Theme.of(context).textTheme.bodySmall, | ||||||
|  |                       ), | ||||||
|                     ), |                     ), | ||||||
|                   ), |                     Align( | ||||||
|                 ], |                       alignment: Alignment.centerRight, | ||||||
|               ), |                       child: Text( | ||||||
|  |                         RelativeTime( | ||||||
|  |                           context, | ||||||
|  |                         ).format(data.lastMessage!.createdAt), | ||||||
|  |                         style: Theme.of(context).textTheme.bodySmall, | ||||||
|  |                       ), | ||||||
|  |                     ), | ||||||
|  |                   ], | ||||||
|  |                 ), | ||||||
|             ], |             ], | ||||||
|           ); |           ); | ||||||
|         }, |         }, | ||||||
|   | |||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -6,7 +6,7 @@ part of 'room.dart'; | |||||||
| // RiverpodGenerator | // RiverpodGenerator | ||||||
| // ************************************************************************** | // ************************************************************************** | ||||||
|  |  | ||||||
| String _$messagesNotifierHash() => r'afc4d43f4948ec571118cef0321838a6cefc89c0'; | String _$messagesNotifierHash() => r'32afe6ea24086d869cc47bd3389c8fd734409ca0'; | ||||||
|  |  | ||||||
| /// Copied from Dart SDK | /// Copied from Dart SDK | ||||||
| class _SystemHash { | class _SystemHash { | ||||||
|   | |||||||
| @@ -10,6 +10,7 @@ import 'package:island/models/chat.dart'; | |||||||
| import 'package:island/pods/network.dart'; | import 'package:island/pods/network.dart'; | ||||||
| import 'package:island/screens/chat/chat.dart'; | import 'package:island/screens/chat/chat.dart'; | ||||||
| import 'package:island/widgets/account/account_picker.dart'; | import 'package:island/widgets/account/account_picker.dart'; | ||||||
|  | import 'package:island/widgets/account/status.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'; | ||||||
| import 'package:island/widgets/content/cloud_files.dart'; | import 'package:island/widgets/content/cloud_files.dart'; | ||||||
| @@ -544,7 +545,7 @@ class ChatMemberListNotifier extends _$ChatMemberListNotifier | |||||||
|     final apiClient = ref.watch(apiClientProvider); |     final apiClient = ref.watch(apiClientProvider); | ||||||
|     final response = await apiClient.get( |     final response = await apiClient.get( | ||||||
|       '/sphere/chat/$roomId/members', |       '/sphere/chat/$roomId/members', | ||||||
|       queryParameters: {'offset': offset, 'take': take}, |       queryParameters: {'offset': offset, 'take': take, 'withStatus': true}, | ||||||
|     ); |     ); | ||||||
|  |  | ||||||
|     final total = int.parse(response.headers.value('X-Total') ?? '0'); |     final total = int.parse(response.headers.value('X-Total') ?? '0'); | ||||||
| @@ -672,6 +673,8 @@ class _ChatMemberListSheet extends HookConsumerWidget { | |||||||
|                         spacing: 6, |                         spacing: 6, | ||||||
|                         children: [ |                         children: [ | ||||||
|                           Flexible(child: Text(member.account.nick)), |                           Flexible(child: Text(member.account.nick)), | ||||||
|  |                           if (member.status != null) | ||||||
|  |                             AccountStatusLabel(status: member.status!), | ||||||
|                           if (member.joinedAt == null) |                           if (member.joinedAt == null) | ||||||
|                             const Icon(Symbols.pending_actions, size: 20), |                             const Icon(Symbols.pending_actions, size: 20), | ||||||
|                         ], |                         ], | ||||||
|   | |||||||
| @@ -7,7 +7,7 @@ part of 'room_detail.dart'; | |||||||
| // ************************************************************************** | // ************************************************************************** | ||||||
|  |  | ||||||
| String _$chatMemberListNotifierHash() => | String _$chatMemberListNotifierHash() => | ||||||
|     r'c8fbf4b95df6dae24b1ba21063e9a43351832974'; |     r'3ea30150278523e9f6b23f9200ea9a9fbae9c973'; | ||||||
|  |  | ||||||
| /// Copied from Dart SDK | /// Copied from Dart SDK | ||||||
| class _SystemHash { | class _SystemHash { | ||||||
|   | |||||||
| @@ -212,30 +212,6 @@ class CreatorHubScreen extends HookConsumerWidget { | |||||||
|         leading: !isWide ? const PageBackButton() : null, |         leading: !isWide ? const PageBackButton() : null, | ||||||
|         title: Text('creatorHub').tr(), |         title: Text('creatorHub').tr(), | ||||||
|         actions: [ |         actions: [ | ||||||
|           IconButton( |  | ||||||
|             icon: Badge( |  | ||||||
|               label: Text( |  | ||||||
|                 publisherInvites.when( |  | ||||||
|                   data: (invites) => invites.length.toString(), |  | ||||||
|                   error: (_, _) => '0', |  | ||||||
|                   loading: () => '0', |  | ||||||
|                 ), |  | ||||||
|               ), |  | ||||||
|               isLabelVisible: publisherInvites.when( |  | ||||||
|                 data: (invites) => invites.isNotEmpty, |  | ||||||
|                 error: (_, _) => false, |  | ||||||
|                 loading: () => false, |  | ||||||
|               ), |  | ||||||
|               child: const Icon(Symbols.email), |  | ||||||
|             ), |  | ||||||
|             onPressed: () { |  | ||||||
|               showModalBottomSheet( |  | ||||||
|                 context: context, |  | ||||||
|                 isScrollControlled: true, |  | ||||||
|                 builder: (_) => const _PublisherInviteSheet(), |  | ||||||
|               ); |  | ||||||
|             }, |  | ||||||
|           ), |  | ||||||
|           DropdownButtonHideUnderline( |           DropdownButtonHideUnderline( | ||||||
|             child: DropdownButton2<SnPublisher>( |             child: DropdownButton2<SnPublisher>( | ||||||
|               alignment: Alignment.centerRight, |               alignment: Alignment.centerRight, | ||||||
| @@ -323,6 +299,23 @@ class CreatorHubScreen extends HookConsumerWidget { | |||||||
|                                 ), |                                 ), | ||||||
|                               ) ?? |                               ) ?? | ||||||
|                               []), |                               []), | ||||||
|  |                           ListTile( | ||||||
|  |                             leading: const CircleAvatar( | ||||||
|  |                               child: Icon(Symbols.mail), | ||||||
|  |                             ), | ||||||
|  |                             title: Text('publisherCollabInvitation').tr(), | ||||||
|  |                             subtitle: Text( | ||||||
|  |                               'publisherCollabInvitationCount', | ||||||
|  |                             ).plural(publisherInvites.value?.length ?? 0), | ||||||
|  |                             trailing: const Icon(Symbols.chevron_right), | ||||||
|  |                             onTap: () { | ||||||
|  |                               showModalBottomSheet( | ||||||
|  |                                 context: context, | ||||||
|  |                                 isScrollControlled: true, | ||||||
|  |                                 builder: (_) => const _PublisherInviteSheet(), | ||||||
|  |                               ); | ||||||
|  |                             }, | ||||||
|  |                           ), | ||||||
|                           ListTile( |                           ListTile( | ||||||
|                             leading: const CircleAvatar( |                             leading: const CircleAvatar( | ||||||
|                               child: Icon(Symbols.add), |                               child: Icon(Symbols.add), | ||||||
|   | |||||||
| @@ -14,17 +14,19 @@ part 'poll_list.g.dart'; | |||||||
|  |  | ||||||
| @riverpod | @riverpod | ||||||
| class PollListNotifier extends _$PollListNotifier | class PollListNotifier extends _$PollListNotifier | ||||||
|     with CursorPagingNotifierMixin<SnPoll> { |     with CursorPagingNotifierMixin<SnPollWithStats> { | ||||||
|   static const int _pageSize = 20; |   static const int _pageSize = 20; | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   Future<CursorPagingData<SnPoll>> build(String? pubName) { |   Future<CursorPagingData<SnPollWithStats>> build(String? pubName) { | ||||||
|     // immediately load first page |     // immediately load first page | ||||||
|     return fetch(cursor: null); |     return fetch(cursor: null); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   Future<CursorPagingData<SnPoll>> fetch({required String? cursor}) async { |   Future<CursorPagingData<SnPollWithStats>> fetch({ | ||||||
|  |     required String? cursor, | ||||||
|  |   }) async { | ||||||
|     final client = ref.read(apiClientProvider); |     final client = ref.read(apiClientProvider); | ||||||
|     final offset = cursor == null ? 0 : int.parse(cursor); |     final offset = cursor == null ? 0 : int.parse(cursor); | ||||||
|  |  | ||||||
| @@ -42,7 +44,7 @@ class PollListNotifier extends _$PollListNotifier | |||||||
|     ); |     ); | ||||||
|     final total = int.parse(response.headers.value('X-Total') ?? '0'); |     final total = int.parse(response.headers.value('X-Total') ?? '0'); | ||||||
|     final List<dynamic> data = response.data; |     final List<dynamic> data = response.data; | ||||||
|     final items = data.map((json) => SnPoll.fromJson(json)).toList(); |     final items = data.map((json) => SnPollWithStats.fromJson(json)).toList(); | ||||||
|  |  | ||||||
|     final hasMore = offset + items.length < total; |     final hasMore = offset + items.length < total; | ||||||
|     final nextCursor = hasMore ? (offset + items.length).toString() : null; |     final nextCursor = hasMore ? (offset + items.length).toString() : null; | ||||||
| @@ -55,6 +57,13 @@ class PollListNotifier extends _$PollListNotifier | |||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @riverpod | ||||||
|  | Future<SnPollWithStats> pollWithStats(Ref ref, String id) async { | ||||||
|  |   final apiClient = ref.watch(apiClientProvider); | ||||||
|  |   final resp = await apiClient.get('/sphere/polls/$id'); | ||||||
|  |   return SnPollWithStats.fromJson(resp.data); | ||||||
|  | } | ||||||
|  |  | ||||||
| class CreatorPollListScreen extends HookConsumerWidget { | class CreatorPollListScreen extends HookConsumerWidget { | ||||||
|   const CreatorPollListScreen({super.key, required this.pubName}); |   const CreatorPollListScreen({super.key, required this.pubName}); | ||||||
|  |  | ||||||
| @@ -64,7 +73,7 @@ class CreatorPollListScreen extends HookConsumerWidget { | |||||||
|     final result = await GoRouter.of( |     final result = await GoRouter.of( | ||||||
|       context, |       context, | ||||||
|     ).pushNamed('creatorPollNew', pathParameters: {'name': pubName}); |     ).pushNamed('creatorPollNew', pathParameters: {'name': pubName}); | ||||||
|     if (result is SnPoll && context.mounted) { |     if (result is SnPollWithStats && context.mounted) { | ||||||
|       Navigator.of(context).maybePop(result); |       Navigator.of(context).maybePop(result); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| @@ -92,8 +101,11 @@ class CreatorPollListScreen extends HookConsumerWidget { | |||||||
|                       if (index == widgetCount - 1) { |                       if (index == widgetCount - 1) { | ||||||
|                         return endItemView; |                         return endItemView; | ||||||
|                       } |                       } | ||||||
|                       final poll = data.items[index]; |                       final pollWithStats = data.items[index]; | ||||||
|                       return _CreatorPollItem(poll: poll, pubName: pubName); |                       return _CreatorPollItem( | ||||||
|  |                         pollWithStats: pollWithStats, | ||||||
|  |                         pubName: pubName, | ||||||
|  |                       ); | ||||||
|                     }, |                     }, | ||||||
|                   ), |                   ), | ||||||
|             ), |             ), | ||||||
| @@ -106,14 +118,14 @@ class CreatorPollListScreen extends HookConsumerWidget { | |||||||
|  |  | ||||||
| class _CreatorPollItem extends StatelessWidget { | class _CreatorPollItem extends StatelessWidget { | ||||||
|   final String pubName; |   final String pubName; | ||||||
|   const _CreatorPollItem({required this.poll, required this.pubName}); |   const _CreatorPollItem({required this.pollWithStats, required this.pubName}); | ||||||
|  |  | ||||||
|   final SnPoll poll; |   final SnPollWithStats pollWithStats; | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
|     final theme = Theme.of(context); |     final theme = Theme.of(context); | ||||||
|     final ended = poll.endedAt; |     final ended = pollWithStats.endedAt; | ||||||
|     final endedText = |     final endedText = | ||||||
|         ended == null |         ended == null | ||||||
|             ? 'No end' |             ? 'No end' | ||||||
| @@ -123,15 +135,16 @@ class _CreatorPollItem extends StatelessWidget { | |||||||
|       margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), |       margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), | ||||||
|       clipBehavior: Clip.antiAlias, |       clipBehavior: Clip.antiAlias, | ||||||
|       child: ListTile( |       child: ListTile( | ||||||
|         title: Text(poll.title ?? 'Untitled poll'), |         title: Text(pollWithStats.title ?? 'Untitled poll'), | ||||||
|         subtitle: Column( |         subtitle: Column( | ||||||
|           crossAxisAlignment: CrossAxisAlignment.start, |           crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|           children: [ |           children: [ | ||||||
|             if (poll.description != null && poll.description!.isNotEmpty) |             if (pollWithStats.description != null && | ||||||
|  |                 pollWithStats.description!.isNotEmpty) | ||||||
|               Padding( |               Padding( | ||||||
|                 padding: const EdgeInsets.only(top: 4), |                 padding: const EdgeInsets.only(top: 4), | ||||||
|                 child: Text( |                 child: Text( | ||||||
|                   poll.description!, |                   pollWithStats.description!, | ||||||
|                   maxLines: 2, |                   maxLines: 2, | ||||||
|                   overflow: TextOverflow.ellipsis, |                   overflow: TextOverflow.ellipsis, | ||||||
|                 ), |                 ), | ||||||
| @@ -139,7 +152,7 @@ class _CreatorPollItem extends StatelessWidget { | |||||||
|             Padding( |             Padding( | ||||||
|               padding: const EdgeInsets.only(top: 4), |               padding: const EdgeInsets.only(top: 4), | ||||||
|               child: Text( |               child: Text( | ||||||
|                 'Questions: ${poll.questions.length} · Ends: $endedText', |                 'Questions: ${pollWithStats.questions.length} · Ends: $endedText', | ||||||
|                 style: theme.textTheme.bodySmall, |                 style: theme.textTheme.bodySmall, | ||||||
|               ), |               ), | ||||||
|             ), |             ), | ||||||
| @@ -159,7 +172,7 @@ class _CreatorPollItem extends StatelessWidget { | |||||||
|                   onTap: () { |                   onTap: () { | ||||||
|                     GoRouter.of(context).pushNamed( |                     GoRouter.of(context).pushNamed( | ||||||
|                       'creatorPollEdit', |                       'creatorPollEdit', | ||||||
|                       pathParameters: {'name': pubName, 'id': poll.id}, |                       pathParameters: {'name': pubName, 'id': pollWithStats.id}, | ||||||
|                     ); |                     ); | ||||||
|                   }, |                   }, | ||||||
|                 ), |                 ), | ||||||
| @@ -170,8 +183,7 @@ class _CreatorPollItem extends StatelessWidget { | |||||||
|             context: context, |             context: context, | ||||||
|             useRootNavigator: true, |             useRootNavigator: true, | ||||||
|             isScrollControlled: true, |             isScrollControlled: true, | ||||||
|             builder: |             builder: (context) => PollFeedbackSheet(pollId: pollWithStats.id), | ||||||
|                 (context) => PollFeedbackSheet(pollId: poll.id, poll: poll), |  | ||||||
|           ); |           ); | ||||||
|         }, |         }, | ||||||
|       ), |       ), | ||||||
|   | |||||||
| @@ -6,7 +6,7 @@ part of 'poll_list.dart'; | |||||||
| // RiverpodGenerator | // RiverpodGenerator | ||||||
| // ************************************************************************** | // ************************************************************************** | ||||||
|  |  | ||||||
| String _$pollListNotifierHash() => r'd3da24ff6bbb8f35b06d57fc41625dc0312508e4'; | String _$pollWithStatsHash() => r'6bb910046ce1e09368f9922dbec52fdc2cc86740'; | ||||||
|  |  | ||||||
| /// Copied from Dart SDK | /// Copied from Dart SDK | ||||||
| class _SystemHash { | class _SystemHash { | ||||||
| @@ -29,11 +29,133 @@ class _SystemHash { | |||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /// See also [pollWithStats]. | ||||||
|  | @ProviderFor(pollWithStats) | ||||||
|  | const pollWithStatsProvider = PollWithStatsFamily(); | ||||||
|  |  | ||||||
|  | /// See also [pollWithStats]. | ||||||
|  | class PollWithStatsFamily extends Family<AsyncValue<SnPollWithStats>> { | ||||||
|  |   /// See also [pollWithStats]. | ||||||
|  |   const PollWithStatsFamily(); | ||||||
|  |  | ||||||
|  |   /// See also [pollWithStats]. | ||||||
|  |   PollWithStatsProvider call(String id) { | ||||||
|  |     return PollWithStatsProvider(id); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   PollWithStatsProvider getProviderOverride( | ||||||
|  |     covariant PollWithStatsProvider 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'pollWithStatsProvider'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// See also [pollWithStats]. | ||||||
|  | class PollWithStatsProvider extends AutoDisposeFutureProvider<SnPollWithStats> { | ||||||
|  |   /// See also [pollWithStats]. | ||||||
|  |   PollWithStatsProvider(String id) | ||||||
|  |     : this._internal( | ||||||
|  |         (ref) => pollWithStats(ref as PollWithStatsRef, id), | ||||||
|  |         from: pollWithStatsProvider, | ||||||
|  |         name: r'pollWithStatsProvider', | ||||||
|  |         debugGetCreateSourceHash: | ||||||
|  |             const bool.fromEnvironment('dart.vm.product') | ||||||
|  |                 ? null | ||||||
|  |                 : _$pollWithStatsHash, | ||||||
|  |         dependencies: PollWithStatsFamily._dependencies, | ||||||
|  |         allTransitiveDependencies: | ||||||
|  |             PollWithStatsFamily._allTransitiveDependencies, | ||||||
|  |         id: id, | ||||||
|  |       ); | ||||||
|  |  | ||||||
|  |   PollWithStatsProvider._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<SnPollWithStats> Function(PollWithStatsRef provider) create, | ||||||
|  |   ) { | ||||||
|  |     return ProviderOverride( | ||||||
|  |       origin: this, | ||||||
|  |       override: PollWithStatsProvider._internal( | ||||||
|  |         (ref) => create(ref as PollWithStatsRef), | ||||||
|  |         from: from, | ||||||
|  |         name: null, | ||||||
|  |         dependencies: null, | ||||||
|  |         allTransitiveDependencies: null, | ||||||
|  |         debugGetCreateSourceHash: null, | ||||||
|  |         id: id, | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   AutoDisposeFutureProviderElement<SnPollWithStats> createElement() { | ||||||
|  |     return _PollWithStatsProviderElement(this); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   bool operator ==(Object other) { | ||||||
|  |     return other is PollWithStatsProvider && 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 PollWithStatsRef on AutoDisposeFutureProviderRef<SnPollWithStats> { | ||||||
|  |   /// The parameter `id` of this provider. | ||||||
|  |   String get id; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class _PollWithStatsProviderElement | ||||||
|  |     extends AutoDisposeFutureProviderElement<SnPollWithStats> | ||||||
|  |     with PollWithStatsRef { | ||||||
|  |   _PollWithStatsProviderElement(super.provider); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   String get id => (origin as PollWithStatsProvider).id; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | String _$pollListNotifierHash() => r'd5b822e737788be8982f5cb3b501d460441930c1'; | ||||||
|  |  | ||||||
| abstract class _$PollListNotifier | abstract class _$PollListNotifier | ||||||
|     extends BuildlessAutoDisposeAsyncNotifier<CursorPagingData<SnPoll>> { |     extends | ||||||
|  |         BuildlessAutoDisposeAsyncNotifier<CursorPagingData<SnPollWithStats>> { | ||||||
|   late final String? pubName; |   late final String? pubName; | ||||||
|  |  | ||||||
|   FutureOr<CursorPagingData<SnPoll>> build(String? pubName); |   FutureOr<CursorPagingData<SnPollWithStats>> build(String? pubName); | ||||||
| } | } | ||||||
|  |  | ||||||
| /// See also [PollListNotifier]. | /// See also [PollListNotifier]. | ||||||
| @@ -42,7 +164,7 @@ const pollListNotifierProvider = PollListNotifierFamily(); | |||||||
|  |  | ||||||
| /// See also [PollListNotifier]. | /// See also [PollListNotifier]. | ||||||
| class PollListNotifierFamily | class PollListNotifierFamily | ||||||
|     extends Family<AsyncValue<CursorPagingData<SnPoll>>> { |     extends Family<AsyncValue<CursorPagingData<SnPollWithStats>>> { | ||||||
|   /// See also [PollListNotifier]. |   /// See also [PollListNotifier]. | ||||||
|   const PollListNotifierFamily(); |   const PollListNotifierFamily(); | ||||||
|  |  | ||||||
| @@ -78,7 +200,7 @@ class PollListNotifierProvider | |||||||
|     extends |     extends | ||||||
|         AutoDisposeAsyncNotifierProviderImpl< |         AutoDisposeAsyncNotifierProviderImpl< | ||||||
|           PollListNotifier, |           PollListNotifier, | ||||||
|           CursorPagingData<SnPoll> |           CursorPagingData<SnPollWithStats> | ||||||
|         > { |         > { | ||||||
|   /// See also [PollListNotifier]. |   /// See also [PollListNotifier]. | ||||||
|   PollListNotifierProvider(String? pubName) |   PollListNotifierProvider(String? pubName) | ||||||
| @@ -109,7 +231,7 @@ class PollListNotifierProvider | |||||||
|   final String? pubName; |   final String? pubName; | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   FutureOr<CursorPagingData<SnPoll>> runNotifierBuild( |   FutureOr<CursorPagingData<SnPollWithStats>> runNotifierBuild( | ||||||
|     covariant PollListNotifier notifier, |     covariant PollListNotifier notifier, | ||||||
|   ) { |   ) { | ||||||
|     return notifier.build(pubName); |     return notifier.build(pubName); | ||||||
| @@ -134,7 +256,7 @@ class PollListNotifierProvider | |||||||
|   @override |   @override | ||||||
|   AutoDisposeAsyncNotifierProviderElement< |   AutoDisposeAsyncNotifierProviderElement< | ||||||
|     PollListNotifier, |     PollListNotifier, | ||||||
|     CursorPagingData<SnPoll> |     CursorPagingData<SnPollWithStats> | ||||||
|   > |   > | ||||||
|   createElement() { |   createElement() { | ||||||
|     return _PollListNotifierProviderElement(this); |     return _PollListNotifierProviderElement(this); | ||||||
| @@ -157,7 +279,7 @@ class PollListNotifierProvider | |||||||
| @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 | ||||||
| mixin PollListNotifierRef | mixin PollListNotifierRef | ||||||
|     on AutoDisposeAsyncNotifierProviderRef<CursorPagingData<SnPoll>> { |     on AutoDisposeAsyncNotifierProviderRef<CursorPagingData<SnPollWithStats>> { | ||||||
|   /// The parameter `pubName` of this provider. |   /// The parameter `pubName` of this provider. | ||||||
|   String? get pubName; |   String? get pubName; | ||||||
| } | } | ||||||
| @@ -166,7 +288,7 @@ class _PollListNotifierProviderElement | |||||||
|     extends |     extends | ||||||
|         AutoDisposeAsyncNotifierProviderElement< |         AutoDisposeAsyncNotifierProviderElement< | ||||||
|           PollListNotifier, |           PollListNotifier, | ||||||
|           CursorPagingData<SnPoll> |           CursorPagingData<SnPollWithStats> | ||||||
|         > |         > | ||||||
|     with PollListNotifierRef { |     with PollListNotifierRef { | ||||||
|   _PollListNotifierProviderElement(super.provider); |   _PollListNotifierProviderElement(super.provider); | ||||||
|   | |||||||
| @@ -19,7 +19,7 @@ import 'package:island/services/responsive.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'; | ||||||
| import 'package:island/widgets/content/cloud_files.dart'; | import 'package:island/widgets/content/cloud_files.dart'; | ||||||
| import 'package:island/widgets/realms/selection_dropdown.dart'; | import 'package:island/widgets/realm/realm_selection_dropdown.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:styled_widget/styled_widget.dart'; | import 'package:styled_widget/styled_widget.dart'; | ||||||
|   | |||||||
| @@ -106,11 +106,7 @@ class StickerPacksNotifier extends _$StickerPacksNotifier | |||||||
|     try { |     try { | ||||||
|       final response = await client.get( |       final response = await client.get( | ||||||
|         '/sphere/stickers', |         '/sphere/stickers', | ||||||
|         queryParameters: { |         queryParameters: {'offset': offset, 'take': _pageSize, 'pub': pubName}, | ||||||
|           'offset': offset, |  | ||||||
|           'take': _pageSize, |  | ||||||
|           'pubName': pubName, |  | ||||||
|         }, |  | ||||||
|       ); |       ); | ||||||
|  |  | ||||||
|       final total = int.parse(response.headers.value('X-Total') ?? '0'); |       final total = int.parse(response.headers.value('X-Total') ?? '0'); | ||||||
|   | |||||||
| @@ -148,7 +148,7 @@ class _StickerPackProviderElement | |||||||
| } | } | ||||||
|  |  | ||||||
| String _$stickerPacksNotifierHash() => | String _$stickerPacksNotifierHash() => | ||||||
|     r'0a8edcf9c35396c411f1214f5e77b1e8fac6a3e6'; |     r'30024b35235f3085a5b1ec2204d0a974ee907e22'; | ||||||
|  |  | ||||||
| abstract class _$StickerPacksNotifier | abstract class _$StickerPacksNotifier | ||||||
|     extends BuildlessAutoDisposeAsyncNotifier<CursorPagingData<SnStickerPack>> { |     extends BuildlessAutoDisposeAsyncNotifier<CursorPagingData<SnStickerPack>> { | ||||||
|   | |||||||
| @@ -11,6 +11,7 @@ import 'package:island/models/realm.dart'; | |||||||
| import 'package:island/models/webfeed.dart'; | import 'package:island/models/webfeed.dart'; | ||||||
| import 'package:island/pods/event_calendar.dart'; | import 'package:island/pods/event_calendar.dart'; | ||||||
| import 'package:island/pods/userinfo.dart'; | import 'package:island/pods/userinfo.dart'; | ||||||
|  | import 'package:island/screens/notification.dart'; | ||||||
| import 'package:island/services/responsive.dart'; | import 'package:island/services/responsive.dart'; | ||||||
| import 'package:island/widgets/account/fortune_graph.dart'; | import 'package:island/widgets/account/fortune_graph.dart'; | ||||||
| import 'package:island/widgets/app_scaffold.dart'; | import 'package:island/widgets/app_scaffold.dart'; | ||||||
| @@ -30,6 +31,33 @@ import 'package:styled_widget/styled_widget.dart'; | |||||||
|  |  | ||||||
| part 'explore.g.dart'; | part 'explore.g.dart'; | ||||||
|  |  | ||||||
|  | Widget notificationIndicatorWidget( | ||||||
|  |   BuildContext context, { | ||||||
|  |   required int count, | ||||||
|  |   EdgeInsets? margin, | ||||||
|  | }) => Card( | ||||||
|  |   margin: margin, | ||||||
|  |   child: ListTile( | ||||||
|  |     shape: const RoundedRectangleBorder( | ||||||
|  |       borderRadius: BorderRadius.all(Radius.circular(8)), | ||||||
|  |     ), | ||||||
|  |     leading: const Icon(Symbols.notifications), | ||||||
|  |     title: Row( | ||||||
|  |       children: [ | ||||||
|  |         Text('notifications').tr().fontSize(14), | ||||||
|  |         const Gap(8), | ||||||
|  |         Badge(label: Text(count.toString())), | ||||||
|  |       ], | ||||||
|  |     ), | ||||||
|  |     trailing: const Icon(Symbols.chevron_right), | ||||||
|  |     minTileHeight: 40, | ||||||
|  |     contentPadding: EdgeInsets.only(left: 16, right: 15), | ||||||
|  |     onTap: () { | ||||||
|  |       GoRouter.of(context).pushNamed('notifications'); | ||||||
|  |     }, | ||||||
|  |   ), | ||||||
|  | ); | ||||||
|  |  | ||||||
| class ExploreScreen extends HookConsumerWidget { | class ExploreScreen extends HookConsumerWidget { | ||||||
|   const ExploreScreen({super.key}); |   const ExploreScreen({super.key}); | ||||||
|  |  | ||||||
| @@ -77,6 +105,10 @@ class ExploreScreen extends HookConsumerWidget { | |||||||
|  |  | ||||||
|     final user = ref.watch(userInfoProvider); |     final user = ref.watch(userInfoProvider); | ||||||
|  |  | ||||||
|  |     final notificationCount = ref.watch( | ||||||
|  |       notificationUnreadCountNotifierProvider, | ||||||
|  |     ); | ||||||
|  |  | ||||||
|     return AppScaffold( |     return AppScaffold( | ||||||
|       isNoBackground: false, |       isNoBackground: false, | ||||||
|       appBar: AppBar( |       appBar: AppBar( | ||||||
| @@ -141,12 +173,60 @@ class ExploreScreen extends HookConsumerWidget { | |||||||
|                     ), |                     ), | ||||||
|                     tooltip: 'webArticlesStand'.tr(), |                     tooltip: 'webArticlesStand'.tr(), | ||||||
|                   ), |                   ), | ||||||
|                   IconButton( |                   PopupMenuButton( | ||||||
|                     onPressed: () { |                     itemBuilder: | ||||||
|                       context.pushNamed('postSearch'); |                         (context) => [ | ||||||
|                     }, |                           PopupMenuItem( | ||||||
|  |                             child: Row( | ||||||
|  |                               children: [ | ||||||
|  |                                 const Icon(Symbols.category), | ||||||
|  |                                 const Gap(12), | ||||||
|  |                                 Text('categories').tr(), | ||||||
|  |                               ], | ||||||
|  |                             ), | ||||||
|  |                             onTap: () { | ||||||
|  |                               context.pushNamed('postCategories'); | ||||||
|  |                             }, | ||||||
|  |                           ), | ||||||
|  |                           PopupMenuItem( | ||||||
|  |                             child: Row( | ||||||
|  |                               children: [ | ||||||
|  |                                 const Icon(Symbols.label), | ||||||
|  |                                 const Gap(12), | ||||||
|  |                                 Text('tags').tr(), | ||||||
|  |                               ], | ||||||
|  |                             ), | ||||||
|  |                             onTap: () { | ||||||
|  |                               context.pushNamed('postTags'); | ||||||
|  |                             }, | ||||||
|  |                           ), | ||||||
|  |                           PopupMenuItem( | ||||||
|  |                             child: Row( | ||||||
|  |                               children: [ | ||||||
|  |                                 const Icon(Symbols.shuffle), | ||||||
|  |                                 const Gap(12), | ||||||
|  |                                 Text('postShuffle').tr(), | ||||||
|  |                               ], | ||||||
|  |                             ), | ||||||
|  |                             onTap: () { | ||||||
|  |                               context.pushNamed('postShuffle'); | ||||||
|  |                             }, | ||||||
|  |                           ), | ||||||
|  |                           PopupMenuItem( | ||||||
|  |                             child: Row( | ||||||
|  |                               children: [ | ||||||
|  |                                 const Icon(Symbols.search), | ||||||
|  |                                 const Gap(12), | ||||||
|  |                                 Text('search').tr(), | ||||||
|  |                               ], | ||||||
|  |                             ), | ||||||
|  |                             onTap: () { | ||||||
|  |                               context.pushNamed('postSearch'); | ||||||
|  |                             }, | ||||||
|  |                           ), | ||||||
|  |                         ], | ||||||
|                     icon: Icon( |                     icon: Icon( | ||||||
|                       Symbols.search, |                       Symbols.action_key, | ||||||
|                       color: Theme.of(context).appBarTheme.foregroundColor!, |                       color: Theme.of(context).appBarTheme.foregroundColor!, | ||||||
|                     ), |                     ), | ||||||
|                     tooltip: 'search'.tr(), |                     tooltip: 'search'.tr(), | ||||||
| @@ -185,7 +265,7 @@ class ExploreScreen extends HookConsumerWidget { | |||||||
|       floatingActionButtonLocation: TabbedFabLocation(context), |       floatingActionButtonLocation: TabbedFabLocation(context), | ||||||
|       body: Builder( |       body: Builder( | ||||||
|         builder: (context) { |         builder: (context) { | ||||||
|           final isWider = isWiderScreen(context); |           final isWide = isWideScreen(context); | ||||||
|  |  | ||||||
|           final bodyView = _buildActivityList( |           final bodyView = _buildActivityList( | ||||||
|             context, |             context, | ||||||
| @@ -193,40 +273,58 @@ class ExploreScreen extends HookConsumerWidget { | |||||||
|             currentFilter.value, |             currentFilter.value, | ||||||
|           ); |           ); | ||||||
|  |  | ||||||
|           if (isWider) { |           if (isWide) { | ||||||
|             return Row( |             return Row( | ||||||
|               children: [ |               children: [ | ||||||
|                 Flexible(flex: 3, child: bodyView.padding(left: 8)), |                 Flexible(flex: 3, child: bodyView.padding(left: 8)), | ||||||
|                 if (user.value != null) |                 if (user.value != null) | ||||||
|                   Flexible( |                   Flexible( | ||||||
|                     flex: 2, |                     flex: 2, | ||||||
|                     child: SingleChildScrollView( |                     child: Align( | ||||||
|                       child: Column( |                       alignment: Alignment.topCenter, | ||||||
|                         children: [ |                       child: SingleChildScrollView( | ||||||
|                           CheckInWidget( |                         child: Column( | ||||||
|                             margin: EdgeInsets.only( |                           children: [ | ||||||
|  |                             CheckInWidget( | ||||||
|  |                               margin: EdgeInsets.only( | ||||||
|  |                                 left: 8, | ||||||
|  |                                 right: 12, | ||||||
|  |                                 top: 16, | ||||||
|  |                               ), | ||||||
|  |                               onChecked: () { | ||||||
|  |                                 ref.invalidate( | ||||||
|  |                                   eventCalendarProvider(query.value), | ||||||
|  |                                 ); | ||||||
|  |                               }, | ||||||
|  |                             ), | ||||||
|  |                             if (notificationCount.value != null && | ||||||
|  |                                 notificationCount.value! > 0) | ||||||
|  |                               notificationIndicatorWidget( | ||||||
|  |                                 context, | ||||||
|  |                                 count: notificationCount.value ?? 0, | ||||||
|  |                                 margin: EdgeInsets.only( | ||||||
|  |                                   left: 8, | ||||||
|  |                                   right: 12, | ||||||
|  |                                   top: 8, | ||||||
|  |                                 ), | ||||||
|  |                               ), | ||||||
|  |                             PostFeaturedList().padding( | ||||||
|                               left: 8, |                               left: 8, | ||||||
|                               right: 12, |                               right: 12, | ||||||
|                               top: 16, |                               top: 8, | ||||||
|                             ), |                             ), | ||||||
|                             onChecked: () { |                             FortuneGraphWidget( | ||||||
|                               ref.invalidate( |                               margin: EdgeInsets.only( | ||||||
|                                 eventCalendarProvider(query.value), |                                 left: 8, | ||||||
|                               ); |                                 right: 12, | ||||||
|                             }, |                                 top: 8, | ||||||
|                           ), |                               ), | ||||||
|                           PostFeaturedList().padding( |                               events: events, | ||||||
|                             left: 8, |                               constrainWidth: true, | ||||||
|                             right: 12, |                               onPointSelected: onDaySelected, | ||||||
|                             top: 8, |                             ), | ||||||
|                           ), |                           ], | ||||||
|                           FortuneGraphWidget( |                         ), | ||||||
|                             margin: EdgeInsets.only(left: 8, right: 12, top: 8), |  | ||||||
|                             events: events, |  | ||||||
|                             constrainWidth: true, |  | ||||||
|                             onPointSelected: onDaySelected, |  | ||||||
|                           ), |  | ||||||
|                         ], |  | ||||||
|                       ), |                       ), | ||||||
|                     ), |                     ), | ||||||
|                   ) |                   ) | ||||||
| @@ -268,7 +366,7 @@ class ExploreScreen extends HookConsumerWidget { | |||||||
|       activityListNotifierProvider(filter).notifier, |       activityListNotifierProvider(filter).notifier, | ||||||
|     ); |     ); | ||||||
|  |  | ||||||
|     final isWider = isWiderScreen(context); |     final isWide = isWideScreen(context); | ||||||
|  |  | ||||||
|     return RefreshIndicator( |     return RefreshIndicator( | ||||||
|       onRefresh: () => Future.sync(activitiesNotifier.forceRefresh), |       onRefresh: () => Future.sync(activitiesNotifier.forceRefresh), | ||||||
| @@ -283,7 +381,7 @@ class ExploreScreen extends HookConsumerWidget { | |||||||
|                 widgetCount: widgetCount, |                 widgetCount: widgetCount, | ||||||
|                 endItemView: endItemView, |                 endItemView: endItemView, | ||||||
|                 activitiesNotifier: activitiesNotifier, |                 activitiesNotifier: activitiesNotifier, | ||||||
|                 contentOnly: isWider || filter != null, |                 contentOnly: isWide || filter != null, | ||||||
|               ), |               ), | ||||||
|             ), |             ), | ||||||
|       ), |       ), | ||||||
| @@ -380,6 +478,10 @@ class _ActivityListView extends HookConsumerWidget { | |||||||
|   Widget build(BuildContext context, WidgetRef ref) { |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|     final user = ref.watch(userInfoProvider); |     final user = ref.watch(userInfoProvider); | ||||||
|  |  | ||||||
|  |     final notificationCount = ref.watch( | ||||||
|  |       notificationUnreadCountNotifierProvider, | ||||||
|  |     ); | ||||||
|  |  | ||||||
|     return CustomScrollView( |     return CustomScrollView( | ||||||
|       slivers: [ |       slivers: [ | ||||||
|         SliverGap(12), |         SliverGap(12), | ||||||
| @@ -393,6 +495,14 @@ class _ActivityListView extends HookConsumerWidget { | |||||||
|           SliverToBoxAdapter( |           SliverToBoxAdapter( | ||||||
|             child: PostFeaturedList().padding(horizontal: 8, bottom: 4, top: 4), |             child: PostFeaturedList().padding(horizontal: 8, bottom: 4, top: 4), | ||||||
|           ), |           ), | ||||||
|  |         if (!contentOnly && (notificationCount.value ?? 0) > 0) | ||||||
|  |           SliverToBoxAdapter( | ||||||
|  |             child: notificationIndicatorWidget( | ||||||
|  |               context, | ||||||
|  |               count: notificationCount.value ?? 0, | ||||||
|  |               margin: EdgeInsets.only(left: 8, right: 8, top: 4, bottom: 4), | ||||||
|  |             ), | ||||||
|  |           ), | ||||||
|         SliverList.builder( |         SliverList.builder( | ||||||
|           itemCount: widgetCount, |           itemCount: widgetCount, | ||||||
|           itemBuilder: (context, index) { |           itemBuilder: (context, index) { | ||||||
|   | |||||||
| @@ -3,14 +3,17 @@ import 'dart:math' as math; | |||||||
|  |  | ||||||
| import 'package:easy_localization/easy_localization.dart'; | import 'package:easy_localization/easy_localization.dart'; | ||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:gap/gap.dart'; | ||||||
| import 'package:go_router/go_router.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/user.dart'; | import 'package:island/models/account.dart'; | ||||||
| import 'package:island/pods/network.dart'; | import 'package:island/pods/network.dart'; | ||||||
| import 'package:island/pods/websocket.dart'; | import 'package:island/pods/websocket.dart'; | ||||||
| import 'package:island/route.dart'; | import 'package:island/route.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/content/markdown.dart'; | import 'package:island/widgets/content/markdown.dart'; | ||||||
|  | import 'package:material_symbols_icons/material_symbols_icons.dart'; | ||||||
| import 'package:relative_time/relative_time.dart'; | import 'package:relative_time/relative_time.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'; | ||||||
| @@ -62,6 +65,10 @@ class NotificationUnreadCountNotifier | |||||||
|     final current = await future; |     final current = await future; | ||||||
|     state = AsyncData(math.max(current - count, 0)); |     state = AsyncData(math.max(current - count, 0)); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   void clear() async { | ||||||
|  |     state = AsyncData(0); | ||||||
|  |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| @riverpod | @riverpod | ||||||
| @@ -111,8 +118,28 @@ class NotificationScreen extends HookConsumerWidget { | |||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context, WidgetRef ref) { |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|  |     Future<void> markAllRead() async { | ||||||
|  |       showLoadingModal(context); | ||||||
|  |       final apiClient = ref.watch(apiClientProvider); | ||||||
|  |       await apiClient.post('/pusher/notifications/all/read'); | ||||||
|  |       if (!context.mounted) return; | ||||||
|  |       hideLoadingModal(context); | ||||||
|  |       ref.invalidate(notificationListNotifierProvider); | ||||||
|  |       ref.watch(notificationUnreadCountNotifierProvider.notifier).clear(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     return AppScaffold( |     return AppScaffold( | ||||||
|       appBar: AppBar(title: const Text('notifications').tr()), |       appBar: AppBar( | ||||||
|  |         leading: const PageBackButton(), | ||||||
|  |         title: const Text('notifications').tr(), | ||||||
|  |         actions: [ | ||||||
|  |           IconButton( | ||||||
|  |             onPressed: markAllRead, | ||||||
|  |             icon: const Icon(Symbols.mark_as_unread), | ||||||
|  |           ), | ||||||
|  |           const Gap(8), | ||||||
|  |         ], | ||||||
|  |       ), | ||||||
|       body: PagingHelperView( |       body: PagingHelperView( | ||||||
|         provider: notificationListNotifierProvider, |         provider: notificationListNotifierProvider, | ||||||
|         futureRefreshable: notificationListNotifierProvider.future, |         futureRefreshable: notificationListNotifierProvider.future, | ||||||
|   | |||||||
| @@ -7,7 +7,7 @@ part of 'notification.dart'; | |||||||
| // ************************************************************************** | // ************************************************************************** | ||||||
|  |  | ||||||
| String _$notificationUnreadCountNotifierHash() => | String _$notificationUnreadCountNotifierHash() => | ||||||
|     r'd199abf0d16944587e747798399a267a790341f3'; |     r'0763b66bd64e5a9b7c317887e109ab367515dfa4'; | ||||||
|  |  | ||||||
| /// See also [NotificationUnreadCountNotifier]. | /// See also [NotificationUnreadCountNotifier]. | ||||||
| @ProviderFor(NotificationUnreadCountNotifier) | @ProviderFor(NotificationUnreadCountNotifier) | ||||||
|   | |||||||
| @@ -11,6 +11,7 @@ import 'package:island/widgets/alert.dart'; | |||||||
| import 'package:island/models/poll.dart'; | import 'package:island/models/poll.dart'; | ||||||
| import 'package:island/widgets/app_scaffold.dart'; | import 'package:island/widgets/app_scaffold.dart'; | ||||||
| import 'package:uuid/uuid.dart'; | import 'package:uuid/uuid.dart'; | ||||||
|  | import 'package:easy_localization/easy_localization.dart'; | ||||||
|  |  | ||||||
| class PollEditorState { | class PollEditorState { | ||||||
|   String? id; // for editing |   String? id; // for editing | ||||||
| @@ -110,7 +111,7 @@ class PollEditor extends Notifier<PollEditorState> { | |||||||
|               ? [ |               ? [ | ||||||
|                 SnPollOption( |                 SnPollOption( | ||||||
|                   id: const Uuid().v4(), |                   id: const Uuid().v4(), | ||||||
|                   label: 'Option 1', |                   label: 'pollOptionDefaultLabel'.tr(), | ||||||
|                   order: 0, |                   order: 0, | ||||||
|                 ), |                 ), | ||||||
|               ] |               ] | ||||||
| @@ -191,7 +192,7 @@ class PollEditor extends Notifier<PollEditorState> { | |||||||
|                 : [ |                 : [ | ||||||
|                   SnPollOption( |                   SnPollOption( | ||||||
|                     id: const Uuid().v4(), |                     id: const Uuid().v4(), | ||||||
|                     label: 'Option 1', |                     label: 'pollOptionDefaultLabel'.tr(), | ||||||
|                     order: 0, |                     order: 0, | ||||||
|                   ), |                   ), | ||||||
|                 ]) |                 ]) | ||||||
| @@ -389,7 +390,7 @@ class PollEditorScreen extends ConsumerWidget { | |||||||
|                 data: body, |                 data: body, | ||||||
|               )); |               )); | ||||||
|  |  | ||||||
|       showSnackBar(isUpdate ? 'Poll updated.' : 'Poll created.'); |       showSnackBar(isUpdate ? 'pollUpdated'.tr() : 'pollCreated'.tr()); | ||||||
|  |  | ||||||
|       if (!context.mounted) return; |       if (!context.mounted) return; | ||||||
|       Navigator.of(context).maybePop(res.data); |       Navigator.of(context).maybePop(res.data); | ||||||
| @@ -416,11 +417,11 @@ class PollEditorScreen extends ConsumerWidget { | |||||||
|  |  | ||||||
|     return AppScaffold( |     return AppScaffold( | ||||||
|       appBar: AppBar( |       appBar: AppBar( | ||||||
|         title: Text(model.id == null ? 'Create Poll' : 'Edit Poll'), |         title: Text(model.id == null ? 'pollCreate'.tr() : 'pollEdit'.tr()), | ||||||
|         actions: [ |         actions: [ | ||||||
|           if (kDebugMode) |           if (kDebugMode) | ||||||
|             IconButton( |             IconButton( | ||||||
|               tooltip: 'Preview JSON (debug)', |               tooltip: 'pollPreviewJsonDebug'.tr(), | ||||||
|               onPressed: () { |               onPressed: () { | ||||||
|                 _showDebugPreview(context, model); |                 _showDebugPreview(context, model); | ||||||
|               }, |               }, | ||||||
| @@ -439,8 +440,8 @@ class PollEditorScreen extends ConsumerWidget { | |||||||
|                 children: [ |                 children: [ | ||||||
|                   TextFormField( |                   TextFormField( | ||||||
|                     initialValue: model.title ?? '', |                     initialValue: model.title ?? '', | ||||||
|                     decoration: const InputDecoration( |                     decoration: InputDecoration( | ||||||
|                       labelText: 'Title', |                       labelText: 'title'.tr(), | ||||||
|                       border: OutlineInputBorder( |                       border: OutlineInputBorder( | ||||||
|                         borderRadius: BorderRadius.all(Radius.circular(16)), |                         borderRadius: BorderRadius.all(Radius.circular(16)), | ||||||
|                       ), |                       ), | ||||||
| @@ -452,7 +453,7 @@ class PollEditorScreen extends ConsumerWidget { | |||||||
|                         (_) => FocusManager.instance.primaryFocus?.unfocus(), |                         (_) => FocusManager.instance.primaryFocus?.unfocus(), | ||||||
|                     validator: (v) { |                     validator: (v) { | ||||||
|                       if (v == null || v.trim().isEmpty) { |                       if (v == null || v.trim().isEmpty) { | ||||||
|                         return 'Title is required'; |                         return 'pollTitleRequired'.tr(); | ||||||
|                       } |                       } | ||||||
|                       return null; |                       return null; | ||||||
|                     }, |                     }, | ||||||
| @@ -460,8 +461,8 @@ class PollEditorScreen extends ConsumerWidget { | |||||||
|                   const Gap(12), |                   const Gap(12), | ||||||
|                   TextFormField( |                   TextFormField( | ||||||
|                     initialValue: model.description ?? '', |                     initialValue: model.description ?? '', | ||||||
|                     decoration: const InputDecoration( |                     decoration: InputDecoration( | ||||||
|                       labelText: 'Description', |                       labelText: 'description'.tr(), | ||||||
|                       alignLabelWithHint: true, |                       alignLabelWithHint: true, | ||||||
|                       border: OutlineInputBorder( |                       border: OutlineInputBorder( | ||||||
|                         borderRadius: BorderRadius.all(Radius.circular(16)), |                         borderRadius: BorderRadius.all(Radius.circular(16)), | ||||||
| @@ -482,7 +483,7 @@ class PollEditorScreen extends ConsumerWidget { | |||||||
|                   Row( |                   Row( | ||||||
|                     children: [ |                     children: [ | ||||||
|                       Text( |                       Text( | ||||||
|                         'Questions', |                         'questions'.tr(), | ||||||
|                         style: Theme.of(context).textTheme.titleLarge, |                         style: Theme.of(context).textTheme.titleLarge, | ||||||
|                       ), |                       ), | ||||||
|                       const Spacer(), |                       const Spacer(), | ||||||
| @@ -495,7 +496,7 @@ class PollEditorScreen extends ConsumerWidget { | |||||||
|                                   : controller.open(); |                                   : controller.open(); | ||||||
|                             }, |                             }, | ||||||
|                             icon: const Icon(Icons.add), |                             icon: const Icon(Icons.add), | ||||||
|                             label: const Text('Add question'), |                             label: Text('pollAddQuestion'.tr()), | ||||||
|                           ); |                           ); | ||||||
|                         }, |                         }, | ||||||
|                         menuChildren: |                         menuChildren: | ||||||
| @@ -514,9 +515,9 @@ class PollEditorScreen extends ConsumerWidget { | |||||||
|                   const Gap(8), |                   const Gap(8), | ||||||
|                   if (model.questions.isEmpty) |                   if (model.questions.isEmpty) | ||||||
|                     _EmptyState( |                     _EmptyState( | ||||||
|                       title: 'No questions yet', |                       title: 'pollNoQuestionsYet'.tr(), | ||||||
|                       subtitle: |                       subtitle: | ||||||
|                           'Use "Add question" to start building your poll.', |                           'pollNoQuestionsHint'.tr(), | ||||||
|                     ) |                     ) | ||||||
|                   else |                   else | ||||||
|                     ReorderableListView.builder( |                     ReorderableListView.builder( | ||||||
| @@ -585,7 +586,7 @@ class PollEditorScreen extends ConsumerWidget { | |||||||
|                   Navigator.of(context).maybePop(); |                   Navigator.of(context).maybePop(); | ||||||
|                 }, |                 }, | ||||||
|                 icon: const Icon(Icons.close), |                 icon: const Icon(Icons.close), | ||||||
|                 label: const Text('Cancel'), |                 label: Text('cancel'.tr()), | ||||||
|               ), |               ), | ||||||
|               const Spacer(), |               const Spacer(), | ||||||
|               FilledButton.icon( |               FilledButton.icon( | ||||||
| @@ -593,7 +594,7 @@ class PollEditorScreen extends ConsumerWidget { | |||||||
|                   _submitPoll(context, ref); |                   _submitPoll(context, ref); | ||||||
|                 }, |                 }, | ||||||
|                 icon: const Icon(Icons.cloud_upload_outlined), |                 icon: const Icon(Icons.cloud_upload_outlined), | ||||||
|                 label: Text(model.id == null ? 'Create' : 'Update'), |                 label: Text(model.id == null ? 'create'.tr() : 'update'.tr()), | ||||||
|               ), |               ), | ||||||
|             ], |             ], | ||||||
|           ), |           ), | ||||||
| @@ -637,14 +638,14 @@ class PollEditorScreen extends ConsumerWidget { | |||||||
|       context: context, |       context: context, | ||||||
|       builder: |       builder: | ||||||
|           (_) => AlertDialog( |           (_) => AlertDialog( | ||||||
|             title: const Text('Debug Preview'), |             title: Text('pollDebugPreview'.tr()), | ||||||
|             content: SingleChildScrollView( |             content: SingleChildScrollView( | ||||||
|               child: SelectableText(buf.toString()), |               child: SelectableText(buf.toString()), | ||||||
|             ), |             ), | ||||||
|             actions: [ |             actions: [ | ||||||
|               TextButton( |               TextButton( | ||||||
|                 onPressed: () => Navigator.of(context).pop(), |                 onPressed: () => Navigator.of(context).pop(), | ||||||
|                 child: const Text('Close'), |                 child: Text('close'.tr()), | ||||||
|               ), |               ), | ||||||
|             ], |             ], | ||||||
|           ), |           ), | ||||||
| @@ -673,15 +674,15 @@ IconData _iconForType(SnPollQuestionType t) { | |||||||
| String _labelForType(SnPollQuestionType t) { | String _labelForType(SnPollQuestionType t) { | ||||||
|   switch (t) { |   switch (t) { | ||||||
|     case SnPollQuestionType.singleChoice: |     case SnPollQuestionType.singleChoice: | ||||||
|       return 'Single choice'; |       return 'pollQuestionTypeSingleChoice'.tr(); | ||||||
|     case SnPollQuestionType.multipleChoice: |     case SnPollQuestionType.multipleChoice: | ||||||
|       return 'Multiple choice'; |       return 'pollQuestionTypeMultipleChoice'.tr(); | ||||||
|     case SnPollQuestionType.freeText: |     case SnPollQuestionType.freeText: | ||||||
|       return 'Free text'; |       return 'pollQuestionTypeFreeText'.tr(); | ||||||
|     case SnPollQuestionType.yesNo: |     case SnPollQuestionType.yesNo: | ||||||
|       return 'Yes / No'; |       return 'pollQuestionTypeYesNo'.tr(); | ||||||
|     case SnPollQuestionType.rating: |     case SnPollQuestionType.rating: | ||||||
|       return 'Rating'; |       return 'pollQuestionTypeRating'.tr(); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -698,8 +699,8 @@ class _EndDatePicker extends StatelessWidget { | |||||||
|       children: [ |       children: [ | ||||||
|         Expanded( |         Expanded( | ||||||
|           child: InputDecorator( |           child: InputDecorator( | ||||||
|             decoration: const InputDecoration( |             decoration: InputDecoration( | ||||||
|               labelText: 'End date & time (optional)', |               labelText: 'pollEndDateOptional'.tr(), | ||||||
|               border: OutlineInputBorder( |               border: OutlineInputBorder( | ||||||
|                 borderRadius: BorderRadius.all(Radius.circular(16)), |                 borderRadius: BorderRadius.all(Radius.circular(16)), | ||||||
|               ), |               ), | ||||||
| @@ -711,7 +712,7 @@ class _EndDatePicker extends StatelessWidget { | |||||||
|                 Icon(Icons.event, color: Theme.of(context).colorScheme.primary), |                 Icon(Icons.event, color: Theme.of(context).colorScheme.primary), | ||||||
|                 Text( |                 Text( | ||||||
|                   value == null |                   value == null | ||||||
|                       ? 'Not set' |                       ? 'notSet'.tr() | ||||||
|                       : MaterialLocalizations.of( |                       : MaterialLocalizations.of( | ||||||
|                         context, |                         context, | ||||||
|                       ).formatFullDate(value!), |                       ).formatFullDate(value!), | ||||||
| @@ -759,12 +760,12 @@ class _EndDatePicker extends StatelessWidget { | |||||||
|                     ); |                     ); | ||||||
|                     onChanged(dt); |                     onChanged(dt); | ||||||
|                   }, |                   }, | ||||||
|                   child: const Text('Pick'), |                   child: Text('pick'.tr()), | ||||||
|                 ), |                 ), | ||||||
|                 if (value != null) |                 if (value != null) | ||||||
|                   TextButton( |                   TextButton( | ||||||
|                     onPressed: () => onChanged(null), |                     onPressed: () => onChanged(null), | ||||||
|                     child: const Text('Clear'), |                     child: Text('clear'.tr()), | ||||||
|                   ), |                   ), | ||||||
|               ], |               ], | ||||||
|             ), |             ), | ||||||
| @@ -799,7 +800,7 @@ class _QuestionHeader extends StatelessWidget { | |||||||
|         child: const Icon(Icons.drag_handle), |         child: const Icon(Icons.drag_handle), | ||||||
|       ), |       ), | ||||||
|       title: Text( |       title: Text( | ||||||
|         question.title.isEmpty ? 'Untitled question' : question.title, |         question.title.isEmpty ? 'pollUntitledQuestion'.tr() : question.title, | ||||||
|         maxLines: 1, |         maxLines: 1, | ||||||
|         overflow: TextOverflow.ellipsis, |         overflow: TextOverflow.ellipsis, | ||||||
|       ), |       ), | ||||||
| @@ -808,17 +809,17 @@ class _QuestionHeader extends StatelessWidget { | |||||||
|         spacing: 4, |         spacing: 4, | ||||||
|         children: [ |         children: [ | ||||||
|           IconButton( |           IconButton( | ||||||
|             tooltip: 'Move up', |             tooltip: 'moveUp'.tr(), | ||||||
|             onPressed: onMoveUp, |             onPressed: onMoveUp, | ||||||
|             icon: const Icon(Icons.arrow_upward), |             icon: const Icon(Icons.arrow_upward), | ||||||
|           ), |           ), | ||||||
|           IconButton( |           IconButton( | ||||||
|             tooltip: 'Move down', |             tooltip: 'moveDown'.tr(), | ||||||
|             onPressed: onMoveDown, |             onPressed: onMoveDown, | ||||||
|             icon: const Icon(Icons.arrow_downward), |             icon: const Icon(Icons.arrow_downward), | ||||||
|           ), |           ), | ||||||
|           IconButton( |           IconButton( | ||||||
|             tooltip: 'Delete', |             tooltip: 'delete'.tr(), | ||||||
|             onPressed: onDelete, |             onPressed: onDelete, | ||||||
|             icon: const Icon(Icons.delete_outline), |             icon: const Icon(Icons.delete_outline), | ||||||
|             color: Theme.of(context).colorScheme.error, |             color: Theme.of(context).colorScheme.error, | ||||||
| @@ -853,7 +854,7 @@ class _QuestionEditor extends ConsumerWidget { | |||||||
|               onChanged: (t) => notifier.setQuestionType(index, t), |               onChanged: (t) => notifier.setQuestionType(index, t), | ||||||
|             ), |             ), | ||||||
|             FilterChip( |             FilterChip( | ||||||
|               label: const Text('Required'), |               label: Text('required'.tr()), | ||||||
|               selected: question.isRequired, |               selected: question.isRequired, | ||||||
|               onSelected: (v) => notifier.setQuestionRequired(index, v), |               onSelected: (v) => notifier.setQuestionRequired(index, v), | ||||||
|               avatar: Icon( |               avatar: Icon( | ||||||
| @@ -867,8 +868,8 @@ class _QuestionEditor extends ConsumerWidget { | |||||||
|         const Gap(12), |         const Gap(12), | ||||||
|         TextFormField( |         TextFormField( | ||||||
|           initialValue: question.title, |           initialValue: question.title, | ||||||
|           decoration: const InputDecoration( |           decoration: InputDecoration( | ||||||
|             labelText: 'Question title', |             labelText: 'pollQuestionTitle'.tr(), | ||||||
|             border: OutlineInputBorder( |             border: OutlineInputBorder( | ||||||
|               borderRadius: BorderRadius.all(Radius.circular(16)), |               borderRadius: BorderRadius.all(Radius.circular(16)), | ||||||
|             ), |             ), | ||||||
| @@ -879,7 +880,7 @@ class _QuestionEditor extends ConsumerWidget { | |||||||
|           onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), |           onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), | ||||||
|           validator: (v) { |           validator: (v) { | ||||||
|             if (v == null || v.trim().isEmpty) { |             if (v == null || v.trim().isEmpty) { | ||||||
|               return 'Question title is required'; |               return 'pollQuestionTitleRequired'.tr(); | ||||||
|             } |             } | ||||||
|             return null; |             return null; | ||||||
|           }, |           }, | ||||||
| @@ -887,8 +888,8 @@ class _QuestionEditor extends ConsumerWidget { | |||||||
|         const Gap(12), |         const Gap(12), | ||||||
|         TextFormField( |         TextFormField( | ||||||
|           initialValue: question.description ?? '', |           initialValue: question.description ?? '', | ||||||
|           decoration: const InputDecoration( |           decoration: InputDecoration( | ||||||
|             labelText: 'Question description (optional)', |             labelText: 'pollQuestionDescriptionOptional'.tr(), | ||||||
|             border: OutlineInputBorder( |             border: OutlineInputBorder( | ||||||
|               borderRadius: BorderRadius.all(Radius.circular(16)), |               borderRadius: BorderRadius.all(Radius.circular(16)), | ||||||
|             ), |             ), | ||||||
| @@ -902,7 +903,7 @@ class _QuestionEditor extends ConsumerWidget { | |||||||
|         ), |         ), | ||||||
|         if (question.options != null) ...[ |         if (question.options != null) ...[ | ||||||
|           const Gap(16), |           const Gap(16), | ||||||
|           Text('Options', style: Theme.of(context).textTheme.titleMedium), |           Text('options'.tr(), style: Theme.of(context).textTheme.titleMedium), | ||||||
|           const Gap(8), |           const Gap(8), | ||||||
|           _OptionsEditor(index: index, options: question.options!), |           _OptionsEditor(index: index, options: question.options!), | ||||||
|           const Gap(4), |           const Gap(4), | ||||||
| @@ -911,7 +912,7 @@ class _QuestionEditor extends ConsumerWidget { | |||||||
|             child: OutlinedButton.icon( |             child: OutlinedButton.icon( | ||||||
|               onPressed: () => notifier.addOption(index), |               onPressed: () => notifier.addOption(index), | ||||||
|               icon: const Icon(Icons.add), |               icon: const Icon(Icons.add), | ||||||
|               label: const Text('Add option'), |               label: Text('pollAddOption'.tr()), | ||||||
|             ), |             ), | ||||||
|           ), |           ), | ||||||
|         ], |         ], | ||||||
| @@ -937,8 +938,8 @@ class _QuestionTypePicker extends StatelessWidget { | |||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
|     return DropdownButtonFormField<SnPollQuestionType>( |     return DropdownButtonFormField<SnPollQuestionType>( | ||||||
|       value: value, |       value: value, | ||||||
|       decoration: const InputDecoration( |       decoration: InputDecoration( | ||||||
|         labelText: 'Type', |         labelText: 'Type'.tr(), | ||||||
|         border: OutlineInputBorder( |         border: OutlineInputBorder( | ||||||
|           borderRadius: BorderRadius.all(Radius.circular(16)), |           borderRadius: BorderRadius.all(Radius.circular(16)), | ||||||
|         ), |         ), | ||||||
| @@ -987,8 +988,8 @@ class _OptionsEditor extends ConsumerWidget { | |||||||
|                   child: TextFormField( |                   child: TextFormField( | ||||||
|                     key: ValueKey(options[i].id), |                     key: ValueKey(options[i].id), | ||||||
|                     initialValue: options[i].label, |                     initialValue: options[i].label, | ||||||
|                     decoration: const InputDecoration( |                     decoration: InputDecoration( | ||||||
|                       labelText: 'Option label', |                       labelText: 'pollOptionLabel'.tr(), | ||||||
|                       border: OutlineInputBorder( |                       border: OutlineInputBorder( | ||||||
|                         borderRadius: BorderRadius.all(Radius.circular(16)), |                         borderRadius: BorderRadius.all(Radius.circular(16)), | ||||||
|                       ), |                       ), | ||||||
| @@ -1003,7 +1004,7 @@ class _OptionsEditor extends ConsumerWidget { | |||||||
|                 SizedBox( |                 SizedBox( | ||||||
|                   width: 40, |                   width: 40, | ||||||
|                   child: IconButton( |                   child: IconButton( | ||||||
|                     tooltip: 'Move up', |                     tooltip: 'moveUp'.tr(), | ||||||
|                     onPressed: |                     onPressed: | ||||||
|                         i > 0 ? () => notifier.moveOptionUp(index, i) : null, |                         i > 0 ? () => notifier.moveOptionUp(index, i) : null, | ||||||
|                     icon: const Icon(Icons.arrow_upward), |                     icon: const Icon(Icons.arrow_upward), | ||||||
| @@ -1012,7 +1013,7 @@ class _OptionsEditor extends ConsumerWidget { | |||||||
|                 SizedBox( |                 SizedBox( | ||||||
|                   width: 40, |                   width: 40, | ||||||
|                   child: IconButton( |                   child: IconButton( | ||||||
|                     tooltip: 'Move down', |                     tooltip: 'moveDown'.tr(), | ||||||
|                     onPressed: |                     onPressed: | ||||||
|                         i < options.length - 1 |                         i < options.length - 1 | ||||||
|                             ? () => notifier.moveOptionDown(index, i) |                             ? () => notifier.moveOptionDown(index, i) | ||||||
| @@ -1023,7 +1024,7 @@ class _OptionsEditor extends ConsumerWidget { | |||||||
|                 SizedBox( |                 SizedBox( | ||||||
|                   width: 40, |                   width: 40, | ||||||
|                   child: IconButton( |                   child: IconButton( | ||||||
|                     tooltip: 'Delete', |                     tooltip: 'delete'.tr(), | ||||||
|                     onPressed: () => notifier.removeOption(index, i), |                     onPressed: () => notifier.removeOption(index, i), | ||||||
|                     icon: const Icon(Icons.close), |                     icon: const Icon(Icons.close), | ||||||
|                   ), |                   ), | ||||||
| @@ -1048,7 +1049,7 @@ class _TextAnswerPreview extends StatelessWidget { | |||||||
|       maxLines: long ? 4 : 1, |       maxLines: long ? 4 : 1, | ||||||
|       decoration: InputDecoration( |       decoration: InputDecoration( | ||||||
|         labelText: |         labelText: | ||||||
|             long ? 'Long text answer (preview)' : 'Short text answer (preview)', |             long ? 'pollLongTextAnswerPreview'.tr() : 'pollShortTextAnswerPreview'.tr(), | ||||||
|         border: const OutlineInputBorder( |         border: const OutlineInputBorder( | ||||||
|           borderRadius: BorderRadius.all(Radius.circular(16)), |           borderRadius: BorderRadius.all(Radius.circular(16)), | ||||||
|         ), |         ), | ||||||
| @@ -1082,9 +1083,9 @@ class _EmptyState extends StatelessWidget { | |||||||
|             child: Column( |             child: Column( | ||||||
|               crossAxisAlignment: CrossAxisAlignment.start, |               crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|               children: [ |               children: [ | ||||||
|                 Text(title, style: Theme.of(context).textTheme.titleMedium), |                 Text('pollNoQuestionsYet'.tr(), style: Theme.of(context).textTheme.titleMedium), | ||||||
|                 const Gap(4), |                 const Gap(4), | ||||||
|                 Text(subtitle, style: Theme.of(context).textTheme.bodyMedium), |                 Text('pollNoQuestionsHint'.tr(), style: Theme.of(context).textTheme.bodyMedium), | ||||||
|               ], |               ], | ||||||
|             ), |             ), | ||||||
|           ), |           ), | ||||||
|   | |||||||
| @@ -89,6 +89,9 @@ class ArticleComposeScreen extends HookConsumerWidget { | |||||||
|     }, [state]); |     }, [state]); | ||||||
|  |  | ||||||
|     final showPreview = useState(false); |     final showPreview = useState(false); | ||||||
|  |     final isAttachmentsExpanded = useState( | ||||||
|  |       true, | ||||||
|  |     ); // New state for attachments section | ||||||
|  |  | ||||||
|     // Initialize publisher once when data is available |     // Initialize publisher once when data is available | ||||||
|     useEffect(() { |     useEffect(() { | ||||||
| @@ -297,71 +300,88 @@ class ArticleComposeScreen extends HookConsumerWidget { | |||||||
|                 valueListenable: state.attachments, |                 valueListenable: state.attachments, | ||||||
|                 builder: (context, attachments, _) { |                 builder: (context, attachments, _) { | ||||||
|                   if (attachments.isEmpty) return const SizedBox.shrink(); |                   if (attachments.isEmpty) return const SizedBox.shrink(); | ||||||
|                   return Column( |                   return Theme( | ||||||
|                     crossAxisAlignment: CrossAxisAlignment.start, |                     data: Theme.of( | ||||||
|                     children: [ |                       context, | ||||||
|                       const Gap(16), |                     ).copyWith(dividerColor: Colors.transparent), | ||||||
|                       Text( |                     child: ExpansionTile( | ||||||
|                         'articleAttachmentHint'.tr(), |                       initiallyExpanded: isAttachmentsExpanded.value, | ||||||
|                         style: Theme.of(context).textTheme.bodySmall?.copyWith( |                       onExpansionChanged: (expanded) { | ||||||
|                           color: Theme.of(context).colorScheme.onSurfaceVariant, |                         isAttachmentsExpanded.value = expanded; | ||||||
|                         ), |                       }, | ||||||
|                       ).padding(bottom: 8), |                       collapsedBackgroundColor: | ||||||
|                       ValueListenableBuilder<Map<int, double>>( |                           Theme.of(context).colorScheme.surfaceContainer, | ||||||
|                         valueListenable: state.attachmentProgress, |                       title: Column( | ||||||
|                         builder: (context, progressMap, _) { |                         crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|                           return Wrap( |                         children: [ | ||||||
|                             spacing: 8, |                           Text('attachments').tr(), | ||||||
|                             runSpacing: 8, |                           Text( | ||||||
|                             children: [ |                             'articleAttachmentHint'.tr(), | ||||||
|                               for (var idx = 0; idx < attachments.length; idx++) |                             style: Theme.of( | ||||||
|                                 SizedBox( |                               context, | ||||||
|                                   width: 280, |                             ).textTheme.bodySmall?.copyWith( | ||||||
|                                   height: 280, |                               color: | ||||||
|                                   child: AttachmentPreview( |                                   Theme.of( | ||||||
|                                     item: attachments[idx], |                                     context, | ||||||
|                                     progress: progressMap[idx], |                                   ).colorScheme.onSurfaceVariant, | ||||||
|                                     onRequestUpload: |                             ), | ||||||
|                                         () => ComposeLogic.uploadAttachment( |                           ), | ||||||
|                                           ref, |                         ], | ||||||
|                                           state, |  | ||||||
|                                           idx, |  | ||||||
|                                         ), |  | ||||||
|                                     onUpdate: |  | ||||||
|                                         (value) => |  | ||||||
|                                             ComposeLogic.updateAttachment( |  | ||||||
|                                               state, |  | ||||||
|                                               value, |  | ||||||
|                                               idx, |  | ||||||
|                                             ), |  | ||||||
|                                     onDelete: |  | ||||||
|                                         () => ComposeLogic.deleteAttachment( |  | ||||||
|                                           ref, |  | ||||||
|                                           state, |  | ||||||
|                                           idx, |  | ||||||
|                                         ), |  | ||||||
|                                     onMove: (delta) { |  | ||||||
|                                       state |  | ||||||
|                                           .attachments |  | ||||||
|                                           .value = ComposeLogic.moveAttachment( |  | ||||||
|                                         state.attachments.value, |  | ||||||
|                                         idx, |  | ||||||
|                                         delta, |  | ||||||
|                                       ); |  | ||||||
|                                     }, |  | ||||||
|                                     onInsert: |  | ||||||
|                                         () => ComposeLogic.insertAttachment( |  | ||||||
|                                           ref, |  | ||||||
|                                           state, |  | ||||||
|                                           idx, |  | ||||||
|                                         ), |  | ||||||
|                                   ), |  | ||||||
|                                 ), |  | ||||||
|                             ], |  | ||||||
|                           ); |  | ||||||
|                         }, |  | ||||||
|                       ), |                       ), | ||||||
|                     ], |                       children: [ | ||||||
|  |                         ValueListenableBuilder<Map<int, double>>( | ||||||
|  |                           valueListenable: state.attachmentProgress, | ||||||
|  |                           builder: (context, progressMap, _) { | ||||||
|  |                             return Wrap( | ||||||
|  |                               runSpacing: 8, | ||||||
|  |                               spacing: 8, | ||||||
|  |                               children: [ | ||||||
|  |                                 for ( | ||||||
|  |                                   var idx = 0; | ||||||
|  |                                   idx < attachments.length; | ||||||
|  |                                   idx++ | ||||||
|  |                                 ) | ||||||
|  |                                   SizedBox( | ||||||
|  |                                     width: 180, | ||||||
|  |                                     height: 180, | ||||||
|  |                                     child: AttachmentPreview( | ||||||
|  |                                       isCompact: true, | ||||||
|  |                                       item: attachments[idx], | ||||||
|  |                                       progress: progressMap[idx], | ||||||
|  |                                       onRequestUpload: | ||||||
|  |                                           () => ComposeLogic.uploadAttachment( | ||||||
|  |                                             ref, | ||||||
|  |                                             state, | ||||||
|  |                                             idx, | ||||||
|  |                                           ), | ||||||
|  |                                       onUpdate: | ||||||
|  |                                           (value) => | ||||||
|  |                                               ComposeLogic.updateAttachment( | ||||||
|  |                                                 state, | ||||||
|  |                                                 value, | ||||||
|  |                                                 idx, | ||||||
|  |                                               ), | ||||||
|  |                                       onDelete: | ||||||
|  |                                           () => ComposeLogic.deleteAttachment( | ||||||
|  |                                             ref, | ||||||
|  |                                             state, | ||||||
|  |                                             idx, | ||||||
|  |                                           ), | ||||||
|  |                                       onInsert: | ||||||
|  |                                           () => ComposeLogic.insertAttachment( | ||||||
|  |                                             ref, | ||||||
|  |                                             state, | ||||||
|  |                                             idx, | ||||||
|  |                                           ), | ||||||
|  |                                     ), | ||||||
|  |                                   ), | ||||||
|  |                               ], | ||||||
|  |                             ); | ||||||
|  |                           }, | ||||||
|  |                         ), | ||||||
|  |                         Gap(16), | ||||||
|  |                       ], | ||||||
|  |                     ), | ||||||
|                   ); |                   ); | ||||||
|                 }, |                 }, | ||||||
|               ), |               ), | ||||||
|   | |||||||
							
								
								
									
										242
									
								
								lib/screens/posts/post_categories_list.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										242
									
								
								lib/screens/posts/post_categories_list.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,242 @@ | |||||||
|  | import 'dart:async'; | ||||||
|  | import 'package:easy_localization/easy_localization.dart'; | ||||||
|  | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:go_router/go_router.dart'; | ||||||
|  | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
|  | import 'package:island/models/post_category.dart'; | ||||||
|  | import 'package:island/models/post_tag.dart'; | ||||||
|  | import 'package:island/pods/network.dart'; | ||||||
|  | import 'package:island/widgets/app_scaffold.dart'; | ||||||
|  | import 'package:island/widgets/response.dart'; | ||||||
|  | import 'package:material_symbols_icons/symbols.dart'; | ||||||
|  | import 'package:riverpod_paging_utils/riverpod_paging_utils.dart'; | ||||||
|  |  | ||||||
|  | // Post Categories Notifier | ||||||
|  | final postCategoriesNotifierProvider = StateNotifierProvider.autoDispose< | ||||||
|  |   PostCategoriesNotifier, | ||||||
|  |   AsyncValue<CursorPagingData<SnPostCategory>> | ||||||
|  | >((ref) { | ||||||
|  |   return PostCategoriesNotifier(ref); | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | class PostCategoriesNotifier | ||||||
|  |     extends StateNotifier<AsyncValue<CursorPagingData<SnPostCategory>>> { | ||||||
|  |   final AutoDisposeRef ref; | ||||||
|  |   static const int _pageSize = 20; | ||||||
|  |   bool _isLoading = false; | ||||||
|  |  | ||||||
|  |   PostCategoriesNotifier(this.ref) : super(const AsyncValue.loading()) { | ||||||
|  |     state = const AsyncValue.data( | ||||||
|  |       CursorPagingData(items: [], hasMore: false, nextCursor: null), | ||||||
|  |     ); | ||||||
|  |     fetch(cursor: null); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   Future<void> fetch({String? cursor}) async { | ||||||
|  |     if (_isLoading) return; | ||||||
|  |  | ||||||
|  |     _isLoading = true; | ||||||
|  |     if (cursor == null) { | ||||||
|  |       state = const AsyncValue.loading(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     try { | ||||||
|  |       final client = ref.read(apiClientProvider); | ||||||
|  |       final offset = cursor == null ? 0 : int.parse(cursor); | ||||||
|  |  | ||||||
|  |       final response = await client.get( | ||||||
|  |         '/sphere/posts/categories', | ||||||
|  |         queryParameters: { | ||||||
|  |           'offset': offset, | ||||||
|  |           'take': _pageSize, | ||||||
|  |           'order': 'usage', | ||||||
|  |         }, | ||||||
|  |       ); | ||||||
|  |  | ||||||
|  |       final data = response.data as List; | ||||||
|  |       final categories = | ||||||
|  |           data.map((json) => SnPostCategory.fromJson(json)).toList(); | ||||||
|  |       final hasMore = categories.length == _pageSize; | ||||||
|  |       final nextCursor = | ||||||
|  |           hasMore ? (offset + categories.length).toString() : null; | ||||||
|  |  | ||||||
|  |       state = AsyncValue.data( | ||||||
|  |         CursorPagingData( | ||||||
|  |           items: [...(state.value?.items ?? []), ...categories], | ||||||
|  |           hasMore: hasMore, | ||||||
|  |           nextCursor: nextCursor, | ||||||
|  |         ), | ||||||
|  |       ); | ||||||
|  |     } catch (e, stack) { | ||||||
|  |       state = AsyncValue.error(e, stack); | ||||||
|  |     } finally { | ||||||
|  |       _isLoading = false; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Post Tags Notifier | ||||||
|  | final postTagsNotifierProvider = StateNotifierProvider.autoDispose< | ||||||
|  |   PostTagsNotifier, | ||||||
|  |   AsyncValue<CursorPagingData<SnPostTag>> | ||||||
|  | >((ref) { | ||||||
|  |   return PostTagsNotifier(ref); | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | class PostTagsNotifier | ||||||
|  |     extends StateNotifier<AsyncValue<CursorPagingData<SnPostTag>>> { | ||||||
|  |   final AutoDisposeRef ref; | ||||||
|  |   static const int _pageSize = 20; | ||||||
|  |   bool _isLoading = false; | ||||||
|  |  | ||||||
|  |   PostTagsNotifier(this.ref) : super(const AsyncValue.loading()) { | ||||||
|  |     state = const AsyncValue.data( | ||||||
|  |       CursorPagingData(items: [], hasMore: false, nextCursor: null), | ||||||
|  |     ); | ||||||
|  |     fetch(cursor: null); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   Future<void> fetch({String? cursor}) async { | ||||||
|  |     if (_isLoading) return; | ||||||
|  |  | ||||||
|  |     _isLoading = true; | ||||||
|  |     if (cursor == null) { | ||||||
|  |       state = const AsyncValue.loading(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     try { | ||||||
|  |       final client = ref.read(apiClientProvider); | ||||||
|  |       final offset = cursor == null ? 0 : int.parse(cursor); | ||||||
|  |  | ||||||
|  |       final response = await client.get( | ||||||
|  |         '/sphere/posts/tags', | ||||||
|  |         queryParameters: { | ||||||
|  |           'offset': offset, | ||||||
|  |           'take': _pageSize, | ||||||
|  |           'order': 'usage', | ||||||
|  |         }, | ||||||
|  |       ); | ||||||
|  |  | ||||||
|  |       final data = response.data as List; | ||||||
|  |       final tags = data.map((json) => SnPostTag.fromJson(json)).toList(); | ||||||
|  |       final hasMore = tags.length == _pageSize; | ||||||
|  |       final nextCursor = hasMore ? (offset + tags.length).toString() : null; | ||||||
|  |  | ||||||
|  |       state = AsyncValue.data( | ||||||
|  |         CursorPagingData( | ||||||
|  |           items: [...(state.value?.items ?? []), ...tags], | ||||||
|  |           hasMore: hasMore, | ||||||
|  |           nextCursor: nextCursor, | ||||||
|  |         ), | ||||||
|  |       ); | ||||||
|  |     } catch (e, stack) { | ||||||
|  |       state = AsyncValue.error(e, stack); | ||||||
|  |     } finally { | ||||||
|  |       _isLoading = false; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class PostCategoriesListScreen extends ConsumerWidget { | ||||||
|  |   const PostCategoriesListScreen({super.key}); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|  |     final categoriesState = ref.watch(postCategoriesNotifierProvider); | ||||||
|  |  | ||||||
|  |     return AppScaffold( | ||||||
|  |       appBar: AppBar(title: const Text('categories').tr()), | ||||||
|  |       body: categoriesState.when( | ||||||
|  |         data: (data) { | ||||||
|  |           if (data.items.isEmpty) { | ||||||
|  |             return const Center(child: Text('No categories found')); | ||||||
|  |           } | ||||||
|  |           return ListView.builder( | ||||||
|  |             padding: EdgeInsets.zero, | ||||||
|  |             itemCount: data.items.length + (data.hasMore ? 1 : 0), | ||||||
|  |             itemBuilder: (context, index) { | ||||||
|  |               if (index >= data.items.length) { | ||||||
|  |                 ref | ||||||
|  |                     .read(postCategoriesNotifierProvider.notifier) | ||||||
|  |                     .fetch(cursor: data.nextCursor); | ||||||
|  |                 return const Center(child: CircularProgressIndicator()); | ||||||
|  |               } | ||||||
|  |               final category = data.items[index]; | ||||||
|  |               return ListTile( | ||||||
|  |                 leading: const Icon(Symbols.category), | ||||||
|  |                 contentPadding: EdgeInsets.symmetric(horizontal: 24), | ||||||
|  |                 trailing: const Icon(Symbols.chevron_right), | ||||||
|  |                 title: Text(category.categoryDisplayTitle), | ||||||
|  |                 subtitle: Text('postCount'.plural(category.usage)), | ||||||
|  |                 onTap: () { | ||||||
|  |                   context.pushNamed( | ||||||
|  |                     'postCategoryDetail', | ||||||
|  |                     pathParameters: {'slug': category.slug}, | ||||||
|  |                   ); | ||||||
|  |                 }, | ||||||
|  |               ); | ||||||
|  |             }, | ||||||
|  |           ); | ||||||
|  |         }, | ||||||
|  |         loading: () => const Center(child: CircularProgressIndicator()), | ||||||
|  |         error: | ||||||
|  |             (error, stack) => ResponseErrorWidget( | ||||||
|  |               error: error, | ||||||
|  |               onRetry: () => ref.invalidate(postCategoriesNotifierProvider), | ||||||
|  |             ), | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class PostTagsListScreen extends ConsumerWidget { | ||||||
|  |   const PostTagsListScreen({super.key}); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|  |     final tagsState = ref.watch(postTagsNotifierProvider); | ||||||
|  |  | ||||||
|  |     return AppScaffold( | ||||||
|  |       appBar: AppBar(title: const Text('tags').tr()), | ||||||
|  |       body: tagsState.when( | ||||||
|  |         data: (data) { | ||||||
|  |           if (data.items.isEmpty) { | ||||||
|  |             return const Center(child: Text('No tags found')); | ||||||
|  |           } | ||||||
|  |           return ListView.builder( | ||||||
|  |             padding: EdgeInsets.zero, | ||||||
|  |             itemCount: data.items.length + (data.hasMore ? 1 : 0), | ||||||
|  |             itemBuilder: (context, index) { | ||||||
|  |               if (index >= data.items.length) { | ||||||
|  |                 ref | ||||||
|  |                     .read(postTagsNotifierProvider.notifier) | ||||||
|  |                     .fetch(cursor: data.nextCursor); | ||||||
|  |                 return const Center(child: CircularProgressIndicator()); | ||||||
|  |               } | ||||||
|  |               final tag = data.items[index]; | ||||||
|  |               return ListTile( | ||||||
|  |                 title: Text(tag.name ?? '#${tag.slug}'), | ||||||
|  |                 contentPadding: EdgeInsets.symmetric(horizontal: 24), | ||||||
|  |                 leading: const Icon(Symbols.label), | ||||||
|  |                 trailing: const Icon(Symbols.chevron_right), | ||||||
|  |                 subtitle: Text('postCount'.plural(tag.usage)), | ||||||
|  |                 onTap: () { | ||||||
|  |                   context.pushNamed( | ||||||
|  |                     'postTagDetail', | ||||||
|  |                     pathParameters: {'slug': tag.slug}, | ||||||
|  |                   ); | ||||||
|  |                 }, | ||||||
|  |               ); | ||||||
|  |             }, | ||||||
|  |           ); | ||||||
|  |         }, | ||||||
|  |         loading: () => const Center(child: CircularProgressIndicator()), | ||||||
|  |         error: | ||||||
|  |             (error, stack) => ResponseErrorWidget( | ||||||
|  |               error: error, | ||||||
|  |               onRetry: () => ref.invalidate(postTagsNotifierProvider), | ||||||
|  |             ), | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -1,3 +1,4 @@ | |||||||
|  | import 'package:easy_localization/easy_localization.dart'; | ||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
| import 'package:gap/gap.dart'; | import 'package:gap/gap.dart'; | ||||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
| @@ -8,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/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: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'; | ||||||
|  |  | ||||||
| @@ -55,7 +57,10 @@ class PostDetailScreen extends HookConsumerWidget { | |||||||
|  |  | ||||||
|     return AppScaffold( |     return AppScaffold( | ||||||
|       isNoBackground: false, |       isNoBackground: false, | ||||||
|       appBar: AppBar(title: const Text('Post')), |       appBar: AppBar( | ||||||
|  |         leading: const PageBackButton(), | ||||||
|  |         title: Text('postDetail').tr(), | ||||||
|  |       ), | ||||||
|       body: postState.when( |       body: postState.when( | ||||||
|         data: (post) { |         data: (post) { | ||||||
|           return Stack( |           return Stack( | ||||||
| @@ -117,8 +122,12 @@ class PostDetailScreen extends HookConsumerWidget { | |||||||
|             ], |             ], | ||||||
|           ); |           ); | ||||||
|         }, |         }, | ||||||
|         loading: () => const Center(child: CircularProgressIndicator()), |         loading: () => ResponseLoadingWidget(), | ||||||
|         error: (e, _) => Text('Error: $e'), |         error: | ||||||
|  |             (e, _) => ResponseErrorWidget( | ||||||
|  |               error: e, | ||||||
|  |               onRetry: () => ref.invalidate(postStateProvider(id)), | ||||||
|  |             ), | ||||||
|       ), |       ), | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -51,12 +51,12 @@ class PostSearchNotifier | |||||||
|       final offset = cursor == null ? 0 : int.parse(cursor); |       final offset = cursor == null ? 0 : int.parse(cursor); | ||||||
|  |  | ||||||
|       final response = await client.get( |       final response = await client.get( | ||||||
|         '/sphere/posts/search', |         '/sphere/posts', | ||||||
|         queryParameters: { |         queryParameters: { | ||||||
|           'query': _currentQuery, |           'query': _currentQuery, | ||||||
|           'offset': offset, |           'offset': offset, | ||||||
|           'take': _pageSize, |           'take': _pageSize, | ||||||
|           'useVector': false, |           'vector': false, | ||||||
|         }, |         }, | ||||||
|       ); |       ); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -7,7 +7,7 @@ import 'package:gap/gap.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/models/publisher.dart'; | import 'package:island/models/publisher.dart'; | ||||||
| import 'package:island/models/user.dart'; | import 'package:island/models/account.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/services/color.dart'; | import 'package:island/services/color.dart'; | ||||||
| @@ -147,7 +147,11 @@ class PublisherProfileScreen extends HookConsumerWidget { | |||||||
|             ), |             ), | ||||||
|             backgroundColor: Theme.of(context).colorScheme.primary, |             backgroundColor: Theme.of(context).colorScheme.primary, | ||||||
|             offset: Offset(0, 48), |             offset: Offset(0, 48), | ||||||
|             child: ProfilePictureWidget(file: data.picture, radius: 32), |             child: ProfilePictureWidget( | ||||||
|  |               file: data.picture, | ||||||
|  |               radius: 32, | ||||||
|  |               borderRadius: data.type == 0 ? null : 12, | ||||||
|  |             ), | ||||||
|           ), |           ), | ||||||
|           onTap: () { |           onTap: () { | ||||||
|             Navigator.pop(context, true); |             Navigator.pop(context, true); | ||||||
|   | |||||||
| @@ -4,6 +4,9 @@ import 'package:island/screens/chat/chat.dart'; | |||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
| import 'package:island/models/chat.dart'; | import 'package:island/models/chat.dart'; | ||||||
| import 'package:island/services/color.dart'; | import 'package:island/services/color.dart'; | ||||||
|  | import 'package:island/services/responsive.dart'; | ||||||
|  | import 'package:island/widgets/account/status.dart'; | ||||||
|  | import 'package:island/widgets/post/post_list.dart'; | ||||||
| import 'package:palette_generator/palette_generator.dart'; | import 'package:palette_generator/palette_generator.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'; | ||||||
| @@ -78,155 +81,287 @@ class RealmDetailScreen extends HookConsumerWidget { | |||||||
|       offset: Offset(1.0, 1.0), |       offset: Offset(1.0, 1.0), | ||||||
|     ); |     ); | ||||||
|  |  | ||||||
|  |     final realmIdentity = ref.watch(realmIdentityProvider(slug)); | ||||||
|  |     final realmChatRooms = ref.watch(realmChatRoomsProvider(slug)); | ||||||
|  |  | ||||||
|  |     Widget realmDescriptionWidget(SnRealm realm) => Card( | ||||||
|  |       margin: EdgeInsets.symmetric(horizontal: 8, vertical: 4), | ||||||
|  |       child: Theme( | ||||||
|  |         data: Theme.of(context).copyWith(dividerColor: Colors.transparent), | ||||||
|  |         child: ExpansionTile( | ||||||
|  |           shape: const RoundedRectangleBorder( | ||||||
|  |             borderRadius: BorderRadius.all(Radius.circular(8)), | ||||||
|  |           ), | ||||||
|  |           collapsedShape: const RoundedRectangleBorder( | ||||||
|  |             borderRadius: BorderRadius.all(Radius.circular(8)), | ||||||
|  |           ), | ||||||
|  |           title: const Text('description').tr(), | ||||||
|  |           initiallyExpanded: | ||||||
|  |               realmIdentity.hasValue && realmIdentity.value == null, | ||||||
|  |           tilePadding: EdgeInsets.only(left: 24, right: 20), | ||||||
|  |           expandedCrossAxisAlignment: CrossAxisAlignment.stretch, | ||||||
|  |           children: [ | ||||||
|  |             Text( | ||||||
|  |               realm.description, | ||||||
|  |               style: const TextStyle(fontSize: 16), | ||||||
|  |             ).padding(horizontal: 20, bottom: 16, top: 8), | ||||||
|  |           ], | ||||||
|  |         ), | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  |     Widget realmActionWidget(SnRealm realm) => Card( | ||||||
|  |       margin: EdgeInsets.symmetric(horizontal: 8, vertical: 4), | ||||||
|  |       child: FilledButton.tonalIcon( | ||||||
|  |         onPressed: () async { | ||||||
|  |           try { | ||||||
|  |             final apiClient = ref.read(apiClientProvider); | ||||||
|  |             await apiClient.post('/sphere/realms/$slug/members/me'); | ||||||
|  |             ref.invalidate(realmIdentityProvider(slug)); | ||||||
|  |             ref.invalidate(realmsJoinedProvider); | ||||||
|  |             showSnackBar('realmJoinSuccess'.tr()); | ||||||
|  |           } catch (err) { | ||||||
|  |             showErrorAlert(err); | ||||||
|  |           } | ||||||
|  |         }, | ||||||
|  |         icon: const Icon(Symbols.add), | ||||||
|  |         label: const Text('realmJoin').tr(), | ||||||
|  |       ).padding(all: 16), | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  |     Widget realmChatRoomListWidget(SnRealm realm) => Card( | ||||||
|  |       margin: EdgeInsets.symmetric(horizontal: 8, vertical: 4), | ||||||
|  |       child: Column( | ||||||
|  |         crossAxisAlignment: CrossAxisAlignment.stretch, | ||||||
|  |         children: [ | ||||||
|  |           Text( | ||||||
|  |             'chatTabGroup', | ||||||
|  |           ).tr().bold().padding(horizontal: 24, top: 12, bottom: 4), | ||||||
|  |           realmChatRooms.when( | ||||||
|  |             loading: () => Center(child: CircularProgressIndicator()), | ||||||
|  |             error: (error, _) => Center(child: Text('Error: $error')), | ||||||
|  |             data: (rooms) { | ||||||
|  |               if (rooms.isEmpty) { | ||||||
|  |                 return Text( | ||||||
|  |                   'dataEmpty', | ||||||
|  |                 ).tr().padding(horizontal: 24, bottom: 12); | ||||||
|  |               } | ||||||
|  |               return Column( | ||||||
|  |                 children: [ | ||||||
|  |                   for (final room in rooms) | ||||||
|  |                     ChatRoomListTile( | ||||||
|  |                       room: room, | ||||||
|  |                       onTap: () { | ||||||
|  |                         context.pushNamed( | ||||||
|  |                           'chatRoom', | ||||||
|  |                           pathParameters: {'id': room.id}, | ||||||
|  |                         ); | ||||||
|  |                       }, | ||||||
|  |                     ), | ||||||
|  |                 ], | ||||||
|  |               ); | ||||||
|  |             }, | ||||||
|  |           ), | ||||||
|  |         ], | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |  | ||||||
|     return AppScaffold( |     return AppScaffold( | ||||||
|       isNoBackground: false, |       isNoBackground: false, | ||||||
|  |       appBar: | ||||||
|  |           isWideScreen(context) | ||||||
|  |               ? realmState.when( | ||||||
|  |                 data: | ||||||
|  |                     (realm) => AppBar( | ||||||
|  |                       foregroundColor: appbarColor.value, | ||||||
|  |                       leading: PageBackButton( | ||||||
|  |                         color: appbarColor.value, | ||||||
|  |                         shadows: [iconShadow], | ||||||
|  |                       ), | ||||||
|  |                       flexibleSpace: Stack( | ||||||
|  |                         children: [ | ||||||
|  |                           Positioned.fill( | ||||||
|  |                             child: | ||||||
|  |                                 realm!.background?.id != null | ||||||
|  |                                     ? CloudImageWidget( | ||||||
|  |                                       fileId: realm.background!.id, | ||||||
|  |                                     ) | ||||||
|  |                                     : Container( | ||||||
|  |                                       color: | ||||||
|  |                                           Theme.of( | ||||||
|  |                                             context, | ||||||
|  |                                           ).appBarTheme.backgroundColor, | ||||||
|  |                                     ), | ||||||
|  |                           ), | ||||||
|  |                           FlexibleSpaceBar( | ||||||
|  |                             title: Text( | ||||||
|  |                               realm.name, | ||||||
|  |                               style: TextStyle( | ||||||
|  |                                 color: | ||||||
|  |                                     appbarColor.value ?? | ||||||
|  |                                     Theme.of( | ||||||
|  |                                       context, | ||||||
|  |                                     ).appBarTheme.foregroundColor, | ||||||
|  |                                 shadows: [iconShadow], | ||||||
|  |                               ), | ||||||
|  |                             ), | ||||||
|  |                             background: Container(), | ||||||
|  |                           ), | ||||||
|  |                         ], | ||||||
|  |                       ), | ||||||
|  |                       actions: [ | ||||||
|  |                         IconButton( | ||||||
|  |                           icon: Icon(Icons.people, shadows: [iconShadow]), | ||||||
|  |                           onPressed: () { | ||||||
|  |                             showModalBottomSheet( | ||||||
|  |                               isScrollControlled: true, | ||||||
|  |                               context: context, | ||||||
|  |                               builder: | ||||||
|  |                                   (context) => | ||||||
|  |                                       _RealmMemberListSheet(realmSlug: slug), | ||||||
|  |                             ); | ||||||
|  |                           }, | ||||||
|  |                         ), | ||||||
|  |                         _RealmActionMenu( | ||||||
|  |                           realmSlug: slug, | ||||||
|  |                           iconShadow: iconShadow, | ||||||
|  |                         ), | ||||||
|  |                         const Gap(8), | ||||||
|  |                       ], | ||||||
|  |                     ), | ||||||
|  |                 error: (_, _) => AppBar(leading: PageBackButton()), | ||||||
|  |                 loading: () => AppBar(leading: PageBackButton()), | ||||||
|  |               ) | ||||||
|  |               : null, | ||||||
|       body: realmState.when( |       body: realmState.when( | ||||||
|         loading: () => const Center(child: CircularProgressIndicator()), |         loading: () => const Center(child: CircularProgressIndicator()), | ||||||
|         error: (error, _) => Center(child: Text('Error: $error')), |         error: (error, _) => Center(child: Text('Error: $error')), | ||||||
|         data: |         data: | ||||||
|             (realm) => CustomScrollView( |             (realm) => | ||||||
|               slivers: [ |                 isWideScreen(context) | ||||||
|                 SliverAppBar( |                     ? Row( | ||||||
|                   expandedHeight: 180, |                       children: [ | ||||||
|                   pinned: true, |                         Flexible( | ||||||
|                   foregroundColor: appbarColor.value, |                           flex: 3, | ||||||
|                   leading: PageBackButton( |                           child: CustomScrollView( | ||||||
|                     color: appbarColor.value, |                             slivers: [SliverPostList(realm: slug)], | ||||||
|                     shadows: [iconShadow], |                           ), | ||||||
|                   ), |                         ), | ||||||
|                   flexibleSpace: FlexibleSpaceBar( |                         Flexible( | ||||||
|                     background: |                           flex: 2, | ||||||
|                         realm!.background?.id != null |                           child: Column( | ||||||
|                             ? CloudImageWidget(fileId: realm.background!.id) |                             children: [ | ||||||
|                             : Container( |                               realmIdentity.when( | ||||||
|                               color: |                                 loading: () => const SizedBox.shrink(), | ||||||
|                                   Theme.of(context).appBarTheme.backgroundColor, |                                 error: (_, _) => const SizedBox.shrink(), | ||||||
|                             ), |                                 data: | ||||||
|                     title: Text( |                                     (identity) => Column( | ||||||
|                       realm.name, |                                       crossAxisAlignment: | ||||||
|                       style: TextStyle( |                                           CrossAxisAlignment.stretch, | ||||||
|                         color: |                                       children: [ | ||||||
|                             appbarColor.value ?? |                                         realmDescriptionWidget(realm!), | ||||||
|                             Theme.of(context).appBarTheme.foregroundColor, |                                         if (identity == null && | ||||||
|                         shadows: [iconShadow], |                                             realm.isCommunity) | ||||||
|                       ), |                                           realmActionWidget(realm) | ||||||
|                     ), |                                         else | ||||||
|                   ), |                                           const SizedBox.shrink(), | ||||||
|                   actions: [ |                                       ], | ||||||
|                     IconButton( |  | ||||||
|                       icon: Icon(Icons.people, shadows: [iconShadow]), |  | ||||||
|                       onPressed: () { |  | ||||||
|                         showModalBottomSheet( |  | ||||||
|                           isScrollControlled: true, |  | ||||||
|                           context: context, |  | ||||||
|                           builder: |  | ||||||
|                               (context) => |  | ||||||
|                                   _RealmMemberListSheet(realmSlug: slug), |  | ||||||
|                         ); |  | ||||||
|                       }, |  | ||||||
|                     ), |  | ||||||
|                     _RealmActionMenu(realmSlug: slug, iconShadow: iconShadow), |  | ||||||
|                     const Gap(8), |  | ||||||
|                   ], |  | ||||||
|                 ), |  | ||||||
|                 SliverToBoxAdapter( |  | ||||||
|                   child: ref |  | ||||||
|                       .watch(realmIdentityProvider(slug)) |  | ||||||
|                       .when( |  | ||||||
|                         loading: () => const SizedBox.shrink(), |  | ||||||
|                         error: (_, _) => const SizedBox.shrink(), |  | ||||||
|                         data: |  | ||||||
|                             (identity) => Column( |  | ||||||
|                               crossAxisAlignment: CrossAxisAlignment.stretch, |  | ||||||
|                               children: [ |  | ||||||
|                                 ExpansionTile( |  | ||||||
|                                   title: const Text('description').tr(), |  | ||||||
|                                   initiallyExpanded: identity == null, |  | ||||||
|                                   tilePadding: EdgeInsets.symmetric( |  | ||||||
|                                     horizontal: 20, |  | ||||||
|                                   ), |  | ||||||
|                                   expandedCrossAxisAlignment: |  | ||||||
|                                       CrossAxisAlignment.stretch, |  | ||||||
|                                   children: [ |  | ||||||
|                                     Text( |  | ||||||
|                                       realm.description, |  | ||||||
|                                       style: const TextStyle(fontSize: 16), |  | ||||||
|                                     ).padding( |  | ||||||
|                                       horizontal: 20, |  | ||||||
|                                       bottom: 16, |  | ||||||
|                                       top: 8, |  | ||||||
|                                     ), |                                     ), | ||||||
|                                   ], |                               ), | ||||||
|  |                               realmChatRoomListWidget(realm!), | ||||||
|  |                             ], | ||||||
|  |                           ), | ||||||
|  |                         ), | ||||||
|  |                       ], | ||||||
|  |                     ).padding(horizontal: 8, top: 8) | ||||||
|  |                     : CustomScrollView( | ||||||
|  |                       slivers: [ | ||||||
|  |                         SliverAppBar( | ||||||
|  |                           expandedHeight: 180, | ||||||
|  |                           pinned: true, | ||||||
|  |                           foregroundColor: appbarColor.value, | ||||||
|  |                           leading: PageBackButton( | ||||||
|  |                             color: appbarColor.value, | ||||||
|  |                             shadows: [iconShadow], | ||||||
|  |                           ), | ||||||
|  |                           flexibleSpace: Stack( | ||||||
|  |                             children: [ | ||||||
|  |                               Positioned.fill( | ||||||
|  |                                 child: | ||||||
|  |                                     realm!.background?.id != null | ||||||
|  |                                         ? CloudImageWidget( | ||||||
|  |                                           fileId: realm.background!.id, | ||||||
|  |                                         ) | ||||||
|  |                                         : Container( | ||||||
|  |                                           color: | ||||||
|  |                                               Theme.of( | ||||||
|  |                                                 context, | ||||||
|  |                                               ).appBarTheme.backgroundColor, | ||||||
|  |                                         ), | ||||||
|  |                               ), | ||||||
|  |                               FlexibleSpaceBar( | ||||||
|  |                                 title: Text( | ||||||
|  |                                   realm.name, | ||||||
|  |                                   style: TextStyle( | ||||||
|  |                                     color: | ||||||
|  |                                         appbarColor.value ?? | ||||||
|  |                                         Theme.of( | ||||||
|  |                                           context, | ||||||
|  |                                         ).appBarTheme.foregroundColor, | ||||||
|  |                                     shadows: [iconShadow], | ||||||
|  |                                   ), | ||||||
|                                 ), |                                 ), | ||||||
|                                 if (identity == null && realm.isCommunity) |                                 background: | ||||||
|                                   FilledButton.tonalIcon( |                                     Container(), // Empty container since background is handled by Stack | ||||||
|                                     onPressed: () async { |                               ), | ||||||
|                                       try { |                             ], | ||||||
|                                         final apiClient = ref.read( |  | ||||||
|                                           apiClientProvider, |  | ||||||
|                                         ); |  | ||||||
|                                         await apiClient.post( |  | ||||||
|                                           '/sphere/realms/$slug/members/me', |  | ||||||
|                                         ); |  | ||||||
|                                         ref.invalidate( |  | ||||||
|                                           realmIdentityProvider(slug), |  | ||||||
|                                         ); |  | ||||||
|                                         ref.invalidate(realmsJoinedProvider); |  | ||||||
|                                         showSnackBar('realmJoinSuccess'.tr()); |  | ||||||
|                                       } catch (err) { |  | ||||||
|                                         showErrorAlert(err); |  | ||||||
|                                       } |  | ||||||
|                                     }, |  | ||||||
|                                     icon: const Icon(Symbols.add), |  | ||||||
|                                     label: const Text('realmJoin').tr(), |  | ||||||
|                                   ).padding(horizontal: 16, vertical: 16) |  | ||||||
|                                 else |  | ||||||
|                                   const SizedBox.shrink(), |  | ||||||
|                               ], |  | ||||||
|                             ), |  | ||||||
|                       ), |  | ||||||
|                 ), |  | ||||||
|                 const SliverToBoxAdapter(child: Divider(height: 1)), |  | ||||||
|                 Consumer( |  | ||||||
|                   builder: (context, ref, _) { |  | ||||||
|                     final chatRooms = ref.watch(realmChatRoomsProvider(slug)); |  | ||||||
|                     return chatRooms.when( |  | ||||||
|                       loading: |  | ||||||
|                           () => const SliverToBoxAdapter( |  | ||||||
|                             child: Center(child: CircularProgressIndicator()), |  | ||||||
|                           ), |                           ), | ||||||
|                       error: |                           actions: [ | ||||||
|                           (error, _) => SliverToBoxAdapter( |                             IconButton( | ||||||
|                             child: Center(child: Text('Error: $error')), |                               icon: Icon(Icons.people, shadows: [iconShadow]), | ||||||
|                           ), |                               onPressed: () { | ||||||
|                       data: (rooms) { |                                 showModalBottomSheet( | ||||||
|                         if (rooms.isEmpty) { |                                   isScrollControlled: true, | ||||||
|                           return const SliverToBoxAdapter( |                                   context: context, | ||||||
|                             child: SizedBox.shrink(), |                                   builder: | ||||||
|                           ); |                                       (context) => _RealmMemberListSheet( | ||||||
|                         } |                                         realmSlug: slug, | ||||||
|                         return SliverList( |                                       ), | ||||||
|                           delegate: SliverChildBuilderDelegate(( |  | ||||||
|                             context, |  | ||||||
|                             index, |  | ||||||
|                           ) { |  | ||||||
|                             return ChatRoomListTile( |  | ||||||
|                               room: rooms[index], |  | ||||||
|                               onTap: () { |  | ||||||
|                                 context.pushNamed( |  | ||||||
|                                   'chatRoom', |  | ||||||
|                                   pathParameters: {'id': rooms[index].id}, |  | ||||||
|                                 ); |                                 ); | ||||||
|                               }, |                               }, | ||||||
|                             ); |                             ), | ||||||
|                           }, childCount: rooms.length), |                             _RealmActionMenu( | ||||||
|                         ); |                               realmSlug: slug, | ||||||
|                       }, |                               iconShadow: iconShadow, | ||||||
|                     ); |                             ), | ||||||
|                   }, |                             const Gap(8), | ||||||
|                 ), |                           ], | ||||||
|               ], |                         ), | ||||||
|             ), |                         SliverGap(4), | ||||||
|  |                         SliverToBoxAdapter( | ||||||
|  |                           child: realmIdentity.when( | ||||||
|  |                             loading: () => const SizedBox.shrink(), | ||||||
|  |                             error: (_, _) => const SizedBox.shrink(), | ||||||
|  |                             data: | ||||||
|  |                                 (identity) => Column( | ||||||
|  |                                   crossAxisAlignment: | ||||||
|  |                                       CrossAxisAlignment.stretch, | ||||||
|  |                                   children: [ | ||||||
|  |                                     realmDescriptionWidget(realm), | ||||||
|  |                                     if (identity == null && realm.isCommunity) | ||||||
|  |                                       realmActionWidget(realm) | ||||||
|  |                                     else | ||||||
|  |                                       const SizedBox.shrink(), | ||||||
|  |                                   ], | ||||||
|  |                                 ), | ||||||
|  |                           ), | ||||||
|  |                         ), | ||||||
|  |                         SliverToBoxAdapter( | ||||||
|  |                           child: realmChatRoomListWidget(realm), | ||||||
|  |                         ), | ||||||
|  |                         SliverPostList(realm: slug), | ||||||
|  |                       ], | ||||||
|  |                     ), | ||||||
|       ), |       ), | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| @@ -398,7 +533,11 @@ class RealmMemberListNotifier extends _$RealmMemberListNotifier | |||||||
|  |  | ||||||
|     final response = await apiClient.get( |     final response = await apiClient.get( | ||||||
|       '/sphere/realms/$realmSlug/members', |       '/sphere/realms/$realmSlug/members', | ||||||
|       queryParameters: {'offset': offset, 'take': _pageSize}, |       queryParameters: { | ||||||
|  |         'offset': offset, | ||||||
|  |         'take': _pageSize, | ||||||
|  |         'withStatus': true, | ||||||
|  |       }, | ||||||
|     ); |     ); | ||||||
|  |  | ||||||
|     final total = int.parse(response.headers.value('X-Total') ?? '0'); |     final total = int.parse(response.headers.value('X-Total') ?? '0'); | ||||||
| @@ -441,7 +580,7 @@ class RealmMemberNotifier extends StateNotifier<RealmMemberState> { | |||||||
|     try { |     try { | ||||||
|       final response = await _apiClient.get( |       final response = await _apiClient.get( | ||||||
|         '/sphere/realms/$realmSlug/members', |         '/sphere/realms/$realmSlug/members', | ||||||
|         queryParameters: {'offset': offset, 'take': take}, |         queryParameters: {'offset': offset, 'take': take, 'withStatus': true}, | ||||||
|       ); |       ); | ||||||
|  |  | ||||||
|       final total = int.parse(response.headers.value('X-Total') ?? '0'); |       final total = int.parse(response.headers.value('X-Total') ?? '0'); | ||||||
| @@ -508,146 +647,154 @@ class _RealmMemberListSheet extends HookConsumerWidget { | |||||||
|       } |       } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     Widget buildMemberListHeader() { | ||||||
|  |       return Padding( | ||||||
|  |         padding: EdgeInsets.only(top: 16, left: 20, right: 16, bottom: 12), | ||||||
|  |         child: Row( | ||||||
|  |           children: [ | ||||||
|  |             Text( | ||||||
|  |               'members'.plural(memberState.total), | ||||||
|  |               style: Theme.of(context).textTheme.headlineSmall?.copyWith( | ||||||
|  |                 fontWeight: FontWeight.w600, | ||||||
|  |                 letterSpacing: -0.5, | ||||||
|  |               ), | ||||||
|  |             ), | ||||||
|  |             const Spacer(), | ||||||
|  |             IconButton( | ||||||
|  |               icon: const Icon(Symbols.person_add), | ||||||
|  |               onPressed: invitePerson, | ||||||
|  |               style: IconButton.styleFrom(minimumSize: const Size(36, 36)), | ||||||
|  |             ), | ||||||
|  |             IconButton( | ||||||
|  |               icon: const Icon(Symbols.refresh), | ||||||
|  |               onPressed: () { | ||||||
|  |                 // Refresh both providers | ||||||
|  |                 memberNotifier.reset(); | ||||||
|  |                 memberNotifier.loadMore(); | ||||||
|  |                 ref.invalidate(memberListProvider); | ||||||
|  |               }, | ||||||
|  |             ), | ||||||
|  |             IconButton( | ||||||
|  |               icon: const Icon(Symbols.close), | ||||||
|  |               onPressed: () => Navigator.pop(context), | ||||||
|  |               style: IconButton.styleFrom(minimumSize: const Size(36, 36)), | ||||||
|  |             ), | ||||||
|  |           ], | ||||||
|  |         ), | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     Widget buildMemberListContent() { | ||||||
|  |       return Expanded( | ||||||
|  |         child: PagingHelperView( | ||||||
|  |           provider: memberListProvider, | ||||||
|  |           futureRefreshable: memberListProvider.future, | ||||||
|  |           notifierRefreshable: memberListProvider.notifier, | ||||||
|  |           contentBuilder: (data, widgetCount, endItemView) { | ||||||
|  |             return ListView.builder( | ||||||
|  |               itemCount: widgetCount, | ||||||
|  |               itemBuilder: (context, index) { | ||||||
|  |                 if (index == data.items.length) { | ||||||
|  |                   return endItemView; | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 final member = data.items[index]; | ||||||
|  |                 return ListTile( | ||||||
|  |                   contentPadding: EdgeInsets.only(left: 16, right: 12), | ||||||
|  |                   leading: ProfilePictureWidget( | ||||||
|  |                     fileId: member.account!.profile.picture?.id, | ||||||
|  |                   ), | ||||||
|  |                   title: Row( | ||||||
|  |                     spacing: 6, | ||||||
|  |                     children: [ | ||||||
|  |                       Flexible(child: Text(member.account!.nick)), | ||||||
|  |                       if (member.status != null) | ||||||
|  |                         AccountStatusLabel(status: member.status!), | ||||||
|  |                       if (member.joinedAt == null) | ||||||
|  |                         const Icon(Symbols.pending_actions, size: 20), | ||||||
|  |                     ], | ||||||
|  |                   ), | ||||||
|  |                   subtitle: Row( | ||||||
|  |                     children: [ | ||||||
|  |                       Text( | ||||||
|  |                         member.role >= 100 | ||||||
|  |                             ? 'permissionOwner' | ||||||
|  |                             : member.role >= 50 | ||||||
|  |                             ? 'permissionModerator' | ||||||
|  |                             : 'permissionMember', | ||||||
|  |                       ).tr(), | ||||||
|  |                       Text('·').bold().padding(horizontal: 6), | ||||||
|  |                       Expanded(child: Text("@${member.account!.name}")), | ||||||
|  |                     ], | ||||||
|  |                   ), | ||||||
|  |                   trailing: Row( | ||||||
|  |                     mainAxisSize: MainAxisSize.min, | ||||||
|  |                     children: [ | ||||||
|  |                       if ((realmIdentity.value?.role ?? 0) >= 50) | ||||||
|  |                         IconButton( | ||||||
|  |                           icon: const Icon(Symbols.edit), | ||||||
|  |                           onPressed: () { | ||||||
|  |                             showModalBottomSheet( | ||||||
|  |                               isScrollControlled: true, | ||||||
|  |                               context: context, | ||||||
|  |                               builder: | ||||||
|  |                                   (context) => _RealmMemberRoleSheet( | ||||||
|  |                                     realmSlug: realmSlug, | ||||||
|  |                                     member: member, | ||||||
|  |                                   ), | ||||||
|  |                             ).then((value) { | ||||||
|  |                               if (value != null) { | ||||||
|  |                                 // Refresh both providers | ||||||
|  |                                 memberNotifier.reset(); | ||||||
|  |                                 memberNotifier.loadMore(); | ||||||
|  |                                 ref.invalidate(memberListProvider); | ||||||
|  |                               } | ||||||
|  |                             }); | ||||||
|  |                           }, | ||||||
|  |                         ), | ||||||
|  |                       if ((realmIdentity.value?.role ?? 0) >= 50) | ||||||
|  |                         IconButton( | ||||||
|  |                           icon: const Icon(Symbols.delete), | ||||||
|  |                           onPressed: () { | ||||||
|  |                             showConfirmAlert( | ||||||
|  |                               'removeRealmMemberHint'.tr(), | ||||||
|  |                               'removeRealmMember'.tr(), | ||||||
|  |                             ).then((confirm) async { | ||||||
|  |                               if (confirm != true) return; | ||||||
|  |                               try { | ||||||
|  |                                 final apiClient = ref.watch(apiClientProvider); | ||||||
|  |                                 await apiClient.delete( | ||||||
|  |                                   '/sphere/realms/$realmSlug/members/${member.accountId}', | ||||||
|  |                                 ); | ||||||
|  |                                 // Refresh both providers | ||||||
|  |                                 memberNotifier.reset(); | ||||||
|  |                                 memberNotifier.loadMore(); | ||||||
|  |                                 ref.invalidate(memberListProvider); | ||||||
|  |                               } catch (err) { | ||||||
|  |                                 showErrorAlert(err); | ||||||
|  |                               } | ||||||
|  |                             }); | ||||||
|  |                           }, | ||||||
|  |                         ), | ||||||
|  |                     ], | ||||||
|  |                   ), | ||||||
|  |                 ); | ||||||
|  |               }, | ||||||
|  |             ); | ||||||
|  |           }, | ||||||
|  |         ), | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     return Container( |     return Container( | ||||||
|       constraints: BoxConstraints( |       constraints: BoxConstraints( | ||||||
|         maxHeight: MediaQuery.of(context).size.height * 0.8, |         maxHeight: MediaQuery.of(context).size.height * 0.8, | ||||||
|       ), |       ), | ||||||
|       child: Column( |       child: Column( | ||||||
|         children: [ |         children: [ | ||||||
|           Padding( |           buildMemberListHeader(), | ||||||
|             padding: EdgeInsets.only(top: 16, left: 20, right: 16, bottom: 12), |  | ||||||
|             child: Row( |  | ||||||
|               children: [ |  | ||||||
|                 Text( |  | ||||||
|                   'members'.plural(memberState.total), |  | ||||||
|                   style: Theme.of(context).textTheme.headlineSmall?.copyWith( |  | ||||||
|                     fontWeight: FontWeight.w600, |  | ||||||
|                     letterSpacing: -0.5, |  | ||||||
|                   ), |  | ||||||
|                 ), |  | ||||||
|                 const Spacer(), |  | ||||||
|                 IconButton( |  | ||||||
|                   icon: const Icon(Symbols.person_add), |  | ||||||
|                   onPressed: invitePerson, |  | ||||||
|                   style: IconButton.styleFrom(minimumSize: const Size(36, 36)), |  | ||||||
|                 ), |  | ||||||
|                 IconButton( |  | ||||||
|                   icon: const Icon(Symbols.refresh), |  | ||||||
|                   onPressed: () { |  | ||||||
|                     // Refresh both providers |  | ||||||
|                     memberNotifier.reset(); |  | ||||||
|                     memberNotifier.loadMore(); |  | ||||||
|                     ref.invalidate(memberListProvider); |  | ||||||
|                   }, |  | ||||||
|                 ), |  | ||||||
|                 IconButton( |  | ||||||
|                   icon: const Icon(Symbols.close), |  | ||||||
|                   onPressed: () => Navigator.pop(context), |  | ||||||
|                   style: IconButton.styleFrom(minimumSize: const Size(36, 36)), |  | ||||||
|                 ), |  | ||||||
|               ], |  | ||||||
|             ), |  | ||||||
|           ), |  | ||||||
|           const Divider(height: 1), |           const Divider(height: 1), | ||||||
|           Expanded( |           buildMemberListContent(), | ||||||
|             child: PagingHelperView( |  | ||||||
|               provider: memberListProvider, |  | ||||||
|               futureRefreshable: memberListProvider.future, |  | ||||||
|               notifierRefreshable: memberListProvider.notifier, |  | ||||||
|               contentBuilder: (data, widgetCount, endItemView) { |  | ||||||
|                 return ListView.builder( |  | ||||||
|                   itemCount: widgetCount, |  | ||||||
|                   itemBuilder: (context, index) { |  | ||||||
|                     if (index == data.items.length) { |  | ||||||
|                       return endItemView; |  | ||||||
|                     } |  | ||||||
|  |  | ||||||
|                     final member = data.items[index]; |  | ||||||
|                     return ListTile( |  | ||||||
|                       contentPadding: EdgeInsets.only(left: 16, right: 12), |  | ||||||
|                       leading: ProfilePictureWidget( |  | ||||||
|                         fileId: member.account!.profile.picture?.id, |  | ||||||
|                       ), |  | ||||||
|                       title: Row( |  | ||||||
|                         spacing: 6, |  | ||||||
|                         children: [ |  | ||||||
|                           Flexible(child: Text(member.account!.nick)), |  | ||||||
|                           if (member.joinedAt == null) |  | ||||||
|                             const Icon(Symbols.pending_actions, size: 20), |  | ||||||
|                         ], |  | ||||||
|                       ), |  | ||||||
|                       subtitle: Row( |  | ||||||
|                         children: [ |  | ||||||
|                           Text( |  | ||||||
|                             member.role >= 100 |  | ||||||
|                                 ? 'permissionOwner' |  | ||||||
|                                 : member.role >= 50 |  | ||||||
|                                 ? 'permissionModerator' |  | ||||||
|                                 : 'permissionMember', |  | ||||||
|                           ).tr(), |  | ||||||
|                           Text('·').bold().padding(horizontal: 6), |  | ||||||
|                           Expanded(child: Text("@${member.account!.name}")), |  | ||||||
|                         ], |  | ||||||
|                       ), |  | ||||||
|                       trailing: Row( |  | ||||||
|                         mainAxisSize: MainAxisSize.min, |  | ||||||
|                         children: [ |  | ||||||
|                           if ((realmIdentity.value?.role ?? 0) >= 50) |  | ||||||
|                             IconButton( |  | ||||||
|                               icon: const Icon(Symbols.edit), |  | ||||||
|                               onPressed: () { |  | ||||||
|                                 showModalBottomSheet( |  | ||||||
|                                   isScrollControlled: true, |  | ||||||
|                                   context: context, |  | ||||||
|                                   builder: |  | ||||||
|                                       (context) => _RealmMemberRoleSheet( |  | ||||||
|                                         realmSlug: realmSlug, |  | ||||||
|                                         member: member, |  | ||||||
|                                       ), |  | ||||||
|                                 ).then((value) { |  | ||||||
|                                   if (value != null) { |  | ||||||
|                                     // Refresh both providers |  | ||||||
|                                     memberNotifier.reset(); |  | ||||||
|                                     memberNotifier.loadMore(); |  | ||||||
|                                     ref.invalidate(memberListProvider); |  | ||||||
|                                   } |  | ||||||
|                                 }); |  | ||||||
|                               }, |  | ||||||
|                             ), |  | ||||||
|                           if ((realmIdentity.value?.role ?? 0) >= 50) |  | ||||||
|                             IconButton( |  | ||||||
|                               icon: const Icon(Symbols.delete), |  | ||||||
|                               onPressed: () { |  | ||||||
|                                 showConfirmAlert( |  | ||||||
|                                   'removeRealmMemberHint'.tr(), |  | ||||||
|                                   'removeRealmMember'.tr(), |  | ||||||
|                                 ).then((confirm) async { |  | ||||||
|                                   if (confirm != true) return; |  | ||||||
|                                   try { |  | ||||||
|                                     final apiClient = ref.watch( |  | ||||||
|                                       apiClientProvider, |  | ||||||
|                                     ); |  | ||||||
|                                     await apiClient.delete( |  | ||||||
|                                       '/sphere/realms/$realmSlug/members/${member.accountId}', |  | ||||||
|                                     ); |  | ||||||
|                                     // Refresh both providers |  | ||||||
|                                     memberNotifier.reset(); |  | ||||||
|                                     memberNotifier.loadMore(); |  | ||||||
|                                     ref.invalidate(memberListProvider); |  | ||||||
|                                   } catch (err) { |  | ||||||
|                                     showErrorAlert(err); |  | ||||||
|                                   } |  | ||||||
|                                 }); |  | ||||||
|                               }, |  | ||||||
|                             ), |  | ||||||
|                         ], |  | ||||||
|                       ), |  | ||||||
|                     ); |  | ||||||
|                   }, |  | ||||||
|                 ); |  | ||||||
|               }, |  | ||||||
|             ), |  | ||||||
|           ), |  | ||||||
|         ], |         ], | ||||||
|       ), |       ), | ||||||
|     ); |     ); | ||||||
|   | |||||||
| @@ -399,7 +399,7 @@ class _RealmChatRoomsProviderElement | |||||||
| } | } | ||||||
|  |  | ||||||
| String _$realmMemberListNotifierHash() => | String _$realmMemberListNotifierHash() => | ||||||
|     r'022bcef5a90cbae05ff23b937851afc3ef913d42'; |     r'2f88f803b2e61e7287ed8a43025173e56ff6ca3b'; | ||||||
|  |  | ||||||
| abstract class _$RealmMemberListNotifier | abstract class _$RealmMemberListNotifier | ||||||
|     extends BuildlessAutoDisposeAsyncNotifier<CursorPagingData<SnRealmMember>> { |     extends BuildlessAutoDisposeAsyncNotifier<CursorPagingData<SnRealmMember>> { | ||||||
|   | |||||||
| @@ -22,6 +22,7 @@ import 'package:island/screens/tabs.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:styled_widget/styled_widget.dart'; | import 'package:styled_widget/styled_widget.dart'; | ||||||
|  | import 'package:island/widgets/realm/realm_list_tile.dart'; | ||||||
|  |  | ||||||
| part 'realms.g.dart'; | part 'realms.g.dart'; | ||||||
|  |  | ||||||
| @@ -95,32 +96,19 @@ class RealmListScreen extends HookConsumerWidget { | |||||||
|               (value) => Column( |               (value) => Column( | ||||||
|                 children: [ |                 children: [ | ||||||
|                   Expanded( |                   Expanded( | ||||||
|                     child: ListView.builder( |                     child: ListView.separated( | ||||||
|                       padding: getTabbedPadding(context), |                       padding: EdgeInsets.only( | ||||||
|  |                         top: 8, | ||||||
|  |                         bottom: getTabbedPadding(context).bottom + 8, | ||||||
|  |                       ), | ||||||
|                       itemCount: value.length, |                       itemCount: value.length, | ||||||
|                       itemBuilder: (context, item) { |                       itemBuilder: (context, item) { | ||||||
|                         return ListTile( |                         return ConstrainedBox( | ||||||
|                           isThreeLine: true, |                           constraints: const BoxConstraints(maxWidth: 540), | ||||||
|                           leading: ProfilePictureWidget( |                           child: RealmListTile(realm: value[item]), | ||||||
|                             fileId: value[item].picture?.id, |                         ).padding(horizontal: 8).center(); | ||||||
|                             fallbackIcon: Symbols.group, |  | ||||||
|                           ), |  | ||||||
|                           title: Text(value[item].name), |  | ||||||
|                           subtitle: Text(value[item].description), |  | ||||||
|                           onTap: () { |  | ||||||
|                             context.pushNamed( |  | ||||||
|                               'realmDetail', |  | ||||||
|                               pathParameters: {'slug': value[item].slug}, |  | ||||||
|                             ); |  | ||||||
|                           }, |  | ||||||
|                           contentPadding: const EdgeInsets.only( |  | ||||||
|                             left: 16, |  | ||||||
|                             right: 14, |  | ||||||
|                             top: 8, |  | ||||||
|                             bottom: 8, |  | ||||||
|                           ), |  | ||||||
|                         ); |  | ||||||
|                       }, |                       }, | ||||||
|  |                       separatorBuilder: (_, _) => const Gap(8), | ||||||
|                     ), |                     ), | ||||||
|                   ), |                   ), | ||||||
|                 ], |                 ], | ||||||
|   | |||||||
| @@ -10,7 +10,7 @@ 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'; | ||||||
| import 'package:island/route.dart'; | import 'package:island/route.dart'; | ||||||
| import 'package:island/models/user.dart'; | import 'package:island/models/account.dart'; | ||||||
| import 'package:island/pods/websocket.dart'; | import 'package:island/pods/websocket.dart'; | ||||||
| import 'package:island/widgets/app_notification.dart'; | 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'; | ||||||
| @@ -26,7 +26,12 @@ StreamSubscription<WebSocketPacket> setupNotificationListener( | |||||||
|       final notification = SnNotification.fromJson(pkt.data!); |       final notification = SnNotification.fromJson(pkt.data!); | ||||||
|       showTopSnackBar( |       showTopSnackBar( | ||||||
|         globalOverlay.currentState!, |         globalOverlay.currentState!, | ||||||
|         NotificationCard(notification: notification), |         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; | ||||||
| @@ -53,9 +58,9 @@ StreamSubscription<WebSocketPacket> setupNotificationListener( | |||||||
|                       (Platform.isMacOS || |                       (Platform.isMacOS || | ||||||
|                           Platform.isWindows || |                           Platform.isWindows || | ||||||
|                           Platform.isLinux)) |                           Platform.isLinux)) | ||||||
|                   ? 24 |                   ? 28 | ||||||
|                   // ignore: use_build_context_synchronously |                   // ignore: use_build_context_synchronously | ||||||
|                   : MediaQuery.of(context).padding.top + 8, |                   : MediaQuery.of(context).padding.top + 16, | ||||||
|           bottom: 16, |           bottom: 16, | ||||||
|         ), |         ), | ||||||
|       ); |       ); | ||||||
| @@ -67,7 +72,7 @@ Future<void> subscribePushNotification( | |||||||
|   Dio apiClient, { |   Dio apiClient, { | ||||||
|   bool detailedErrors = false, |   bool detailedErrors = false, | ||||||
| }) async { | }) async { | ||||||
|   if (Platform.isLinux){ |   if (Platform.isLinux) { | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|   await FirebaseMessaging.instance.requestPermission( |   await FirebaseMessaging.instance.requestPermission( | ||||||
|   | |||||||
| @@ -3,33 +3,34 @@ import 'dart:convert'; | |||||||
| 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:hooks_riverpod/hooks_riverpod.dart'; | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
| import 'package:island/models/auth.dart'; | import 'package:island/models/account.dart'; | ||||||
| import 'package:island/pods/network.dart'; | import 'package:island/pods/network.dart'; | ||||||
| import 'package:island/services/responsive.dart'; | import 'package:island/services/responsive.dart'; | ||||||
|  | import 'package:island/services/udid.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'; | ||||||
| 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'; | ||||||
| 
 | 
 | ||||||
| part 'account_session_sheet.g.dart'; | part 'account_devices.g.dart'; | ||||||
| 
 | 
 | ||||||
| @riverpod | @riverpod | ||||||
| Future<List<SnAuthDevice>> authDevices(Ref ref) async { | Future<List<SnAuthDeviceWithChallenge>> authDevices(Ref ref) async { | ||||||
|   final resp = await ref |   final resp = await ref | ||||||
|       .watch(apiClientProvider) |       .watch(apiClientProvider) | ||||||
|       .get('/id/accounts/me/devices'); |       .get('/id/accounts/me/devices'); | ||||||
|   final sessionId = resp.headers.value('x-auth-session'); |   final currentId = await getUdid(); | ||||||
|   final data = |   final data = | ||||||
|       resp.data.map<SnAuthDevice>((e) { |       resp.data.map<SnAuthDeviceWithChallenge>((e) { | ||||||
|         final ele = SnAuthDevice.fromJson(e); |         final ele = SnAuthDeviceWithChallenge.fromJson(e); | ||||||
|         return ele.copyWith(isCurrent: ele.sessions.first.id == sessionId); |         return ele.copyWith(isCurrent: ele.deviceId == currentId); | ||||||
|       }).toList(); |       }).toList(); | ||||||
|   return data; |   return data; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| class _DeviceListTile extends StatelessWidget { | class _DeviceListTile extends StatelessWidget { | ||||||
|   final SnAuthDevice device; |   final SnAuthDeviceWithChallenge device; | ||||||
|   final Function(String) updateDeviceLabel; |   final Function(String) updateDeviceLabel; | ||||||
|   final Function(String) logoutDevice; |   final Function(String) logoutDevice; | ||||||
| 
 | 
 | ||||||
| @@ -57,17 +58,16 @@ class _DeviceListTile extends StatelessWidget { | |||||||
|       subtitle: Column( |       subtitle: Column( | ||||||
|         crossAxisAlignment: CrossAxisAlignment.stretch, |         crossAxisAlignment: CrossAxisAlignment.stretch, | ||||||
|         children: [ |         children: [ | ||||||
|           Text('authSessionsCount'.plural(device.sessions.length)), |  | ||||||
|           Text( |           Text( | ||||||
|             'lastActiveAt'.tr( |             'lastActiveAt'.tr( | ||||||
|               args: [ |               args: [ | ||||||
|                 DateFormat().format( |                 DateFormat().format( | ||||||
|                   device.sessions.first.lastGrantedAt.toLocal(), |                   device.challenges.first.createdAt.toLocal(), | ||||||
|                 ), |                 ), | ||||||
|               ], |               ], | ||||||
|             ), |             ), | ||||||
|           ), |           ), | ||||||
|           Text(device.sessions.first.challenge.ipAddress), |           Text(device.challenges.first.ipAddress), | ||||||
|           if (device.isCurrent) |           if (device.isCurrent) | ||||||
|             Row( |             Row( | ||||||
|               children: [ |               children: [ | ||||||
| @@ -84,7 +84,7 @@ class _DeviceListTile extends StatelessWidget { | |||||||
|             ).padding(top: 4), |             ).padding(top: 4), | ||||||
|         ], |         ], | ||||||
|       ), |       ), | ||||||
|       title: Text(device.label ?? device.sessions.first.challenge.userAgent), |       title: Text(device.deviceLabel ?? device.deviceName), | ||||||
|       trailing: |       trailing: | ||||||
|           isWideScreen(context) |           isWideScreen(context) | ||||||
|               ? Row( |               ? Row( | ||||||
| @@ -93,14 +93,13 @@ class _DeviceListTile extends StatelessWidget { | |||||||
|                   IconButton( |                   IconButton( | ||||||
|                     icon: Icon(Icons.edit), |                     icon: Icon(Icons.edit), | ||||||
|                     tooltip: 'authDeviceEditLabel'.tr(), |                     tooltip: 'authDeviceEditLabel'.tr(), | ||||||
|                     onPressed: |                     onPressed: () => updateDeviceLabel(device.deviceId), | ||||||
|                         () => updateDeviceLabel(device.sessions.first.id), |  | ||||||
|                   ), |                   ), | ||||||
|                   if (!device.isCurrent) |                   if (!device.isCurrent) | ||||||
|                     IconButton( |                     IconButton( | ||||||
|                       icon: Icon(Icons.logout), |                       icon: Icon(Icons.logout), | ||||||
|                       tooltip: 'authDeviceLogout'.tr(), |                       tooltip: 'authDeviceLogout'.tr(), | ||||||
|                       onPressed: () => logoutDevice(device.sessions.first.id), |                       onPressed: () => logoutDevice(device.deviceId), | ||||||
|                     ), |                     ), | ||||||
|                 ], |                 ], | ||||||
|               ) |               ) | ||||||
| @@ -124,7 +123,7 @@ class AccountSessionSheet extends HookConsumerWidget { | |||||||
|       if (!confirm || !context.mounted) return; |       if (!confirm || !context.mounted) return; | ||||||
|       try { |       try { | ||||||
|         final apiClient = ref.watch(apiClientProvider); |         final apiClient = ref.watch(apiClientProvider); | ||||||
|         await apiClient.delete('/id/accounts/me/sessions/$sessionId'); |         await apiClient.delete('/id/accounts/me/devices/$sessionId'); | ||||||
|         ref.invalidate(authDevicesProvider); |         ref.invalidate(authDevicesProvider); | ||||||
|       } catch (err) { |       } catch (err) { | ||||||
|         showErrorAlert(err); |         showErrorAlert(err); | ||||||
| @@ -163,7 +162,7 @@ class AccountSessionSheet extends HookConsumerWidget { | |||||||
|       try { |       try { | ||||||
|         final apiClient = ref.watch(apiClientProvider); |         final apiClient = ref.watch(apiClientProvider); | ||||||
|         await apiClient.patch( |         await apiClient.patch( | ||||||
|           '/accounts/me/sessions/$sessionId/label', |           '/id/accounts/me/devices/$sessionId/label', | ||||||
|           data: jsonEncode(label), |           data: jsonEncode(label), | ||||||
|         ); |         ); | ||||||
|         ref.invalidate(authDevicesProvider); |         ref.invalidate(authDevicesProvider); | ||||||
| @@ -194,7 +193,7 @@ class AccountSessionSheet extends HookConsumerWidget { | |||||||
|                     ); |                     ); | ||||||
|                   } else { |                   } else { | ||||||
|                     return Dismissible( |                     return Dismissible( | ||||||
|                       key: Key('device-${device.sessions.first.id}'), |                       key: Key('device-${device.id}'), | ||||||
|                       direction: |                       direction: | ||||||
|                           device.isCurrent |                           device.isCurrent | ||||||
|                               ? DismissDirection.startToEnd |                               ? DismissDirection.startToEnd | ||||||
| @@ -213,7 +212,7 @@ class AccountSessionSheet extends HookConsumerWidget { | |||||||
|                       ), |                       ), | ||||||
|                       confirmDismiss: (direction) async { |                       confirmDismiss: (direction) async { | ||||||
|                         if (direction == DismissDirection.startToEnd) { |                         if (direction == DismissDirection.startToEnd) { | ||||||
|                           updateDeviceLabel(device.sessions.first.id); |                           updateDeviceLabel(device.deviceId); | ||||||
|                           return false; |                           return false; | ||||||
|                         } else { |                         } else { | ||||||
|                           final confirm = await showConfirmAlert( |                           final confirm = await showConfirmAlert( | ||||||
| @@ -221,7 +220,7 @@ class AccountSessionSheet extends HookConsumerWidget { | |||||||
|                             'authDeviceLogout'.tr(), |                             'authDeviceLogout'.tr(), | ||||||
|                           ); |                           ); | ||||||
|                           if (confirm && context.mounted) { |                           if (confirm && context.mounted) { | ||||||
|                             logoutDevice(device.sessions.first.id); |                             logoutDevice(device.deviceId); | ||||||
|                           } |                           } | ||||||
|                           return false; // Don't dismiss |                           return false; // Don't dismiss | ||||||
|                         } |                         } | ||||||
| @@ -1,17 +1,17 @@ | |||||||
| // GENERATED CODE - DO NOT MODIFY BY HAND | // GENERATED CODE - DO NOT MODIFY BY HAND | ||||||
| 
 | 
 | ||||||
| part of 'account_session_sheet.dart'; | part of 'account_devices.dart'; | ||||||
| 
 | 
 | ||||||
| // ************************************************************************** | // ************************************************************************** | ||||||
| // RiverpodGenerator | // RiverpodGenerator | ||||||
| // ************************************************************************** | // ************************************************************************** | ||||||
| 
 | 
 | ||||||
| String _$authDevicesHash() => r'8bc41a1ffc37df8e757c977b4ddae11db8faaeb5'; | String _$authDevicesHash() => r'feb19238f759921e51c888f8b443a3d7761e68da'; | ||||||
| 
 | 
 | ||||||
| /// See also [authDevices]. | /// See also [authDevices]. | ||||||
| @ProviderFor(authDevices) | @ProviderFor(authDevices) | ||||||
| final authDevicesProvider = | final authDevicesProvider = | ||||||
|     AutoDisposeFutureProvider<List<SnAuthDevice>>.internal( |     AutoDisposeFutureProvider<List<SnAuthDeviceWithChallenge>>.internal( | ||||||
|       authDevices, |       authDevices, | ||||||
|       name: r'authDevicesProvider', |       name: r'authDevicesProvider', | ||||||
|       debugGetCreateSourceHash: |       debugGetCreateSourceHash: | ||||||
| @@ -24,6 +24,7 @@ final authDevicesProvider = | |||||||
| 
 | 
 | ||||||
| @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 AuthDevicesRef = AutoDisposeFutureProviderRef<List<SnAuthDevice>>; | typedef AuthDevicesRef = | ||||||
|  |     AutoDisposeFutureProviderRef<List<SnAuthDeviceWithChallenge>>; | ||||||
| // 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 | ||||||
| @@ -1,7 +1,7 @@ | |||||||
| import 'package:easy_localization/easy_localization.dart'; | import 'package:easy_localization/easy_localization.dart'; | ||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
| import 'package:gap/gap.dart'; | import 'package:gap/gap.dart'; | ||||||
| import 'package:island/models/user.dart'; | import 'package:island/models/account.dart'; | ||||||
| import 'package:island/models/wallet.dart'; | import 'package:island/models/wallet.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'; | ||||||
|   | |||||||
| @@ -4,7 +4,7 @@ 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'; | ||||||
| import 'package:island/models/user.dart'; | import 'package:island/models/account.dart'; | ||||||
| import 'package:island/pods/network.dart'; | import 'package:island/pods/network.dart'; | ||||||
| import 'package:island/widgets/content/cloud_files.dart'; | import 'package:island/widgets/content/cloud_files.dart'; | ||||||
| import 'package:riverpod_annotation/riverpod_annotation.dart'; | import 'package:riverpod_annotation/riverpod_annotation.dart'; | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| 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:island/models/user.dart'; | import 'package:island/models/account.dart'; | ||||||
| import 'package:island/models/badge.dart'; | import 'package:island/models/badge.dart'; | ||||||
|  |  | ||||||
| class BadgeList extends StatelessWidget { | class BadgeList extends StatelessWidget { | ||||||
|   | |||||||
| @@ -2,7 +2,7 @@ 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:hooks_riverpod/hooks_riverpod.dart'; | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
| import 'package:island/models/user.dart'; | import 'package:island/models/account.dart'; | ||||||
| import 'package:island/pods/network.dart'; | import 'package:island/pods/network.dart'; | ||||||
| import 'package:island/screens/account/profile.dart'; | import 'package:island/screens/account/profile.dart'; | ||||||
| import 'package:island/services/time.dart'; | import 'package:island/services/time.dart'; | ||||||
| @@ -158,3 +158,42 @@ class AccountStatusWidget extends HookConsumerWidget { | |||||||
|     ).opacity((status.value?.isCustomized ?? false) ? 1 : 0.85); |     ).opacity((status.value?.isCustomized ?? false) ? 1 : 0.85); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | class AccountStatusLabel extends StatelessWidget { | ||||||
|  |   final SnAccountStatus status; | ||||||
|  |   final TextStyle? style; | ||||||
|  |   final int maxLines; | ||||||
|  |   final TextOverflow overflow; | ||||||
|  |  | ||||||
|  |   const AccountStatusLabel({ | ||||||
|  |     super.key, | ||||||
|  |     required this.status, | ||||||
|  |     this.style, | ||||||
|  |     this.maxLines = 1, | ||||||
|  |     this.overflow = TextOverflow.ellipsis, | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context) { | ||||||
|  |     return Row( | ||||||
|  |       mainAxisSize: MainAxisSize.min, | ||||||
|  |       crossAxisAlignment: CrossAxisAlignment.center, | ||||||
|  |       children: [ | ||||||
|  |         Icon( | ||||||
|  |           Symbols.circle, | ||||||
|  |           fill: 1, | ||||||
|  |           color: status.isOnline ? Colors.green : Colors.grey, | ||||||
|  |           size: 14, | ||||||
|  |         ).padding(right: 4), | ||||||
|  |         Flexible( | ||||||
|  |           child: Text( | ||||||
|  |             status.label, | ||||||
|  |             style: style, | ||||||
|  |             maxLines: maxLines, | ||||||
|  |             overflow: overflow, | ||||||
|  |           ).fontSize(13), | ||||||
|  |         ), | ||||||
|  |       ], | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
|   | |||||||
| @@ -4,7 +4,7 @@ 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:gap/gap.dart'; | ||||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
| import 'package:island/models/user.dart'; | import 'package:island/models/account.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/widgets/account/status.dart'; | import 'package:island/widgets/account/status.dart'; | ||||||
|   | |||||||
| @@ -11,7 +11,12 @@ export 'content/alert.native.dart' | |||||||
| void showSnackBar(String message, {SnackBarAction? action}) { | void showSnackBar(String message, {SnackBarAction? action}) { | ||||||
|   showTopSnackBar( |   showTopSnackBar( | ||||||
|     globalOverlay.currentState!, |     globalOverlay.currentState!, | ||||||
|     Card(child: Text(message).padding(horizontal: 20, vertical: 16)), |     ConstrainedBox( | ||||||
|  |       constraints: const BoxConstraints(maxWidth: 480), | ||||||
|  |       child: Center( | ||||||
|  |         child: Card(child: Text(message).padding(horizontal: 20, vertical: 16)), | ||||||
|  |       ), | ||||||
|  |     ), | ||||||
|     snackBarPosition: SnackBarPosition.bottom, |     snackBarPosition: SnackBarPosition.bottom, | ||||||
|   ); |   ); | ||||||
| } | } | ||||||
| @@ -69,7 +74,7 @@ void showLoadingModal(BuildContext context) { | |||||||
|                 child: Column( |                 child: Column( | ||||||
|                   mainAxisSize: MainAxisSize.min, |                   mainAxisSize: MainAxisSize.min, | ||||||
|                   children: [ |                   children: [ | ||||||
|                     CircularProgressIndicator(year2023: true), |                     CircularProgressIndicator(year2023: false), | ||||||
|                     const Gap(24), |                     const Gap(24), | ||||||
|                     Text('loading'.tr()), |                     Text('loading'.tr()), | ||||||
|                   ], |                   ], | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
| import 'package:island/models/user.dart'; | import 'package:island/models/account.dart'; | ||||||
| import 'package:island/widgets/content/cloud_files.dart'; | import 'package:island/widgets/content/cloud_files.dart'; | ||||||
| import 'package:material_symbols_icons/material_symbols_icons.dart'; | import 'package:material_symbols_icons/material_symbols_icons.dart'; | ||||||
| import 'package:styled_widget/styled_widget.dart'; | import 'package:styled_widget/styled_widget.dart'; | ||||||
|   | |||||||
| @@ -235,7 +235,11 @@ class PageBackButton extends StatelessWidget { | |||||||
|     return IconButton( |     return IconButton( | ||||||
|       onPressed: () { |       onPressed: () { | ||||||
|         onWillPop?.call(); |         onWillPop?.call(); | ||||||
|         context.pop(); |         if (context.canPop()) { | ||||||
|  |           context.pop(); | ||||||
|  |         } else { | ||||||
|  |           context.go('/'); | ||||||
|  |         } | ||||||
|       }, |       }, | ||||||
|       icon: Icon( |       icon: Icon( | ||||||
|         color: color, |         color: color, | ||||||
|   | |||||||
| @@ -50,6 +50,6 @@ class AppWrapper extends HookConsumerWidget { | |||||||
|       } |       } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     return TourTriggerWidget(child: child); |     return TourTriggerWidget(key: UniqueKey(), child: child); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user