Compare commits
	
		
			85 Commits
		
	
	
		
			3.1.0+116
			...
			f478ea8b84
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| f478ea8b84 | |||
| 0f481aff5b | |||
| 7a31663310 | |||
| 0239c53c04 | |||
| 16987c758e | |||
| 3a36915140 | |||
| 4bde708878 | |||
| 2f0cf560f8 | |||
| cf355a95fd | |||
| 2f43073172 | |||
| 8236d31ecc | |||
| 459a7dade0 | |||
| e6000a660a | |||
| 75abaac205 | |||
| 603d5c3f73 | |||
| 4e4bd99598 | |||
| d1fbe5f15e | |||
| c061ef2132 | |||
| c378309bdd | |||
| b2c5d64fc5 | |||
|  | 5371637b16 | ||
| c5cbf0af37 | |||
| 1a31e22450 | |||
|  | 49db54529d | ||
| 8e0c0c6054 | |||
| f3d1183076 | |||
| a9f7f0cce0 | |||
| f2943f8411 | |||
| 808e7dcffa | |||
| 9bed4fa6fb | |||
| e6255a340b | |||
| 78bf319fb7 | |||
| 36a966d582 | |||
| f72b268d36 | |||
| 44ef31034e | |||
| 229dc2186f | |||
| a2f9a1efb4 | |||
|  | 823e3c5de6 | ||
|  | faac7bac35 | ||
| 1fac1bfe02 | |||
| 9394b1d9c8 | |||
| 43dd13bac4 | |||
| 65bc372103 | |||
| 6558854a7a | |||
| 892035ab27 | |||
| 87ae8d2ff4 | |||
| 15c2dbaa0d | |||
| 6b3338b885 | |||
| bb00b1bc6a | |||
| 5e1a15ada2 | |||
| 9bdf8ba346 | |||
| 204c087f29 | |||
| 1def3e1895 | |||
| 550c74e544 | |||
| a39565f012 | |||
| aa9755e6a7 | |||
| b25e8d661a | |||
| 4b253ac3ec | |||
| 5d1b875d3c | |||
| e2e103fa67 | |||
| 43c90da4e3 | |||
| fa210dd98f | |||
| 43d9ca92bf | |||
| 5e592c143f | |||
| 0c59816f26 | |||
| 19c2457895 | |||
| af8d87857e | |||
| d05f63a36a | |||
| e2dc520012 | |||
| cff9c15e31 | |||
| f00135c4bf | |||
| 30b8a6c30f | |||
| b9c4ee31b1 | |||
| 87870af866 | |||
| b83cb0fb0b | |||
| 7fd1fe34e5 | |||
| 1c18330891 | |||
| d320879ad0 | |||
| 950150e119 | |||
| 3c4a9767e1 | |||
| 5df2445f3f | |||
| 56543d7b4c | |||
| 4c6fea1242 | |||
| fff43de9e3 | |||
| b31a915544 | 
							
								
								
									
										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. | ||||||
| @@ -51,6 +52,12 @@ android { | |||||||
|     buildTypes { |     buildTypes { | ||||||
|         release { |         release { | ||||||
|             signingConfig = signingConfigs.getByName("release") |             signingConfig = signingConfigs.getByName("release") | ||||||
|  |  | ||||||
|  |             isMinifyEnabled = true | ||||||
|  |             proguardFiles( | ||||||
|  |                 getDefaultProguardFile("proguard-android-optimize.txt"), | ||||||
|  |                 "proguard-rules.pro" | ||||||
|  |             ) | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -58,7 +65,7 @@ android { | |||||||
| dependencies { | dependencies { | ||||||
|     implementation("com.google.android.material:material:1.12.0") |     implementation("com.google.android.material:material:1.12.0") | ||||||
|     implementation("com.github.bumptech.glide:glide:4.16.0") |     implementation("com.github.bumptech.glide:glide:4.16.0") | ||||||
|     implementation("com.squareup.okhttp3:okhttp:4.12.0") |     implementation("com.squareup.okhttp3:okhttp:5.1.0") | ||||||
| } | } | ||||||
|  |  | ||||||
| flutter { | flutter { | ||||||
|   | |||||||
| @@ -12,7 +12,12 @@ | |||||||
|           "package_name": "dev.solsynth.solian" |           "package_name": "dev.solsynth.solian" | ||||||
|         } |         } | ||||||
|       }, |       }, | ||||||
|       "oauth_client": [], |       "oauth_client": [ | ||||||
|  |         { | ||||||
|  |           "client_id": "961776991058-963m1qin2vtp8fv693b5fdrab5hmpl89.apps.googleusercontent.com", | ||||||
|  |           "client_type": 3 | ||||||
|  |         } | ||||||
|  |       ], | ||||||
|       "api_key": [ |       "api_key": [ | ||||||
|         { |         { | ||||||
|           "current_key": "AIzaSyDvFNudXYs29uDtcCv6pFR8h5tXBs90FYk" |           "current_key": "AIzaSyDvFNudXYs29uDtcCv6pFR8h5tXBs90FYk" | ||||||
| @@ -20,7 +25,20 @@ | |||||||
|       ], |       ], | ||||||
|       "services": { |       "services": { | ||||||
|         "appinvite_service": { |         "appinvite_service": { | ||||||
|           "other_platform_oauth_client": [] |           "other_platform_oauth_client": [ | ||||||
|  |             { | ||||||
|  |               "client_id": "961776991058-963m1qin2vtp8fv693b5fdrab5hmpl89.apps.googleusercontent.com", | ||||||
|  |               "client_type": 3 | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |               "client_id": "961776991058-stt7et4qvn3cpscl4r61gl1hnlatqkig.apps.googleusercontent.com", | ||||||
|  |               "client_type": 2, | ||||||
|  |               "ios_info": { | ||||||
|  |                 "bundle_id": "dev.solsynth.solian", | ||||||
|  |                 "app_store_id": "6499032345" | ||||||
|  |               } | ||||||
|  |             } | ||||||
|  |           ] | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|   | |||||||
							
								
								
									
										5
									
								
								android/app/proguard-rules.pro
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								android/app/proguard-rules.pro
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | # JNI Zero initialization (required for WebRTC native method registration) | ||||||
|  | -keep class livekit.org.jni_zero.JniInit { | ||||||
|  |     # Keep the init method un-obfuscated for native code callback | ||||||
|  |     private static java.lang.Object[] init(); | ||||||
|  | } | ||||||
							
								
								
									
										
											BIN
										
									
								
								android/app/src/main/res/drawable/ic_notification.png
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								android/app/src/main/res/drawable/ic_notification.png
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 70 KiB | 
| @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME | |||||||
| distributionPath=wrapper/dists | distributionPath=wrapper/dists | ||||||
| zipStoreBase=GRADLE_USER_HOME | zipStoreBase=GRADLE_USER_HOME | ||||||
| zipStorePath=wrapper/dists | zipStorePath=wrapper/dists | ||||||
| distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-all.zip | distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-all.zip | ||||||
|   | |||||||
| @@ -18,11 +18,12 @@ pluginManagement { | |||||||
|  |  | ||||||
| plugins { | plugins { | ||||||
|     id("dev.flutter.flutter-plugin-loader") version "1.0.0" |     id("dev.flutter.flutter-plugin-loader") version "1.0.0" | ||||||
|     id("com.android.application") version "8.10.1" 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 "1.8.22" apply false |     id("org.jetbrains.kotlin.android") version("2.2.0") apply false | ||||||
| } | } | ||||||
|  |  | ||||||
| include(":app") | include(":app") | ||||||
|   | |||||||
| @@ -144,6 +144,7 @@ | |||||||
|     "other": "{} attachments" |     "other": "{} attachments" | ||||||
|   }, |   }, | ||||||
|   "edited": "Edited", |   "edited": "Edited", | ||||||
|  |   "editedAt": "Edited at {}", | ||||||
|   "addVideo": "Add video", |   "addVideo": "Add video", | ||||||
|   "addPhoto": "Add photo", |   "addPhoto": "Add photo", | ||||||
|   "addAudio": "Add audio", |   "addAudio": "Add audio", | ||||||
| @@ -572,6 +573,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", | ||||||
| @@ -705,6 +707,7 @@ | |||||||
|   "copyToClipboardTooltip": "Copy to clipboard", |   "copyToClipboardTooltip": "Copy to clipboard", | ||||||
|   "postForwardingTo": "Forwarding to", |   "postForwardingTo": "Forwarding to", | ||||||
|   "postReplyingTo": "Replying to", |   "postReplyingTo": "Replying to", | ||||||
|  |   "postReplyPlaceholder": "Post your reply", | ||||||
|   "postEditing": "You are editing an existing post", |   "postEditing": "You are editing an existing post", | ||||||
|   "postArticle": "Article", |   "postArticle": "Article", | ||||||
|   "aboutDeviceName": "Device Name", |   "aboutDeviceName": "Device Name", | ||||||
| @@ -758,8 +761,80 @@ | |||||||
|   "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", | ||||||
|   "operationFailed": "Operation failed: {}" |   "operationFailed": "Operation failed: {}", | ||||||
|  |   "stickerMarketplace": "Sticker Marketplace", | ||||||
|  |   "stickerPackAdded": "Sticker pack added to your collection", | ||||||
|  |   "stickerPackRemoved": "Sticker pack removed from your collection", | ||||||
|  |   "addPack": "Add Pack", | ||||||
|  |   "removePack": "Remove Pack", | ||||||
|  |   "browseAndAddStickers": "Browse and add sticker packs", | ||||||
|  |   "stickerPack": "Sticker Pack", | ||||||
|  |   "postCategoryTechnology": "Technology", | ||||||
|  |   "postCategoryTravel": "Travel", | ||||||
|  |   "postCategoryFood": "Food", | ||||||
|  |   "postCategoryHealth": "Health", | ||||||
|  |   "postCategoryScience": "Science", | ||||||
|  |   "postCategorySports": "Sports", | ||||||
|  |   "postCategoryFinance": "Finance", | ||||||
|  |   "postCategoryLife": "Life", | ||||||
|  |   "postCategoryArt": "Art", | ||||||
|  |   "postCategoryStudy": "Study", | ||||||
|  |   "postCategoryGaming": "Gaming", | ||||||
|  |   "postCategoryProgramming": "Programming", | ||||||
|  |   "postCategoryMusic": "Music", | ||||||
|  |   "links": "Links", | ||||||
|  |   "addLink": "Add link", | ||||||
|  |   "linkKey": "Link Name", | ||||||
|  |   "linkValue": "URL", | ||||||
|  |   "debugOptions": "Debug Options", | ||||||
|  |   "joinedAt": "Joined at {}", | ||||||
|  |   "searchAccounts": "Search accounts...", | ||||||
|  |   "webFeeds": "Web Feeds", | ||||||
|  |   "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)" | ||||||
| } | } | ||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1 +1 @@ | |||||||
| {"flutter":{"platforms":{"android":{"default":{"projectId":"solian-0x001","appId":"1:961776991058:android:a8d3f7995b0b8e86f4188b","fileOutput":"android/app/google-services.json"}},"ios":{"default":{"projectId":"solian-0x001","appId":"1:961776991058:ios:727229d368cc47e1f4188b","uploadDebugSymbols":false,"fileOutput":"ios/Runner/GoogleService-Info.plist"}},"macos":{"default":{"projectId":"solian-0x001","appId":"1:961776991058:ios:727229d368cc47e1f4188b","uploadDebugSymbols":false,"fileOutput":"macos/Runner/GoogleService-Info.plist"}},"dart":{"lib/firebase_options.dart":{"projectId":"solian-0x001","configurations":{"android":"1:961776991058:android:a8d3f7995b0b8e86f4188b","ios":"1:961776991058:ios:727229d368cc47e1f4188b","macos":"1:961776991058:ios:727229d368cc47e1f4188b","web":"1:961776991058:web:b91d12f2892a5609f4188b","windows":"1:961776991058:web:3a912c0eb14028e5f4188b"}}}}}} | {"flutter":{"platforms":{"android":{"default":{"projectId":"solian-0x001","appId":"1:961776991058:android:a8d3f7995b0b8e86f4188b","fileOutput":"android/app/google-services.json"}},"ios":{"default":{"projectId":"solian-0x001","appId":"1:961776991058:ios:727229d368cc47e1f4188b","uploadDebugSymbols":false,"fileOutput":"ios/Runner/GoogleService-Info.plist"}},"macos":{"default":{"projectId":"solian-0x001","appId":"1:961776991058:ios:727229d368cc47e1f4188b","uploadDebugSymbols":false,"fileOutput":"macos/Runner/GoogleService-Info.plist"}},"dart":{"lib/firebase_options.dart":{"projectId":"solian-0x001","configurations":{"android":"1:961776991058:android:a8d3f7995b0b8e86f4188b","ios":"1:961776991058:ios:727229d368cc47e1f4188b","macos":"1:961776991058:ios:727229d368cc47e1f4188b","web":"1:961776991058:web:3a912c0eb14028e5f4188b","windows":"1:961776991058:web:3a912c0eb14028e5f4188b"}}}}}} | ||||||
							
								
								
									
										135
									
								
								ios/Podfile.lock
									
									
									
									
									
								
							
							
						
						
									
										135
									
								
								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,7 +112,19 @@ 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 | ||||||
|   - flutter_inappwebview_ios (0.0.1): |   - flutter_inappwebview_ios (0.0.1): | ||||||
|     - Flutter |     - Flutter | ||||||
|     - flutter_inappwebview_ios/Core (= 0.0.1) |     - flutter_inappwebview_ios/Core (= 0.0.1) | ||||||
| @@ -99,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) | ||||||
| @@ -112,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" | ||||||
| @@ -160,6 +241,8 @@ 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.0.0): | ||||||
| @@ -178,25 +261,25 @@ PODS: | |||||||
|   - sqflite_darwin (0.0.4): |   - sqflite_darwin (0.0.4): | ||||||
|     - Flutter |     - Flutter | ||||||
|     - FlutterMacOS |     - FlutterMacOS | ||||||
|   - sqlite3 (3.50.3): |   - sqlite3 (3.50.4): | ||||||
|     - sqlite3/common (= 3.50.3) |     - sqlite3/common (= 3.50.4) | ||||||
|   - sqlite3/common (3.50.3) |   - sqlite3/common (3.50.4) | ||||||
|   - sqlite3/dbstatvtab (3.50.3): |   - sqlite3/dbstatvtab (3.50.4): | ||||||
|     - sqlite3/common |     - sqlite3/common | ||||||
|   - sqlite3/fts5 (3.50.3): |   - sqlite3/fts5 (3.50.4): | ||||||
|     - sqlite3/common |     - sqlite3/common | ||||||
|   - sqlite3/math (3.50.3): |   - sqlite3/math (3.50.4): | ||||||
|     - sqlite3/common |     - sqlite3/common | ||||||
|   - sqlite3/perf-threadsafe (3.50.3): |   - sqlite3/perf-threadsafe (3.50.4): | ||||||
|     - sqlite3/common |     - sqlite3/common | ||||||
|   - sqlite3/rtree (3.50.3): |   - sqlite3/rtree (3.50.4): | ||||||
|     - sqlite3/common |     - sqlite3/common | ||||||
|   - sqlite3/session (3.50.3): |   - sqlite3/session (3.50.4): | ||||||
|     - sqlite3/common |     - sqlite3/common | ||||||
|   - sqlite3_flutter_libs (0.0.1): |   - sqlite3_flutter_libs (0.0.1): | ||||||
|     - Flutter |     - Flutter | ||||||
|     - FlutterMacOS |     - FlutterMacOS | ||||||
|     - sqlite3 (~> 3.50.3) |     - sqlite3 (~> 3.50.4) | ||||||
|     - sqlite3/dbstatvtab |     - sqlite3/dbstatvtab | ||||||
|     - sqlite3/fts5 |     - sqlite3/fts5 | ||||||
|     - sqlite3/math |     - sqlite3/math | ||||||
| @@ -220,9 +303,12 @@ 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_inappwebview_ios (from `.symlinks/plugins/flutter_inappwebview_ios/ios`) |   - flutter_inappwebview_ios (from `.symlinks/plugins/flutter_inappwebview_ios/ios`) | ||||||
|   - flutter_keyboard_visibility (from `.symlinks/plugins/flutter_keyboard_visibility/ios`) |   - flutter_keyboard_visibility (from `.symlinks/plugins/flutter_keyboard_visibility/ios`) | ||||||
|   - flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`) |   - flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`) | ||||||
| @@ -262,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 | ||||||
| @@ -287,12 +381,18 @@ 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: | ||||||
|     :path: Flutter |     :path: Flutter | ||||||
|  |   flutter_app_update: | ||||||
|  |     :path: ".symlinks/plugins/flutter_app_update/ios" | ||||||
|   flutter_inappwebview_ios: |   flutter_inappwebview_ios: | ||||||
|     :path: ".symlinks/plugins/flutter_inappwebview_ios/ios" |     :path: ".symlinks/plugins/flutter_inappwebview_ios/ios" | ||||||
|   flutter_keyboard_visibility: |   flutter_keyboard_visibility: | ||||||
| @@ -365,13 +465,21 @@ 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_inappwebview_ios: b89ba3482b96fb25e00c967aae065701b66e9b99 |   flutter_inappwebview_ios: b89ba3482b96fb25e00c967aae065701b66e9b99 | ||||||
|   flutter_keyboard_visibility: 4625131e43015dbbe759d9b20daaf77e0e3f6619 |   flutter_keyboard_visibility: 4625131e43015dbbe759d9b20daaf77e0e3f6619 | ||||||
|   flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf |   flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf | ||||||
| @@ -381,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 | ||||||
| @@ -398,6 +508,7 @@ 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: fee1c924aa4879b882ebca2b4bce6011bcfc3d8b | ||||||
|   SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c |   SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c | ||||||
| @@ -406,8 +517,8 @@ SPEC CHECKSUMS: | |||||||
|   shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 |   shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 | ||||||
|   sign_in_with_apple: c5dcc141574c8c54d5ac99dd2163c0c72ad22418 |   sign_in_with_apple: c5dcc141574c8c54d5ac99dd2163c0c72ad22418 | ||||||
|   sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0 |   sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0 | ||||||
|   sqlite3: 83105acd294c9137c026e2da1931c30b4588ab81 |   sqlite3: 73513155ec6979715d3904ef53a8d68892d4032b | ||||||
|   sqlite3_flutter_libs: 616267f2fca40e9c6af8c5d82324e05667040b6e |   sqlite3_flutter_libs: 83f8e9f5b6554077f1d93119fe20ebaa5f3a9ef1 | ||||||
|   super_native_extensions: b763c02dc3a8fd078389f410bf15149179020cb4 |   super_native_extensions: b763c02dc3a8fd078389f410bf15149179020cb4 | ||||||
|   SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4 |   SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4 | ||||||
|   url_launcher_ios: 694010445543906933d732453a59da0a173ae33d |   url_launcher_ios: 694010445543906933d732453a59da0a173ae33d | ||||||
|   | |||||||
| @@ -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; | ||||||
|   | |||||||
| @@ -2,6 +2,12 @@ | |||||||
| <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | ||||||
| <plist version="1.0"> | <plist version="1.0"> | ||||||
| <dict> | <dict> | ||||||
|  | 	<key>CLIENT_ID</key> | ||||||
|  | 	<string>961776991058-stt7et4qvn3cpscl4r61gl1hnlatqkig.apps.googleusercontent.com</string> | ||||||
|  | 	<key>REVERSED_CLIENT_ID</key> | ||||||
|  | 	<string>com.googleusercontent.apps.961776991058-stt7et4qvn3cpscl4r61gl1hnlatqkig</string> | ||||||
|  | 	<key>ANDROID_CLIENT_ID</key> | ||||||
|  | 	<string>961776991058-r4iv9qoio57ul7utbfpgfrda2etvtch8.apps.googleusercontent.com</string> | ||||||
| 	<key>API_KEY</key> | 	<key>API_KEY</key> | ||||||
| 	<string>AIzaSyCzQIyiYKoYHTpGXhN-IjgMML8z797WVD8</string> | 	<string>AIzaSyCzQIyiYKoYHTpGXhN-IjgMML8z797WVD8</string> | ||||||
| 	<key>GCM_SENDER_ID</key> | 	<key>GCM_SENDER_ID</key> | ||||||
|   | |||||||
| @@ -34,7 +34,7 @@ class NotifyDelegate: UIResponder, UNUserNotificationCenterDelegate { | |||||||
|         } |         } | ||||||
|          |          | ||||||
|         let serverUrl = UserDefaults.standard.getServerUrl() |         let serverUrl = UserDefaults.standard.getServerUrl() | ||||||
|         let url = "\(serverUrl)/chat/\(metadata["room_id"] ?? "")/messages" |         let url = "\(serverUrl)/sphere/chat/\(metadata["room_id"] ?? "")/messages" | ||||||
|          |          | ||||||
|         let parameters: [String: Any?] = [ |         let parameters: [String: Any?] = [ | ||||||
|             "content": textResponse.userText, |             "content": textResponse.userText, | ||||||
|   | |||||||
| @@ -8,7 +8,7 @@ | |||||||
| import Foundation | import Foundation | ||||||
|  |  | ||||||
| func getAttachmentUrl(for identifier: String) -> String { | func getAttachmentUrl(for identifier: String) -> String { | ||||||
|     let serverBaseUrl = "https://nt.solian.app" |     let serverBaseUrl = "https://api.solian.app" | ||||||
|      |      | ||||||
|     return identifier.starts(with: "http") ? identifier : "\(serverBaseUrl)/files/\(identifier)" |     return identifier.starts(with: "http") ? identifier : "\(serverBaseUrl)/drive/files/\(identifier)" | ||||||
| } | } | ||||||
|   | |||||||
| @@ -29,10 +29,7 @@ class DefaultFirebaseOptions { | |||||||
|       case TargetPlatform.windows: |       case TargetPlatform.windows: | ||||||
|         return windows; |         return windows; | ||||||
|       case TargetPlatform.linux: |       case TargetPlatform.linux: | ||||||
|         throw UnsupportedError( |         return windows; | ||||||
|           'DefaultFirebaseOptions have not been configured for linux - ' |  | ||||||
|           'you can reconfigure this by running the FlutterFire CLI again.', |  | ||||||
|         ); |  | ||||||
|       default: |       default: | ||||||
|         throw UnsupportedError( |         throw UnsupportedError( | ||||||
|           'DefaultFirebaseOptions are not supported for this platform.', |           'DefaultFirebaseOptions are not supported for this platform.', | ||||||
| @@ -41,13 +38,13 @@ class DefaultFirebaseOptions { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   static const FirebaseOptions web = FirebaseOptions( |   static const FirebaseOptions web = FirebaseOptions( | ||||||
|     apiKey: 'AIzaSyBKfIQpTouj5rXnlzkEieSlbAzepm4mgJE', |     apiKey: 'AIzaSyCfgOdlcr7h8x8j0WKx_S2wXnGkOopq320', | ||||||
|     appId: '1:961776991058:web:b91d12f2892a5609f4188b', |     appId: '1:961776991058:web:3a912c0eb14028e5f4188b', | ||||||
|     messagingSenderId: '961776991058', |     messagingSenderId: '961776991058', | ||||||
|     projectId: 'solian-0x001', |     projectId: 'solian-0x001', | ||||||
|     authDomain: 'solian-0x001.firebaseapp.com', |     authDomain: 'solian-0x001.firebaseapp.com', | ||||||
|     storageBucket: 'solian-0x001.firebasestorage.app', |     storageBucket: 'solian-0x001.firebasestorage.app', | ||||||
|     measurementId: 'G-XY3HHKG0PE', |     measurementId: 'G-JD1YEG9D6F', | ||||||
|   ); |   ); | ||||||
|  |  | ||||||
|   static const FirebaseOptions android = FirebaseOptions( |   static const FirebaseOptions android = FirebaseOptions( | ||||||
| @@ -64,6 +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: '961776991058-r4iv9qoio57ul7utbfpgfrda2etvtch8.apps.googleusercontent.com', | ||||||
|  |     iosClientId: '961776991058-stt7et4qvn3cpscl4r61gl1hnlatqkig.apps.googleusercontent.com', | ||||||
|     iosBundleId: 'dev.solsynth.solian', |     iosBundleId: 'dev.solsynth.solian', | ||||||
|   ); |   ); | ||||||
|  |  | ||||||
| @@ -73,6 +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: '961776991058-r4iv9qoio57ul7utbfpgfrda2etvtch8.apps.googleusercontent.com', | ||||||
|  |     iosClientId: '961776991058-stt7et4qvn3cpscl4r61gl1hnlatqkig.apps.googleusercontent.com', | ||||||
|     iosBundleId: 'dev.solsynth.solian', |     iosBundleId: 'dev.solsynth.solian', | ||||||
|   ); |   ); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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'; | ||||||
| @@ -53,10 +54,26 @@ void main() async { | |||||||
|   try { |   try { | ||||||
|     await langdetect.initLangDetect(); |     await langdetect.initLangDetect(); | ||||||
|     await EasyLocalization.ensureInitialized(); |     await EasyLocalization.ensureInitialized(); | ||||||
|     await Firebase.initializeApp( |  | ||||||
|       options: DefaultFirebaseOptions.currentPlatform, |     if (kIsWeb || !Platform.isLinux) { | ||||||
|     ); |       await Firebase.initializeApp( | ||||||
|     FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler); |         options: DefaultFirebaseOptions.currentPlatform, | ||||||
|  |       ); | ||||||
|  |       FirebaseMessaging.onBackgroundMessage( | ||||||
|  |         _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 | ||||||
|  |       if (kIsWeb || !Platform.isWindows) { | ||||||
|  |         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!"); | ||||||
|   } catch (err) { |   } catch (err) { | ||||||
|     showErrorAlert(err); |     showErrorAlert(err); | ||||||
| @@ -165,6 +182,9 @@ class IslandApp extends HookConsumerWidget { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     useEffect(() { |     useEffect(() { | ||||||
|  |       if (!kIsWeb && Platform.isLinux) { | ||||||
|  |         return null; | ||||||
|  |       } | ||||||
|       const channel = MethodChannel('dev.solsynth.solian/notifications'); |       const channel = MethodChannel('dev.solsynth.solian/notifications'); | ||||||
|  |  | ||||||
|       Future<void> handleInitialLink() async { |       Future<void> handleInitialLink() async { | ||||||
|   | |||||||
| @@ -1,13 +1,25 @@ | |||||||
| import 'package:freezed_annotation/freezed_annotation.dart'; | import 'package:freezed_annotation/freezed_annotation.dart'; | ||||||
|  | import 'package:island/models/publisher.dart'; | ||||||
|  |  | ||||||
| part 'developer.freezed.dart'; | part 'developer.freezed.dart'; | ||||||
| part 'developer.g.dart'; | part 'developer.g.dart'; | ||||||
|  |  | ||||||
|  | @freezed | ||||||
|  | sealed class SnDeveloper with _$SnDeveloper { | ||||||
|  |   const factory SnDeveloper({ | ||||||
|  |     required String id, | ||||||
|  |     required String publisherId, | ||||||
|  |     SnPublisher? publisher, | ||||||
|  |   }) = _SnDeveloper; | ||||||
|  |  | ||||||
|  |   factory SnDeveloper.fromJson(Map<String, dynamic> json) => | ||||||
|  |       _$SnDeveloperFromJson(json); | ||||||
|  | } | ||||||
|  |  | ||||||
| @freezed | @freezed | ||||||
| sealed class DeveloperStats with _$DeveloperStats { | sealed class DeveloperStats with _$DeveloperStats { | ||||||
|   const factory DeveloperStats({ |   const factory DeveloperStats({@Default(0) int totalCustomApps}) = | ||||||
|     @Default(0) int totalCustomApps, |       _DeveloperStats; | ||||||
|   }) = _DeveloperStats; |  | ||||||
|  |  | ||||||
|   factory DeveloperStats.fromJson(Map<String, dynamic> json) => |   factory DeveloperStats.fromJson(Map<String, dynamic> json) => | ||||||
|       _$DeveloperStatsFromJson(json); |       _$DeveloperStatsFromJson(json); | ||||||
|   | |||||||
| @@ -12,6 +12,293 @@ part of 'developer.dart'; | |||||||
| // dart format off | // dart format off | ||||||
| T _$identity<T>(T value) => value; | T _$identity<T>(T value) => value; | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | mixin _$SnDeveloper { | ||||||
|  |  | ||||||
|  |  String get id; String get publisherId; SnPublisher? get publisher; | ||||||
|  | /// Create a copy of SnDeveloper | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
|  | $SnDeveloperCopyWith<SnDeveloper> get copyWith => _$SnDeveloperCopyWithImpl<SnDeveloper>(this as SnDeveloper, _$identity); | ||||||
|  |  | ||||||
|  |   /// Serializes this SnDeveloper to a JSON map. | ||||||
|  |   Map<String, dynamic> toJson(); | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | bool operator ==(Object other) { | ||||||
|  |   return identical(this, other) || (other.runtimeType == runtimeType&&other is SnDeveloper&&(identical(other.id, id) || other.id == id)&&(identical(other.publisherId, publisherId) || other.publisherId == publisherId)&&(identical(other.publisher, publisher) || other.publisher == publisher)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  | @override | ||||||
|  | int get hashCode => Object.hash(runtimeType,id,publisherId,publisher); | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | String toString() { | ||||||
|  |   return 'SnDeveloper(id: $id, publisherId: $publisherId, publisher: $publisher)'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | abstract mixin class $SnDeveloperCopyWith<$Res>  { | ||||||
|  |   factory $SnDeveloperCopyWith(SnDeveloper value, $Res Function(SnDeveloper) _then) = _$SnDeveloperCopyWithImpl; | ||||||
|  | @useResult | ||||||
|  | $Res call({ | ||||||
|  |  String id, String publisherId, SnPublisher? publisher | ||||||
|  | }); | ||||||
|  |  | ||||||
|  |  | ||||||
|  | $SnPublisherCopyWith<$Res>? get publisher; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | /// @nodoc | ||||||
|  | class _$SnDeveloperCopyWithImpl<$Res> | ||||||
|  |     implements $SnDeveloperCopyWith<$Res> { | ||||||
|  |   _$SnDeveloperCopyWithImpl(this._self, this._then); | ||||||
|  |  | ||||||
|  |   final SnDeveloper _self; | ||||||
|  |   final $Res Function(SnDeveloper) _then; | ||||||
|  |  | ||||||
|  | /// Create a copy of SnDeveloper | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? publisherId = null,Object? publisher = freezed,}) { | ||||||
|  |   return _then(_self.copyWith( | ||||||
|  | id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,publisherId: null == publisherId ? _self.publisherId : publisherId // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,publisher: freezed == publisher ? _self.publisher : publisher // ignore: cast_nullable_to_non_nullable | ||||||
|  | as SnPublisher?, | ||||||
|  |   )); | ||||||
|  | } | ||||||
|  | /// Create a copy of SnDeveloper | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
|  | $SnPublisherCopyWith<$Res>? get publisher { | ||||||
|  |     if (_self.publisher == null) { | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return $SnPublisherCopyWith<$Res>(_self.publisher!, (value) { | ||||||
|  |     return _then(_self.copyWith(publisher: value)); | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | /// Adds pattern-matching-related methods to [SnDeveloper]. | ||||||
|  | extension SnDeveloperPatterns on SnDeveloper { | ||||||
|  | /// 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( _SnDeveloper value)?  $default,{required TResult orElse(),}){ | ||||||
|  | final _that = this; | ||||||
|  | switch (_that) { | ||||||
|  | case _SnDeveloper() 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( _SnDeveloper value)  $default,){ | ||||||
|  | final _that = this; | ||||||
|  | switch (_that) { | ||||||
|  | case _SnDeveloper(): | ||||||
|  | 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( _SnDeveloper value)?  $default,){ | ||||||
|  | final _that = this; | ||||||
|  | switch (_that) { | ||||||
|  | case _SnDeveloper() when $default != null: | ||||||
|  | return $default(_that);case _: | ||||||
|  |   return null; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  | /// A variant of `when` that fallback to an `orElse` callback. | ||||||
|  | /// | ||||||
|  | /// It is equivalent to doing: | ||||||
|  | /// ```dart | ||||||
|  | /// switch (sealedClass) { | ||||||
|  | ///   case Subclass(:final field): | ||||||
|  | ///     return ...; | ||||||
|  | ///   case _: | ||||||
|  | ///     return orElse(); | ||||||
|  | /// } | ||||||
|  | /// ``` | ||||||
|  |  | ||||||
|  | @optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id,  String publisherId,  SnPublisher? publisher)?  $default,{required TResult orElse(),}) {final _that = this; | ||||||
|  | switch (_that) { | ||||||
|  | case _SnDeveloper() when $default != null: | ||||||
|  | return $default(_that.id,_that.publisherId,_that.publisher);case _: | ||||||
|  |   return orElse(); | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  | /// A `switch`-like method, using callbacks. | ||||||
|  | /// | ||||||
|  | /// As opposed to `map`, this offers destructuring. | ||||||
|  | /// It is equivalent to doing: | ||||||
|  | /// ```dart | ||||||
|  | /// switch (sealedClass) { | ||||||
|  | ///   case Subclass(:final field): | ||||||
|  | ///     return ...; | ||||||
|  | ///   case Subclass2(:final field2): | ||||||
|  | ///     return ...; | ||||||
|  | /// } | ||||||
|  | /// ``` | ||||||
|  |  | ||||||
|  | @optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id,  String publisherId,  SnPublisher? publisher)  $default,) {final _that = this; | ||||||
|  | switch (_that) { | ||||||
|  | case _SnDeveloper(): | ||||||
|  | return $default(_that.id,_that.publisherId,_that.publisher);} | ||||||
|  | } | ||||||
|  | /// A variant of `when` that fallback to returning `null` | ||||||
|  | /// | ||||||
|  | /// It is equivalent to doing: | ||||||
|  | /// ```dart | ||||||
|  | /// switch (sealedClass) { | ||||||
|  | ///   case Subclass(:final field): | ||||||
|  | ///     return ...; | ||||||
|  | ///   case _: | ||||||
|  | ///     return null; | ||||||
|  | /// } | ||||||
|  | /// ``` | ||||||
|  |  | ||||||
|  | @optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id,  String publisherId,  SnPublisher? publisher)?  $default,) {final _that = this; | ||||||
|  | switch (_that) { | ||||||
|  | case _SnDeveloper() when $default != null: | ||||||
|  | return $default(_that.id,_that.publisherId,_that.publisher);case _: | ||||||
|  |   return null; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | @JsonSerializable() | ||||||
|  |  | ||||||
|  | class _SnDeveloper implements SnDeveloper { | ||||||
|  |   const _SnDeveloper({required this.id, required this.publisherId, this.publisher}); | ||||||
|  |   factory _SnDeveloper.fromJson(Map<String, dynamic> json) => _$SnDeveloperFromJson(json); | ||||||
|  |  | ||||||
|  | @override final  String id; | ||||||
|  | @override final  String publisherId; | ||||||
|  | @override final  SnPublisher? publisher; | ||||||
|  |  | ||||||
|  | /// Create a copy of SnDeveloper | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
|  | _$SnDeveloperCopyWith<_SnDeveloper> get copyWith => __$SnDeveloperCopyWithImpl<_SnDeveloper>(this, _$identity); | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | Map<String, dynamic> toJson() { | ||||||
|  |   return _$SnDeveloperToJson(this, ); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | bool operator ==(Object other) { | ||||||
|  |   return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnDeveloper&&(identical(other.id, id) || other.id == id)&&(identical(other.publisherId, publisherId) || other.publisherId == publisherId)&&(identical(other.publisher, publisher) || other.publisher == publisher)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  | @override | ||||||
|  | int get hashCode => Object.hash(runtimeType,id,publisherId,publisher); | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | String toString() { | ||||||
|  |   return 'SnDeveloper(id: $id, publisherId: $publisherId, publisher: $publisher)'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | abstract mixin class _$SnDeveloperCopyWith<$Res> implements $SnDeveloperCopyWith<$Res> { | ||||||
|  |   factory _$SnDeveloperCopyWith(_SnDeveloper value, $Res Function(_SnDeveloper) _then) = __$SnDeveloperCopyWithImpl; | ||||||
|  | @override @useResult | ||||||
|  | $Res call({ | ||||||
|  |  String id, String publisherId, SnPublisher? publisher | ||||||
|  | }); | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @override $SnPublisherCopyWith<$Res>? get publisher; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | /// @nodoc | ||||||
|  | class __$SnDeveloperCopyWithImpl<$Res> | ||||||
|  |     implements _$SnDeveloperCopyWith<$Res> { | ||||||
|  |   __$SnDeveloperCopyWithImpl(this._self, this._then); | ||||||
|  |  | ||||||
|  |   final _SnDeveloper _self; | ||||||
|  |   final $Res Function(_SnDeveloper) _then; | ||||||
|  |  | ||||||
|  | /// Create a copy of SnDeveloper | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? publisherId = null,Object? publisher = freezed,}) { | ||||||
|  |   return _then(_SnDeveloper( | ||||||
|  | id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,publisherId: null == publisherId ? _self.publisherId : publisherId // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,publisher: freezed == publisher ? _self.publisher : publisher // ignore: cast_nullable_to_non_nullable | ||||||
|  | as SnPublisher?, | ||||||
|  |   )); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// Create a copy of SnDeveloper | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
|  | $SnPublisherCopyWith<$Res>? get publisher { | ||||||
|  |     if (_self.publisher == null) { | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return $SnPublisherCopyWith<$Res>(_self.publisher!, (value) { | ||||||
|  |     return _then(_self.copyWith(publisher: value)); | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
| /// @nodoc | /// @nodoc | ||||||
| mixin _$DeveloperStats { | mixin _$DeveloperStats { | ||||||
|  |  | ||||||
|   | |||||||
| @@ -6,6 +6,22 @@ part of 'developer.dart'; | |||||||
| // JsonSerializableGenerator | // JsonSerializableGenerator | ||||||
| // ************************************************************************** | // ************************************************************************** | ||||||
|  |  | ||||||
|  | _SnDeveloper _$SnDeveloperFromJson(Map<String, dynamic> json) => _SnDeveloper( | ||||||
|  |   id: json['id'] as String, | ||||||
|  |   publisherId: json['publisher_id'] as String, | ||||||
|  |   publisher: | ||||||
|  |       json['publisher'] == null | ||||||
|  |           ? null | ||||||
|  |           : SnPublisher.fromJson(json['publisher'] as Map<String, dynamic>), | ||||||
|  | ); | ||||||
|  |  | ||||||
|  | Map<String, dynamic> _$SnDeveloperToJson(_SnDeveloper instance) => | ||||||
|  |     <String, dynamic>{ | ||||||
|  |       'id': instance.id, | ||||||
|  |       'publisher_id': instance.publisherId, | ||||||
|  |       'publisher': instance.publisher?.toJson(), | ||||||
|  |     }; | ||||||
|  |  | ||||||
| _DeveloperStats _$DeveloperStatsFromJson(Map<String, dynamic> json) => | _DeveloperStats _$DeveloperStatsFromJson(Map<String, dynamic> json) => | ||||||
|     _DeveloperStats( |     _DeveloperStats( | ||||||
|       totalCustomApps: (json['total_custom_apps'] as num?)?.toInt() ?? 0, |       totalCustomApps: (json['total_custom_apps'] as num?)?.toInt() ?? 0, | ||||||
|   | |||||||
| @@ -3,25 +3,6 @@ import 'package:freezed_annotation/freezed_annotation.dart'; | |||||||
| part 'embed.freezed.dart'; | part 'embed.freezed.dart'; | ||||||
| part 'embed.g.dart'; | part 'embed.g.dart'; | ||||||
|  |  | ||||||
| @freezed |  | ||||||
| sealed class SnEmbedLink with _$SnEmbedLink { |  | ||||||
|   const factory SnEmbedLink({ |  | ||||||
|     @JsonKey(name: 'Type') required String type, |  | ||||||
|     @JsonKey(name: 'Url') required String url, |  | ||||||
|     @JsonKey(name: 'Title') required String title, |  | ||||||
|     @JsonKey(name: 'Description') required String? description, |  | ||||||
|     @JsonKey(name: 'ImageUrl') required String? imageUrl, |  | ||||||
|     @JsonKey(name: 'FaviconUrl') @Default("") String faviconUrl, |  | ||||||
|     @JsonKey(name: 'SiteName') @Default("") String siteName, |  | ||||||
|     @JsonKey(name: 'ContentType') required String? contentType, |  | ||||||
|     @JsonKey(name: 'Author') required String? author, |  | ||||||
|     @JsonKey(name: 'PublishedDate') required DateTime? publishedDate, |  | ||||||
|   }) = _SnEmbedLink; |  | ||||||
|  |  | ||||||
|   factory SnEmbedLink.fromJson(Map<String, dynamic> json) => |  | ||||||
|       _$SnEmbedLinkFromJson(json); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| @freezed | @freezed | ||||||
| sealed class SnScrappedLink with _$SnScrappedLink { | sealed class SnScrappedLink with _$SnScrappedLink { | ||||||
|   const factory SnScrappedLink({ |   const factory SnScrappedLink({ | ||||||
|   | |||||||
| @@ -12,290 +12,6 @@ part of 'embed.dart'; | |||||||
| // dart format off | // dart format off | ||||||
| T _$identity<T>(T value) => value; | T _$identity<T>(T value) => value; | ||||||
|  |  | ||||||
| /// @nodoc |  | ||||||
| mixin _$SnEmbedLink { |  | ||||||
|  |  | ||||||
| @JsonKey(name: 'Type') String get type;@JsonKey(name: 'Url') String get url;@JsonKey(name: 'Title') String get title;@JsonKey(name: 'Description') String? get description;@JsonKey(name: 'ImageUrl') String? get imageUrl;@JsonKey(name: 'FaviconUrl') String get faviconUrl;@JsonKey(name: 'SiteName') String get siteName;@JsonKey(name: 'ContentType') String? get contentType;@JsonKey(name: 'Author') String? get author;@JsonKey(name: 'PublishedDate') DateTime? get publishedDate; |  | ||||||
| /// Create a copy of SnEmbedLink |  | ||||||
| /// with the given fields replaced by the non-null parameter values. |  | ||||||
| @JsonKey(includeFromJson: false, includeToJson: false) |  | ||||||
| @pragma('vm:prefer-inline') |  | ||||||
| $SnEmbedLinkCopyWith<SnEmbedLink> get copyWith => _$SnEmbedLinkCopyWithImpl<SnEmbedLink>(this as SnEmbedLink, _$identity); |  | ||||||
|  |  | ||||||
|   /// Serializes this SnEmbedLink to a JSON map. |  | ||||||
|   Map<String, dynamic> toJson(); |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @override |  | ||||||
| bool operator ==(Object other) { |  | ||||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is SnEmbedLink&&(identical(other.type, type) || other.type == type)&&(identical(other.url, url) || other.url == url)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)&&(identical(other.imageUrl, imageUrl) || other.imageUrl == imageUrl)&&(identical(other.faviconUrl, faviconUrl) || other.faviconUrl == faviconUrl)&&(identical(other.siteName, siteName) || other.siteName == siteName)&&(identical(other.contentType, contentType) || other.contentType == contentType)&&(identical(other.author, author) || other.author == author)&&(identical(other.publishedDate, publishedDate) || other.publishedDate == publishedDate)); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| @JsonKey(includeFromJson: false, includeToJson: false) |  | ||||||
| @override |  | ||||||
| int get hashCode => Object.hash(runtimeType,type,url,title,description,imageUrl,faviconUrl,siteName,contentType,author,publishedDate); |  | ||||||
|  |  | ||||||
| @override |  | ||||||
| String toString() { |  | ||||||
|   return 'SnEmbedLink(type: $type, url: $url, title: $title, description: $description, imageUrl: $imageUrl, faviconUrl: $faviconUrl, siteName: $siteName, contentType: $contentType, author: $author, publishedDate: $publishedDate)'; |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /// @nodoc |  | ||||||
| abstract mixin class $SnEmbedLinkCopyWith<$Res>  { |  | ||||||
|   factory $SnEmbedLinkCopyWith(SnEmbedLink value, $Res Function(SnEmbedLink) _then) = _$SnEmbedLinkCopyWithImpl; |  | ||||||
| @useResult |  | ||||||
| $Res call({ |  | ||||||
| @JsonKey(name: 'Type') String type,@JsonKey(name: 'Url') String url,@JsonKey(name: 'Title') String title,@JsonKey(name: 'Description') String? description,@JsonKey(name: 'ImageUrl') String? imageUrl,@JsonKey(name: 'FaviconUrl') String faviconUrl,@JsonKey(name: 'SiteName') String siteName,@JsonKey(name: 'ContentType') String? contentType,@JsonKey(name: 'Author') String? author,@JsonKey(name: 'PublishedDate') DateTime? publishedDate |  | ||||||
| }); |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| } |  | ||||||
| /// @nodoc |  | ||||||
| class _$SnEmbedLinkCopyWithImpl<$Res> |  | ||||||
|     implements $SnEmbedLinkCopyWith<$Res> { |  | ||||||
|   _$SnEmbedLinkCopyWithImpl(this._self, this._then); |  | ||||||
|  |  | ||||||
|   final SnEmbedLink _self; |  | ||||||
|   final $Res Function(SnEmbedLink) _then; |  | ||||||
|  |  | ||||||
| /// Create a copy of SnEmbedLink |  | ||||||
| /// 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,}) { |  | ||||||
|   return _then(_self.copyWith( |  | ||||||
| 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,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?,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,siteName: null == 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?,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 DateTime?, |  | ||||||
|   )); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| /// Adds pattern-matching-related methods to [SnEmbedLink]. |  | ||||||
| extension SnEmbedLinkPatterns on SnEmbedLink { |  | ||||||
| /// 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( _SnEmbedLink value)?  $default,{required TResult orElse(),}){ |  | ||||||
| final _that = this; |  | ||||||
| switch (_that) { |  | ||||||
| case _SnEmbedLink() 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( _SnEmbedLink value)  $default,){ |  | ||||||
| final _that = this; |  | ||||||
| switch (_that) { |  | ||||||
| case _SnEmbedLink(): |  | ||||||
| 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( _SnEmbedLink value)?  $default,){ |  | ||||||
| final _that = this; |  | ||||||
| switch (_that) { |  | ||||||
| case _SnEmbedLink() 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(@JsonKey(name: 'Type')  String type, @JsonKey(name: 'Url')  String url, @JsonKey(name: 'Title')  String title, @JsonKey(name: 'Description')  String? description, @JsonKey(name: 'ImageUrl')  String? imageUrl, @JsonKey(name: 'FaviconUrl')  String faviconUrl, @JsonKey(name: 'SiteName')  String siteName, @JsonKey(name: 'ContentType')  String? contentType, @JsonKey(name: 'Author')  String? author, @JsonKey(name: 'PublishedDate')  DateTime? publishedDate)?  $default,{required TResult orElse(),}) {final _that = this; |  | ||||||
| switch (_that) { |  | ||||||
| case _SnEmbedLink() 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 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(@JsonKey(name: 'Type')  String type, @JsonKey(name: 'Url')  String url, @JsonKey(name: 'Title')  String title, @JsonKey(name: 'Description')  String? description, @JsonKey(name: 'ImageUrl')  String? imageUrl, @JsonKey(name: 'FaviconUrl')  String faviconUrl, @JsonKey(name: 'SiteName')  String siteName, @JsonKey(name: 'ContentType')  String? contentType, @JsonKey(name: 'Author')  String? author, @JsonKey(name: 'PublishedDate')  DateTime? publishedDate)  $default,) {final _that = this; |  | ||||||
| switch (_that) { |  | ||||||
| case _SnEmbedLink(): |  | ||||||
| return $default(_that.type,_that.url,_that.title,_that.description,_that.imageUrl,_that.faviconUrl,_that.siteName,_that.contentType,_that.author,_that.publishedDate);} |  | ||||||
| } |  | ||||||
| /// 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(@JsonKey(name: 'Type')  String type, @JsonKey(name: 'Url')  String url, @JsonKey(name: 'Title')  String title, @JsonKey(name: 'Description')  String? description, @JsonKey(name: 'ImageUrl')  String? imageUrl, @JsonKey(name: 'FaviconUrl')  String faviconUrl, @JsonKey(name: 'SiteName')  String siteName, @JsonKey(name: 'ContentType')  String? contentType, @JsonKey(name: 'Author')  String? author, @JsonKey(name: 'PublishedDate')  DateTime? publishedDate)?  $default,) {final _that = this; |  | ||||||
| switch (_that) { |  | ||||||
| case _SnEmbedLink() 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 null; |  | ||||||
|  |  | ||||||
| } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /// @nodoc |  | ||||||
| @JsonSerializable() |  | ||||||
|  |  | ||||||
| class _SnEmbedLink implements SnEmbedLink { |  | ||||||
|   const _SnEmbedLink({@JsonKey(name: 'Type') required this.type, @JsonKey(name: 'Url') required this.url, @JsonKey(name: 'Title') required this.title, @JsonKey(name: 'Description') required this.description, @JsonKey(name: 'ImageUrl') required this.imageUrl, @JsonKey(name: 'FaviconUrl') this.faviconUrl = "", @JsonKey(name: 'SiteName') this.siteName = "", @JsonKey(name: 'ContentType') required this.contentType, @JsonKey(name: 'Author') required this.author, @JsonKey(name: 'PublishedDate') required this.publishedDate}); |  | ||||||
|   factory _SnEmbedLink.fromJson(Map<String, dynamic> json) => _$SnEmbedLinkFromJson(json); |  | ||||||
|  |  | ||||||
| @override@JsonKey(name: 'Type') final  String type; |  | ||||||
| @override@JsonKey(name: 'Url') final  String url; |  | ||||||
| @override@JsonKey(name: 'Title') final  String title; |  | ||||||
| @override@JsonKey(name: 'Description') final  String? description; |  | ||||||
| @override@JsonKey(name: 'ImageUrl') final  String? imageUrl; |  | ||||||
| @override@JsonKey(name: 'FaviconUrl') final  String faviconUrl; |  | ||||||
| @override@JsonKey(name: 'SiteName') final  String siteName; |  | ||||||
| @override@JsonKey(name: 'ContentType') final  String? contentType; |  | ||||||
| @override@JsonKey(name: 'Author') final  String? author; |  | ||||||
| @override@JsonKey(name: 'PublishedDate') final  DateTime? publishedDate; |  | ||||||
|  |  | ||||||
| /// Create a copy of SnEmbedLink |  | ||||||
| /// with the given fields replaced by the non-null parameter values. |  | ||||||
| @override @JsonKey(includeFromJson: false, includeToJson: false) |  | ||||||
| @pragma('vm:prefer-inline') |  | ||||||
| _$SnEmbedLinkCopyWith<_SnEmbedLink> get copyWith => __$SnEmbedLinkCopyWithImpl<_SnEmbedLink>(this, _$identity); |  | ||||||
|  |  | ||||||
| @override |  | ||||||
| Map<String, dynamic> toJson() { |  | ||||||
|   return _$SnEmbedLinkToJson(this, ); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| @override |  | ||||||
| bool operator ==(Object other) { |  | ||||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnEmbedLink&&(identical(other.type, type) || other.type == type)&&(identical(other.url, url) || other.url == url)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)&&(identical(other.imageUrl, imageUrl) || other.imageUrl == imageUrl)&&(identical(other.faviconUrl, faviconUrl) || other.faviconUrl == faviconUrl)&&(identical(other.siteName, siteName) || other.siteName == siteName)&&(identical(other.contentType, contentType) || other.contentType == contentType)&&(identical(other.author, author) || other.author == author)&&(identical(other.publishedDate, publishedDate) || other.publishedDate == publishedDate)); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| @JsonKey(includeFromJson: false, includeToJson: false) |  | ||||||
| @override |  | ||||||
| int get hashCode => Object.hash(runtimeType,type,url,title,description,imageUrl,faviconUrl,siteName,contentType,author,publishedDate); |  | ||||||
|  |  | ||||||
| @override |  | ||||||
| String toString() { |  | ||||||
|   return 'SnEmbedLink(type: $type, url: $url, title: $title, description: $description, imageUrl: $imageUrl, faviconUrl: $faviconUrl, siteName: $siteName, contentType: $contentType, author: $author, publishedDate: $publishedDate)'; |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /// @nodoc |  | ||||||
| abstract mixin class _$SnEmbedLinkCopyWith<$Res> implements $SnEmbedLinkCopyWith<$Res> { |  | ||||||
|   factory _$SnEmbedLinkCopyWith(_SnEmbedLink value, $Res Function(_SnEmbedLink) _then) = __$SnEmbedLinkCopyWithImpl; |  | ||||||
| @override @useResult |  | ||||||
| $Res call({ |  | ||||||
| @JsonKey(name: 'Type') String type,@JsonKey(name: 'Url') String url,@JsonKey(name: 'Title') String title,@JsonKey(name: 'Description') String? description,@JsonKey(name: 'ImageUrl') String? imageUrl,@JsonKey(name: 'FaviconUrl') String faviconUrl,@JsonKey(name: 'SiteName') String siteName,@JsonKey(name: 'ContentType') String? contentType,@JsonKey(name: 'Author') String? author,@JsonKey(name: 'PublishedDate') DateTime? publishedDate |  | ||||||
| }); |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| } |  | ||||||
| /// @nodoc |  | ||||||
| class __$SnEmbedLinkCopyWithImpl<$Res> |  | ||||||
|     implements _$SnEmbedLinkCopyWith<$Res> { |  | ||||||
|   __$SnEmbedLinkCopyWithImpl(this._self, this._then); |  | ||||||
|  |  | ||||||
|   final _SnEmbedLink _self; |  | ||||||
|   final $Res Function(_SnEmbedLink) _then; |  | ||||||
|  |  | ||||||
| /// Create a copy of SnEmbedLink |  | ||||||
| /// 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,}) { |  | ||||||
|   return _then(_SnEmbedLink( |  | ||||||
| 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,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?,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,siteName: null == 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?,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 DateTime?, |  | ||||||
|   )); |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| /// @nodoc | /// @nodoc | ||||||
| mixin _$SnScrappedLink { | mixin _$SnScrappedLink { | ||||||
|  |  | ||||||
|   | |||||||
| @@ -6,36 +6,6 @@ part of 'embed.dart'; | |||||||
| // JsonSerializableGenerator | // JsonSerializableGenerator | ||||||
| // ************************************************************************** | // ************************************************************************** | ||||||
|  |  | ||||||
| _SnEmbedLink _$SnEmbedLinkFromJson(Map<String, dynamic> json) => _SnEmbedLink( |  | ||||||
|   type: json['Type'] as String, |  | ||||||
|   url: json['Url'] as String, |  | ||||||
|   title: json['Title'] as String, |  | ||||||
|   description: json['Description'] as String?, |  | ||||||
|   imageUrl: json['ImageUrl'] as String?, |  | ||||||
|   faviconUrl: json['FaviconUrl'] as String? ?? "", |  | ||||||
|   siteName: json['SiteName'] as String? ?? "", |  | ||||||
|   contentType: json['ContentType'] as String?, |  | ||||||
|   author: json['Author'] as String?, |  | ||||||
|   publishedDate: |  | ||||||
|       json['PublishedDate'] == null |  | ||||||
|           ? null |  | ||||||
|           : DateTime.parse(json['PublishedDate'] as String), |  | ||||||
| ); |  | ||||||
|  |  | ||||||
| Map<String, dynamic> _$SnEmbedLinkToJson(_SnEmbedLink instance) => |  | ||||||
|     <String, dynamic>{ |  | ||||||
|       'Type': instance.type, |  | ||||||
|       'Url': instance.url, |  | ||||||
|       'Title': instance.title, |  | ||||||
|       'Description': instance.description, |  | ||||||
|       'ImageUrl': instance.imageUrl, |  | ||||||
|       'FaviconUrl': instance.faviconUrl, |  | ||||||
|       'SiteName': instance.siteName, |  | ||||||
|       'ContentType': instance.contentType, |  | ||||||
|       'Author': instance.author, |  | ||||||
|       'PublishedDate': instance.publishedDate?.toIso8601String(), |  | ||||||
|     }; |  | ||||||
|  |  | ||||||
| _SnScrappedLink _$SnScrappedLinkFromJson(Map<String, dynamic> json) => | _SnScrappedLink _$SnScrappedLinkFromJson(Map<String, dynamic> json) => | ||||||
|     _SnScrappedLink( |     _SnScrappedLink( | ||||||
|       type: json['type'] as String, |       type: json['type'] as String, | ||||||
|   | |||||||
| @@ -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, | ||||||
| @@ -90,3 +90,19 @@ enum SnPollQuestionType { | |||||||
|   @JsonValue(4) |   @JsonValue(4) | ||||||
|   freeText, |   freeText, | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @freezed | ||||||
|  | sealed class SnPollAnswer with _$SnPollAnswer { | ||||||
|  |   const factory SnPollAnswer({ | ||||||
|  |     required String id, | ||||||
|  |     required Map<String, dynamic> answer, | ||||||
|  |     required String accountId, | ||||||
|  |     required String pollId, | ||||||
|  |     required DateTime createdAt, | ||||||
|  |     required DateTime updatedAt, | ||||||
|  |     required DateTime? deletedAt, | ||||||
|  |   }) = _SnPollAnswer; | ||||||
|  |  | ||||||
|  |   factory SnPollAnswer.fromJson(Map<String, dynamic> json) => | ||||||
|  |       _$SnPollAnswerFromJson(json); | ||||||
|  | } | ||||||
|   | |||||||
| @@ -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); | ||||||
| @@ -1181,6 +1181,287 @@ as int, | |||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | mixin _$SnPollAnswer { | ||||||
|  |  | ||||||
|  |  String get id; Map<String, dynamic> get answer; String get accountId; String get pollId; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; | ||||||
|  | /// Create a copy of SnPollAnswer | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
|  | $SnPollAnswerCopyWith<SnPollAnswer> get copyWith => _$SnPollAnswerCopyWithImpl<SnPollAnswer>(this as SnPollAnswer, _$identity); | ||||||
|  |  | ||||||
|  |   /// Serializes this SnPollAnswer to a JSON map. | ||||||
|  |   Map<String, dynamic> toJson(); | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | bool operator ==(Object other) { | ||||||
|  |   return identical(this, other) || (other.runtimeType == runtimeType&&other is SnPollAnswer&&(identical(other.id, id) || other.id == id)&&const DeepCollectionEquality().equals(other.answer, answer)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.pollId, pollId) || other.pollId == pollId)&&(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) | ||||||
|  | @override | ||||||
|  | int get hashCode => Object.hash(runtimeType,id,const DeepCollectionEquality().hash(answer),accountId,pollId,createdAt,updatedAt,deletedAt); | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | String toString() { | ||||||
|  |   return 'SnPollAnswer(id: $id, answer: $answer, accountId: $accountId, pollId: $pollId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | abstract mixin class $SnPollAnswerCopyWith<$Res>  { | ||||||
|  |   factory $SnPollAnswerCopyWith(SnPollAnswer value, $Res Function(SnPollAnswer) _then) = _$SnPollAnswerCopyWithImpl; | ||||||
|  | @useResult | ||||||
|  | $Res call({ | ||||||
|  |  String id, Map<String, dynamic> answer, String accountId, String pollId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt | ||||||
|  | }); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  | /// @nodoc | ||||||
|  | class _$SnPollAnswerCopyWithImpl<$Res> | ||||||
|  |     implements $SnPollAnswerCopyWith<$Res> { | ||||||
|  |   _$SnPollAnswerCopyWithImpl(this._self, this._then); | ||||||
|  |  | ||||||
|  |   final SnPollAnswer _self; | ||||||
|  |   final $Res Function(SnPollAnswer) _then; | ||||||
|  |  | ||||||
|  | /// Create a copy of SnPollAnswer | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? answer = null,Object? accountId = null,Object? pollId = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) { | ||||||
|  |   return _then(_self.copyWith( | ||||||
|  | id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,answer: null == answer ? _self.answer : answer // ignore: cast_nullable_to_non_nullable | ||||||
|  | as Map<String, dynamic>,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,pollId: null == pollId ? _self.pollId : pollId // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable | ||||||
|  | as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable | ||||||
|  | as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable | ||||||
|  | as DateTime?, | ||||||
|  |   )); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | /// Adds pattern-matching-related methods to [SnPollAnswer]. | ||||||
|  | extension SnPollAnswerPatterns on SnPollAnswer { | ||||||
|  | /// 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( _SnPollAnswer value)?  $default,{required TResult orElse(),}){ | ||||||
|  | final _that = this; | ||||||
|  | switch (_that) { | ||||||
|  | case _SnPollAnswer() 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( _SnPollAnswer value)  $default,){ | ||||||
|  | final _that = this; | ||||||
|  | switch (_that) { | ||||||
|  | case _SnPollAnswer(): | ||||||
|  | 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( _SnPollAnswer value)?  $default,){ | ||||||
|  | final _that = this; | ||||||
|  | switch (_that) { | ||||||
|  | case _SnPollAnswer() when $default != null: | ||||||
|  | return $default(_that);case _: | ||||||
|  |   return null; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  | /// A variant of `when` that fallback to an `orElse` callback. | ||||||
|  | /// | ||||||
|  | /// It is equivalent to doing: | ||||||
|  | /// ```dart | ||||||
|  | /// switch (sealedClass) { | ||||||
|  | ///   case Subclass(:final field): | ||||||
|  | ///     return ...; | ||||||
|  | ///   case _: | ||||||
|  | ///     return orElse(); | ||||||
|  | /// } | ||||||
|  | /// ``` | ||||||
|  |  | ||||||
|  | @optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id,  Map<String, dynamic> answer,  String accountId,  String pollId,  DateTime createdAt,  DateTime updatedAt,  DateTime? deletedAt)?  $default,{required TResult orElse(),}) {final _that = this; | ||||||
|  | switch (_that) { | ||||||
|  | case _SnPollAnswer() when $default != null: | ||||||
|  | return $default(_that.id,_that.answer,_that.accountId,_that.pollId,_that.createdAt,_that.updatedAt,_that.deletedAt);case _: | ||||||
|  |   return orElse(); | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  | /// A `switch`-like method, using callbacks. | ||||||
|  | /// | ||||||
|  | /// As opposed to `map`, this offers destructuring. | ||||||
|  | /// It is equivalent to doing: | ||||||
|  | /// ```dart | ||||||
|  | /// switch (sealedClass) { | ||||||
|  | ///   case Subclass(:final field): | ||||||
|  | ///     return ...; | ||||||
|  | ///   case Subclass2(:final field2): | ||||||
|  | ///     return ...; | ||||||
|  | /// } | ||||||
|  | /// ``` | ||||||
|  |  | ||||||
|  | @optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id,  Map<String, dynamic> answer,  String accountId,  String pollId,  DateTime createdAt,  DateTime updatedAt,  DateTime? deletedAt)  $default,) {final _that = this; | ||||||
|  | switch (_that) { | ||||||
|  | case _SnPollAnswer(): | ||||||
|  | return $default(_that.id,_that.answer,_that.accountId,_that.pollId,_that.createdAt,_that.updatedAt,_that.deletedAt);} | ||||||
|  | } | ||||||
|  | /// A variant of `when` that fallback to returning `null` | ||||||
|  | /// | ||||||
|  | /// It is equivalent to doing: | ||||||
|  | /// ```dart | ||||||
|  | /// switch (sealedClass) { | ||||||
|  | ///   case Subclass(:final field): | ||||||
|  | ///     return ...; | ||||||
|  | ///   case _: | ||||||
|  | ///     return null; | ||||||
|  | /// } | ||||||
|  | /// ``` | ||||||
|  |  | ||||||
|  | @optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id,  Map<String, dynamic> answer,  String accountId,  String pollId,  DateTime createdAt,  DateTime updatedAt,  DateTime? deletedAt)?  $default,) {final _that = this; | ||||||
|  | switch (_that) { | ||||||
|  | case _SnPollAnswer() when $default != null: | ||||||
|  | return $default(_that.id,_that.answer,_that.accountId,_that.pollId,_that.createdAt,_that.updatedAt,_that.deletedAt);case _: | ||||||
|  |   return null; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | @JsonSerializable() | ||||||
|  |  | ||||||
|  | class _SnPollAnswer implements SnPollAnswer { | ||||||
|  |   const _SnPollAnswer({required this.id, required final  Map<String, dynamic> answer, required this.accountId, required this.pollId, required this.createdAt, required this.updatedAt, required this.deletedAt}): _answer = answer; | ||||||
|  |   factory _SnPollAnswer.fromJson(Map<String, dynamic> json) => _$SnPollAnswerFromJson(json); | ||||||
|  |  | ||||||
|  | @override final  String id; | ||||||
|  |  final  Map<String, dynamic> _answer; | ||||||
|  | @override Map<String, dynamic> get answer { | ||||||
|  |   if (_answer is EqualUnmodifiableMapView) return _answer; | ||||||
|  |   // ignore: implicit_dynamic_type | ||||||
|  |   return EqualUnmodifiableMapView(_answer); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @override final  String accountId; | ||||||
|  | @override final  String pollId; | ||||||
|  | @override final  DateTime createdAt; | ||||||
|  | @override final  DateTime updatedAt; | ||||||
|  | @override final  DateTime? deletedAt; | ||||||
|  |  | ||||||
|  | /// Create a copy of SnPollAnswer | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
|  | _$SnPollAnswerCopyWith<_SnPollAnswer> get copyWith => __$SnPollAnswerCopyWithImpl<_SnPollAnswer>(this, _$identity); | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | Map<String, dynamic> toJson() { | ||||||
|  |   return _$SnPollAnswerToJson(this, ); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | bool operator ==(Object other) { | ||||||
|  |   return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnPollAnswer&&(identical(other.id, id) || other.id == id)&&const DeepCollectionEquality().equals(other._answer, _answer)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.pollId, pollId) || other.pollId == pollId)&&(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) | ||||||
|  | @override | ||||||
|  | int get hashCode => Object.hash(runtimeType,id,const DeepCollectionEquality().hash(_answer),accountId,pollId,createdAt,updatedAt,deletedAt); | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | String toString() { | ||||||
|  |   return 'SnPollAnswer(id: $id, answer: $answer, accountId: $accountId, pollId: $pollId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | abstract mixin class _$SnPollAnswerCopyWith<$Res> implements $SnPollAnswerCopyWith<$Res> { | ||||||
|  |   factory _$SnPollAnswerCopyWith(_SnPollAnswer value, $Res Function(_SnPollAnswer) _then) = __$SnPollAnswerCopyWithImpl; | ||||||
|  | @override @useResult | ||||||
|  | $Res call({ | ||||||
|  |  String id, Map<String, dynamic> answer, String accountId, String pollId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt | ||||||
|  | }); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  | /// @nodoc | ||||||
|  | class __$SnPollAnswerCopyWithImpl<$Res> | ||||||
|  |     implements _$SnPollAnswerCopyWith<$Res> { | ||||||
|  |   __$SnPollAnswerCopyWithImpl(this._self, this._then); | ||||||
|  |  | ||||||
|  |   final _SnPollAnswer _self; | ||||||
|  |   final $Res Function(_SnPollAnswer) _then; | ||||||
|  |  | ||||||
|  | /// Create a copy of SnPollAnswer | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? answer = null,Object? accountId = null,Object? pollId = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) { | ||||||
|  |   return _then(_SnPollAnswer( | ||||||
|  | id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,answer: null == answer ? _self._answer : answer // ignore: cast_nullable_to_non_nullable | ||||||
|  | as Map<String, dynamic>,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,pollId: null == pollId ? _self.pollId : pollId // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable | ||||||
|  | as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable | ||||||
|  | as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable | ||||||
|  | as DateTime?, | ||||||
|  |   )); | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
| // dart format on | // dart format on | ||||||
|   | |||||||
| @@ -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>) | ||||||
| @@ -131,3 +131,28 @@ Map<String, dynamic> _$SnPollOptionToJson(_SnPollOption instance) => | |||||||
|       'description': instance.description, |       'description': instance.description, | ||||||
|       'order': instance.order, |       'order': instance.order, | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|  | _SnPollAnswer _$SnPollAnswerFromJson(Map<String, dynamic> json) => | ||||||
|  |     _SnPollAnswer( | ||||||
|  |       id: json['id'] as String, | ||||||
|  |       answer: json['answer'] as Map<String, dynamic>, | ||||||
|  |       accountId: json['account_id'] as String, | ||||||
|  |       pollId: json['poll_id'] 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> _$SnPollAnswerToJson(_SnPollAnswer instance) => | ||||||
|  |     <String, dynamic>{ | ||||||
|  |       'id': instance.id, | ||||||
|  |       'answer': instance.answer, | ||||||
|  |       'account_id': instance.accountId, | ||||||
|  |       'poll_id': instance.pollId, | ||||||
|  |       'created_at': instance.createdAt.toIso8601String(), | ||||||
|  |       'updated_at': instance.updatedAt.toIso8601String(), | ||||||
|  |       'deleted_at': instance.deletedAt?.toIso8601String(), | ||||||
|  |     }; | ||||||
|   | |||||||
| @@ -36,8 +36,8 @@ sealed class SnPost with _$SnPost { | |||||||
|     @Default({}) Map<String, int> reactionsCount, |     @Default({}) Map<String, int> reactionsCount, | ||||||
|     @Default({}) Map<String, bool> reactionsMade, |     @Default({}) Map<String, bool> reactionsMade, | ||||||
|     @Default([]) List<dynamic> reactions, |     @Default([]) List<dynamic> reactions, | ||||||
|     @Default([]) List<PostTag> tags, |     @Default([]) List<SnPostTag> tags, | ||||||
|     @Default([]) List<PostCategory> categories, |     @Default([]) List<SnPostCategory> categories, | ||||||
|     @Default([]) List<dynamic> collections, |     @Default([]) List<dynamic> collections, | ||||||
|     @Default(null) DateTime? createdAt, |     @Default(null) DateTime? createdAt, | ||||||
|     @Default(null) DateTime? updatedAt, |     @Default(null) DateTime? updatedAt, | ||||||
|   | |||||||
| @@ -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<PostTag> get tags; List<PostCategory> 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; 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; | ||||||
| /// 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) | ||||||
| @@ -48,7 +48,7 @@ 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<PostTag> tags, List<PostCategory> 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, 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 | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -94,8 +94,8 @@ as SnPublisher,reactionsCount: null == reactionsCount ? _self.reactionsCount : r | |||||||
| 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 | ||||||
| as Map<String, bool>,reactions: null == reactions ? _self.reactions : reactions // ignore: cast_nullable_to_non_nullable | as Map<String, bool>,reactions: null == reactions ? _self.reactions : reactions // ignore: cast_nullable_to_non_nullable | ||||||
| as List<dynamic>,tags: null == tags ? _self.tags : tags // ignore: cast_nullable_to_non_nullable | as List<dynamic>,tags: null == tags ? _self.tags : tags // ignore: cast_nullable_to_non_nullable | ||||||
| as List<PostTag>,categories: null == categories ? _self.categories : categories // ignore: cast_nullable_to_non_nullable | as List<SnPostTag>,categories: null == categories ? _self.categories : categories // ignore: cast_nullable_to_non_nullable | ||||||
| as List<PostCategory>,collections: null == collections ? _self.collections : collections // ignore: cast_nullable_to_non_nullable | as List<SnPostCategory>,collections: null == collections ? _self.collections : collections // ignore: cast_nullable_to_non_nullable | ||||||
| as List<dynamic>,createdAt: freezed == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable | as List<dynamic>,createdAt: freezed == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable | ||||||
| as DateTime?,updatedAt: freezed == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable | as DateTime?,updatedAt: freezed == 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 | ||||||
| @@ -227,7 +227,7 @@ 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<PostTag> tags,  List<PostCategory> 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,  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; | ||||||
| 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.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 _: | ||||||
| @@ -248,7 +248,7 @@ 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<PostTag> tags,  List<PostCategory> 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,  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; | ||||||
| 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.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);} | ||||||
| @@ -265,7 +265,7 @@ 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<PostTag> tags,  List<PostCategory> 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,  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; | ||||||
| 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.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 _: | ||||||
| @@ -280,7 +280,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<PostTag> tags = const [], final  List<PostCategory> 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.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; | ||||||
|   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; | ||||||
| @@ -341,15 +341,15 @@ class _SnPost implements SnPost { | |||||||
|   return EqualUnmodifiableListView(_reactions); |   return EqualUnmodifiableListView(_reactions); | ||||||
| } | } | ||||||
|  |  | ||||||
|  final  List<PostTag> _tags; |  final  List<SnPostTag> _tags; | ||||||
| @override@JsonKey() List<PostTag> get tags { | @override@JsonKey() List<SnPostTag> get tags { | ||||||
|   if (_tags is EqualUnmodifiableListView) return _tags; |   if (_tags is EqualUnmodifiableListView) return _tags; | ||||||
|   // ignore: implicit_dynamic_type |   // ignore: implicit_dynamic_type | ||||||
|   return EqualUnmodifiableListView(_tags); |   return EqualUnmodifiableListView(_tags); | ||||||
| } | } | ||||||
|  |  | ||||||
|  final  List<PostCategory> _categories; |  final  List<SnPostCategory> _categories; | ||||||
| @override@JsonKey() List<PostCategory> get categories { | @override@JsonKey() List<SnPostCategory> get categories { | ||||||
|   if (_categories is EqualUnmodifiableListView) return _categories; |   if (_categories is EqualUnmodifiableListView) return _categories; | ||||||
|   // ignore: implicit_dynamic_type |   // ignore: implicit_dynamic_type | ||||||
|   return EqualUnmodifiableListView(_categories); |   return EqualUnmodifiableListView(_categories); | ||||||
| @@ -400,7 +400,7 @@ 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<PostTag> tags, List<PostCategory> 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, 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 | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -446,8 +446,8 @@ as SnPublisher,reactionsCount: null == reactionsCount ? _self._reactionsCount : | |||||||
| 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 | ||||||
| as Map<String, bool>,reactions: null == reactions ? _self._reactions : reactions // ignore: cast_nullable_to_non_nullable | as Map<String, bool>,reactions: null == reactions ? _self._reactions : reactions // ignore: cast_nullable_to_non_nullable | ||||||
| as List<dynamic>,tags: null == tags ? _self._tags : tags // ignore: cast_nullable_to_non_nullable | as List<dynamic>,tags: null == tags ? _self._tags : tags // ignore: cast_nullable_to_non_nullable | ||||||
| as List<PostTag>,categories: null == categories ? _self._categories : categories // ignore: cast_nullable_to_non_nullable | as List<SnPostTag>,categories: null == categories ? _self._categories : categories // ignore: cast_nullable_to_non_nullable | ||||||
| as List<PostCategory>,collections: null == collections ? _self._collections : collections // ignore: cast_nullable_to_non_nullable | as List<SnPostCategory>,collections: null == collections ? _self._collections : collections // ignore: cast_nullable_to_non_nullable | ||||||
| as List<dynamic>,createdAt: freezed == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable | as List<dynamic>,createdAt: freezed == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable | ||||||
| as DateTime?,updatedAt: freezed == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable | as DateTime?,updatedAt: freezed == 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 | ||||||
|   | |||||||
| @@ -62,12 +62,12 @@ _SnPost _$SnPostFromJson(Map<String, dynamic> json) => _SnPost( | |||||||
|   reactions: json['reactions'] as List<dynamic>? ?? const [], |   reactions: json['reactions'] as List<dynamic>? ?? const [], | ||||||
|   tags: |   tags: | ||||||
|       (json['tags'] as List<dynamic>?) |       (json['tags'] as List<dynamic>?) | ||||||
|           ?.map((e) => PostTag.fromJson(e as Map<String, dynamic>)) |           ?.map((e) => SnPostTag.fromJson(e as Map<String, dynamic>)) | ||||||
|           .toList() ?? |           .toList() ?? | ||||||
|       const [], |       const [], | ||||||
|   categories: |   categories: | ||||||
|       (json['categories'] as List<dynamic>?) |       (json['categories'] as List<dynamic>?) | ||||||
|           ?.map((e) => PostCategory.fromJson(e as Map<String, dynamic>)) |           ?.map((e) => SnPostCategory.fromJson(e as Map<String, dynamic>)) | ||||||
|           .toList() ?? |           .toList() ?? | ||||||
|       const [], |       const [], | ||||||
|   collections: json['collections'] as List<dynamic>? ?? const [], |   collections: json['collections'] as List<dynamic>? ?? const [], | ||||||
|   | |||||||
| @@ -1,19 +1,30 @@ | |||||||
|  | import 'package:easy_localization/easy_localization.dart'; | ||||||
| import 'package:freezed_annotation/freezed_annotation.dart'; | import 'package:freezed_annotation/freezed_annotation.dart'; | ||||||
| import 'package:island/models/post.dart'; | import 'package:island/models/post.dart'; | ||||||
|  | import 'package:island/services/text.dart'; | ||||||
|  |  | ||||||
| part 'post_category.freezed.dart'; | part 'post_category.freezed.dart'; | ||||||
| part 'post_category.g.dart'; | part 'post_category.g.dart'; | ||||||
|  |  | ||||||
| @freezed | @freezed | ||||||
| sealed class PostCategory with _$PostCategory { | sealed class SnPostCategory with _$SnPostCategory { | ||||||
|   const factory PostCategory({ |   const SnPostCategory._(); | ||||||
|  |  | ||||||
|  |   const factory SnPostCategory({ | ||||||
|     required String id, |     required String id, | ||||||
|     required String slug, |     required String slug, | ||||||
|     String? name, |     String? name, | ||||||
|     @Default([]) List<SnPost> posts, |     @Default([]) List<SnPost> posts, | ||||||
|   }) = _PostCategory; |   }) = _SnPostCategory; | ||||||
|  |  | ||||||
|   factory PostCategory.fromJson(Map<String, dynamic> json) => |   factory SnPostCategory.fromJson(Map<String, dynamic> json) => | ||||||
|       _$PostCategoryFromJson(json); |       _$SnPostCategoryFromJson(json); | ||||||
|  |  | ||||||
|  |   String get categoryDisplayTitle { | ||||||
|  |     final capitalizedSlug = slug.capitalizeEachWord(); | ||||||
|  |     if ('postCategory$capitalizedSlug'.trExists()) { | ||||||
|  |       return 'postCategory$capitalizedSlug'.tr(); | ||||||
|  |     } | ||||||
|  |     return name ?? slug; | ||||||
|  |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -13,22 +13,22 @@ part of 'post_category.dart'; | |||||||
| T _$identity<T>(T value) => value; | T _$identity<T>(T value) => value; | ||||||
|  |  | ||||||
| /// @nodoc | /// @nodoc | ||||||
| mixin _$PostCategory { | 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; | ||||||
| /// Create a copy of PostCategory | /// 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) | ||||||
| @pragma('vm:prefer-inline') | @pragma('vm:prefer-inline') | ||||||
| $PostCategoryCopyWith<PostCategory> get copyWith => _$PostCategoryCopyWithImpl<PostCategory>(this as PostCategory, _$identity); | $SnPostCategoryCopyWith<SnPostCategory> get copyWith => _$SnPostCategoryCopyWithImpl<SnPostCategory>(this as SnPostCategory, _$identity); | ||||||
|  |  | ||||||
|   /// Serializes this PostCategory to a JSON map. |   /// Serializes this SnPostCategory to a JSON map. | ||||||
|   Map<String, dynamic> toJson(); |   Map<String, dynamic> toJson(); | ||||||
|  |  | ||||||
|  |  | ||||||
| @override | @override | ||||||
| bool operator ==(Object other) { | bool operator ==(Object other) { | ||||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is PostCategory&&(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)); | ||||||
| } | } | ||||||
|  |  | ||||||
| @JsonKey(includeFromJson: false, includeToJson: false) | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
| @@ -37,15 +37,15 @@ int get hashCode => Object.hash(runtimeType,id,slug,name,const DeepCollectionEqu | |||||||
|  |  | ||||||
| @override | @override | ||||||
| String toString() { | String toString() { | ||||||
|   return 'PostCategory(id: $id, slug: $slug, name: $name, posts: $posts)'; |   return 'SnPostCategory(id: $id, slug: $slug, name: $name, posts: $posts)'; | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
| /// @nodoc | /// @nodoc | ||||||
| abstract mixin class $PostCategoryCopyWith<$Res>  { | abstract mixin class $SnPostCategoryCopyWith<$Res>  { | ||||||
|   factory $PostCategoryCopyWith(PostCategory value, $Res Function(PostCategory) _then) = _$PostCategoryCopyWithImpl; |   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 | ||||||
| @@ -56,14 +56,14 @@ $Res call({ | |||||||
|  |  | ||||||
| } | } | ||||||
| /// @nodoc | /// @nodoc | ||||||
| class _$PostCategoryCopyWithImpl<$Res> | class _$SnPostCategoryCopyWithImpl<$Res> | ||||||
|     implements $PostCategoryCopyWith<$Res> { |     implements $SnPostCategoryCopyWith<$Res> { | ||||||
|   _$PostCategoryCopyWithImpl(this._self, this._then); |   _$SnPostCategoryCopyWithImpl(this._self, this._then); | ||||||
|  |  | ||||||
|   final PostCategory _self; |   final SnPostCategory _self; | ||||||
|   final $Res Function(PostCategory) _then; |   final $Res Function(SnPostCategory) _then; | ||||||
|  |  | ||||||
| /// Create a copy of PostCategory | /// 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,}) { | ||||||
|   return _then(_self.copyWith( |   return _then(_self.copyWith( | ||||||
| @@ -78,8 +78,8 @@ as List<SnPost>, | |||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| /// Adds pattern-matching-related methods to [PostCategory]. | /// Adds pattern-matching-related methods to [SnPostCategory]. | ||||||
| extension PostCategoryPatterns on PostCategory { | extension SnPostCategoryPatterns on SnPostCategory { | ||||||
| /// A variant of `map` that fallback to returning `orElse`. | /// A variant of `map` that fallback to returning `orElse`. | ||||||
| /// | /// | ||||||
| /// It is equivalent to doing: | /// It is equivalent to doing: | ||||||
| @@ -92,10 +92,10 @@ extension PostCategoryPatterns on PostCategory { | |||||||
| /// } | /// } | ||||||
| /// ``` | /// ``` | ||||||
|  |  | ||||||
| @optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _PostCategory value)?  $default,{required TResult orElse(),}){ | @optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _SnPostCategory value)?  $default,{required TResult orElse(),}){ | ||||||
| final _that = this; | final _that = this; | ||||||
| switch (_that) { | switch (_that) { | ||||||
| case _PostCategory() when $default != null: | case _SnPostCategory() when $default != null: | ||||||
| return $default(_that);case _: | return $default(_that);case _: | ||||||
|   return orElse(); |   return orElse(); | ||||||
|  |  | ||||||
| @@ -114,10 +114,10 @@ return $default(_that);case _: | |||||||
| /// } | /// } | ||||||
| /// ``` | /// ``` | ||||||
|  |  | ||||||
| @optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _PostCategory value)  $default,){ | @optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _SnPostCategory value)  $default,){ | ||||||
| final _that = this; | final _that = this; | ||||||
| switch (_that) { | switch (_that) { | ||||||
| case _PostCategory(): | case _SnPostCategory(): | ||||||
| return $default(_that);} | return $default(_that);} | ||||||
| } | } | ||||||
| /// A variant of `map` that fallback to returning `null`. | /// A variant of `map` that fallback to returning `null`. | ||||||
| @@ -132,10 +132,10 @@ return $default(_that);} | |||||||
| /// } | /// } | ||||||
| /// ``` | /// ``` | ||||||
|  |  | ||||||
| @optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _PostCategory value)?  $default,){ | @optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _SnPostCategory value)?  $default,){ | ||||||
| final _that = this; | final _that = this; | ||||||
| switch (_that) { | switch (_that) { | ||||||
| case _PostCategory() when $default != null: | case _SnPostCategory() when $default != null: | ||||||
| return $default(_that);case _: | return $default(_that);case _: | ||||||
|   return null; |   return null; | ||||||
|  |  | ||||||
| @@ -155,7 +155,7 @@ 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)?  $default,{required TResult orElse(),}) {final _that = this; | ||||||
| switch (_that) { | switch (_that) { | ||||||
| case _PostCategory() 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);case _: | ||||||
|   return orElse(); |   return orElse(); | ||||||
|  |  | ||||||
| @@ -176,7 +176,7 @@ 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)  $default,) {final _that = this; | ||||||
| switch (_that) { | switch (_that) { | ||||||
| case _PostCategory(): | case _SnPostCategory(): | ||||||
| return $default(_that.id,_that.slug,_that.name,_that.posts);} | return $default(_that.id,_that.slug,_that.name,_that.posts);} | ||||||
| } | } | ||||||
| /// A variant of `when` that fallback to returning `null` | /// A variant of `when` that fallback to returning `null` | ||||||
| @@ -193,7 +193,7 @@ 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)?  $default,) {final _that = this; | ||||||
| switch (_that) { | switch (_that) { | ||||||
| case _PostCategory() 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);case _: | ||||||
|   return null; |   return null; | ||||||
|  |  | ||||||
| @@ -205,9 +205,9 @@ return $default(_that.id,_that.slug,_that.name,_that.posts);case _: | |||||||
| /// @nodoc | /// @nodoc | ||||||
| @JsonSerializable() | @JsonSerializable() | ||||||
|  |  | ||||||
| class _PostCategory implements PostCategory { | class _SnPostCategory extends SnPostCategory { | ||||||
|   const _PostCategory({required this.id, required this.slug, this.name, final  List<SnPost> posts = const []}): _posts = posts; |   const _SnPostCategory({required this.id, required this.slug, this.name, final  List<SnPost> posts = const []}): _posts = posts,super._(); | ||||||
|   factory _PostCategory.fromJson(Map<String, dynamic> json) => _$PostCategoryFromJson(json); |   factory _SnPostCategory.fromJson(Map<String, dynamic> json) => _$SnPostCategoryFromJson(json); | ||||||
|  |  | ||||||
| @override final  String id; | @override final  String id; | ||||||
| @override final  String slug; | @override final  String slug; | ||||||
| @@ -220,20 +220,20 @@ class _PostCategory implements PostCategory { | |||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| /// Create a copy of PostCategory | /// 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 @JsonKey(includeFromJson: false, includeToJson: false) | @override @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
| @pragma('vm:prefer-inline') | @pragma('vm:prefer-inline') | ||||||
| _$PostCategoryCopyWith<_PostCategory> get copyWith => __$PostCategoryCopyWithImpl<_PostCategory>(this, _$identity); | _$SnPostCategoryCopyWith<_SnPostCategory> get copyWith => __$SnPostCategoryCopyWithImpl<_SnPostCategory>(this, _$identity); | ||||||
|  |  | ||||||
| @override | @override | ||||||
| Map<String, dynamic> toJson() { | Map<String, dynamic> toJson() { | ||||||
|   return _$PostCategoryToJson(this, ); |   return _$SnPostCategoryToJson(this, ); | ||||||
| } | } | ||||||
|  |  | ||||||
| @override | @override | ||||||
| bool operator ==(Object other) { | bool operator ==(Object other) { | ||||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is _PostCategory&&(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)); | ||||||
| } | } | ||||||
|  |  | ||||||
| @JsonKey(includeFromJson: false, includeToJson: false) | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
| @@ -242,15 +242,15 @@ int get hashCode => Object.hash(runtimeType,id,slug,name,const DeepCollectionEqu | |||||||
|  |  | ||||||
| @override | @override | ||||||
| String toString() { | String toString() { | ||||||
|   return 'PostCategory(id: $id, slug: $slug, name: $name, posts: $posts)'; |   return 'SnPostCategory(id: $id, slug: $slug, name: $name, posts: $posts)'; | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
| /// @nodoc | /// @nodoc | ||||||
| abstract mixin class _$PostCategoryCopyWith<$Res> implements $PostCategoryCopyWith<$Res> { | abstract mixin class _$SnPostCategoryCopyWith<$Res> implements $SnPostCategoryCopyWith<$Res> { | ||||||
|   factory _$PostCategoryCopyWith(_PostCategory value, $Res Function(_PostCategory) _then) = __$PostCategoryCopyWithImpl; |   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 | ||||||
| @@ -261,17 +261,17 @@ $Res call({ | |||||||
|  |  | ||||||
| } | } | ||||||
| /// @nodoc | /// @nodoc | ||||||
| class __$PostCategoryCopyWithImpl<$Res> | class __$SnPostCategoryCopyWithImpl<$Res> | ||||||
|     implements _$PostCategoryCopyWith<$Res> { |     implements _$SnPostCategoryCopyWith<$Res> { | ||||||
|   __$PostCategoryCopyWithImpl(this._self, this._then); |   __$SnPostCategoryCopyWithImpl(this._self, this._then); | ||||||
|  |  | ||||||
|   final _PostCategory _self; |   final _SnPostCategory _self; | ||||||
|   final $Res Function(_PostCategory) _then; |   final $Res Function(_SnPostCategory) _then; | ||||||
|  |  | ||||||
| /// Create a copy of PostCategory | /// 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,}) { | ||||||
|   return _then(_PostCategory( |   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 | ||||||
|   | |||||||
| @@ -6,8 +6,8 @@ part of 'post_category.dart'; | |||||||
| // JsonSerializableGenerator | // JsonSerializableGenerator | ||||||
| // ************************************************************************** | // ************************************************************************** | ||||||
|  |  | ||||||
| _PostCategory _$PostCategoryFromJson(Map<String, dynamic> json) => | _SnPostCategory _$SnPostCategoryFromJson(Map<String, dynamic> json) => | ||||||
|     _PostCategory( |     _SnPostCategory( | ||||||
|       id: json['id'] as String, |       id: json['id'] as String, | ||||||
|       slug: json['slug'] as String, |       slug: json['slug'] as String, | ||||||
|       name: json['name'] as String?, |       name: json['name'] as String?, | ||||||
| @@ -18,7 +18,7 @@ _PostCategory _$PostCategoryFromJson(Map<String, dynamic> json) => | |||||||
|           const [], |           const [], | ||||||
|     ); |     ); | ||||||
|  |  | ||||||
| Map<String, dynamic> _$PostCategoryToJson(_PostCategory instance) => | Map<String, dynamic> _$SnPostCategoryToJson(_SnPostCategory instance) => | ||||||
|     <String, dynamic>{ |     <String, dynamic>{ | ||||||
|       'id': instance.id, |       'id': instance.id, | ||||||
|       'slug': instance.slug, |       'slug': instance.slug, | ||||||
|   | |||||||
| @@ -1,4 +1,3 @@ | |||||||
|  |  | ||||||
| import 'package:freezed_annotation/freezed_annotation.dart'; | import 'package:freezed_annotation/freezed_annotation.dart'; | ||||||
| import 'package:island/models/post.dart'; | import 'package:island/models/post.dart'; | ||||||
|  |  | ||||||
| @@ -6,14 +5,14 @@ part 'post_tag.freezed.dart'; | |||||||
| part 'post_tag.g.dart'; | part 'post_tag.g.dart'; | ||||||
|  |  | ||||||
| @freezed | @freezed | ||||||
| sealed class PostTag with _$PostTag { | sealed class SnPostTag with _$SnPostTag { | ||||||
|   const factory PostTag({ |   const factory SnPostTag({ | ||||||
|     required String id, |     required String id, | ||||||
|     required String slug, |     required String slug, | ||||||
|     String? name, |     String? name, | ||||||
|     @Default([]) List<SnPost> posts, |     @Default([]) List<SnPost> posts, | ||||||
|   }) = _PostTag; |   }) = _SnPostTag; | ||||||
|  |  | ||||||
|   factory PostTag.fromJson(Map<String, dynamic> json) => |   factory SnPostTag.fromJson(Map<String, dynamic> json) => | ||||||
|       _$PostTagFromJson(json); |       _$SnPostTagFromJson(json); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -13,22 +13,22 @@ part of 'post_tag.dart'; | |||||||
| T _$identity<T>(T value) => value; | T _$identity<T>(T value) => value; | ||||||
|  |  | ||||||
| /// @nodoc | /// @nodoc | ||||||
| mixin _$PostTag { | 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; | ||||||
| /// Create a copy of PostTag | /// 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) | ||||||
| @pragma('vm:prefer-inline') | @pragma('vm:prefer-inline') | ||||||
| $PostTagCopyWith<PostTag> get copyWith => _$PostTagCopyWithImpl<PostTag>(this as PostTag, _$identity); | $SnPostTagCopyWith<SnPostTag> get copyWith => _$SnPostTagCopyWithImpl<SnPostTag>(this as SnPostTag, _$identity); | ||||||
|  |  | ||||||
|   /// Serializes this PostTag to a JSON map. |   /// Serializes this SnPostTag to a JSON map. | ||||||
|   Map<String, dynamic> toJson(); |   Map<String, dynamic> toJson(); | ||||||
|  |  | ||||||
|  |  | ||||||
| @override | @override | ||||||
| bool operator ==(Object other) { | bool operator ==(Object other) { | ||||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is PostTag&&(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)); | ||||||
| } | } | ||||||
|  |  | ||||||
| @JsonKey(includeFromJson: false, includeToJson: false) | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
| @@ -37,15 +37,15 @@ int get hashCode => Object.hash(runtimeType,id,slug,name,const DeepCollectionEqu | |||||||
|  |  | ||||||
| @override | @override | ||||||
| String toString() { | String toString() { | ||||||
|   return 'PostTag(id: $id, slug: $slug, name: $name, posts: $posts)'; |   return 'SnPostTag(id: $id, slug: $slug, name: $name, posts: $posts)'; | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
| /// @nodoc | /// @nodoc | ||||||
| abstract mixin class $PostTagCopyWith<$Res>  { | abstract mixin class $SnPostTagCopyWith<$Res>  { | ||||||
|   factory $PostTagCopyWith(PostTag value, $Res Function(PostTag) _then) = _$PostTagCopyWithImpl; |   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 | ||||||
| @@ -56,14 +56,14 @@ $Res call({ | |||||||
|  |  | ||||||
| } | } | ||||||
| /// @nodoc | /// @nodoc | ||||||
| class _$PostTagCopyWithImpl<$Res> | class _$SnPostTagCopyWithImpl<$Res> | ||||||
|     implements $PostTagCopyWith<$Res> { |     implements $SnPostTagCopyWith<$Res> { | ||||||
|   _$PostTagCopyWithImpl(this._self, this._then); |   _$SnPostTagCopyWithImpl(this._self, this._then); | ||||||
|  |  | ||||||
|   final PostTag _self; |   final SnPostTag _self; | ||||||
|   final $Res Function(PostTag) _then; |   final $Res Function(SnPostTag) _then; | ||||||
|  |  | ||||||
| /// Create a copy of PostTag | /// 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,}) { | ||||||
|   return _then(_self.copyWith( |   return _then(_self.copyWith( | ||||||
| @@ -78,8 +78,8 @@ as List<SnPost>, | |||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| /// Adds pattern-matching-related methods to [PostTag]. | /// Adds pattern-matching-related methods to [SnPostTag]. | ||||||
| extension PostTagPatterns on PostTag { | extension SnPostTagPatterns on SnPostTag { | ||||||
| /// A variant of `map` that fallback to returning `orElse`. | /// A variant of `map` that fallback to returning `orElse`. | ||||||
| /// | /// | ||||||
| /// It is equivalent to doing: | /// It is equivalent to doing: | ||||||
| @@ -92,10 +92,10 @@ extension PostTagPatterns on PostTag { | |||||||
| /// } | /// } | ||||||
| /// ``` | /// ``` | ||||||
|  |  | ||||||
| @optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _PostTag value)?  $default,{required TResult orElse(),}){ | @optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _SnPostTag value)?  $default,{required TResult orElse(),}){ | ||||||
| final _that = this; | final _that = this; | ||||||
| switch (_that) { | switch (_that) { | ||||||
| case _PostTag() when $default != null: | case _SnPostTag() when $default != null: | ||||||
| return $default(_that);case _: | return $default(_that);case _: | ||||||
|   return orElse(); |   return orElse(); | ||||||
|  |  | ||||||
| @@ -114,10 +114,10 @@ return $default(_that);case _: | |||||||
| /// } | /// } | ||||||
| /// ``` | /// ``` | ||||||
|  |  | ||||||
| @optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _PostTag value)  $default,){ | @optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _SnPostTag value)  $default,){ | ||||||
| final _that = this; | final _that = this; | ||||||
| switch (_that) { | switch (_that) { | ||||||
| case _PostTag(): | case _SnPostTag(): | ||||||
| return $default(_that);} | return $default(_that);} | ||||||
| } | } | ||||||
| /// A variant of `map` that fallback to returning `null`. | /// A variant of `map` that fallback to returning `null`. | ||||||
| @@ -132,10 +132,10 @@ return $default(_that);} | |||||||
| /// } | /// } | ||||||
| /// ``` | /// ``` | ||||||
|  |  | ||||||
| @optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _PostTag value)?  $default,){ | @optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _SnPostTag value)?  $default,){ | ||||||
| final _that = this; | final _that = this; | ||||||
| switch (_that) { | switch (_that) { | ||||||
| case _PostTag() when $default != null: | case _SnPostTag() when $default != null: | ||||||
| return $default(_that);case _: | return $default(_that);case _: | ||||||
|   return null; |   return null; | ||||||
|  |  | ||||||
| @@ -155,7 +155,7 @@ 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)?  $default,{required TResult orElse(),}) {final _that = this; | ||||||
| switch (_that) { | switch (_that) { | ||||||
| case _PostTag() 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);case _: | ||||||
|   return orElse(); |   return orElse(); | ||||||
|  |  | ||||||
| @@ -176,7 +176,7 @@ 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)  $default,) {final _that = this; | ||||||
| switch (_that) { | switch (_that) { | ||||||
| case _PostTag(): | case _SnPostTag(): | ||||||
| return $default(_that.id,_that.slug,_that.name,_that.posts);} | return $default(_that.id,_that.slug,_that.name,_that.posts);} | ||||||
| } | } | ||||||
| /// A variant of `when` that fallback to returning `null` | /// A variant of `when` that fallback to returning `null` | ||||||
| @@ -193,7 +193,7 @@ 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)?  $default,) {final _that = this; | ||||||
| switch (_that) { | switch (_that) { | ||||||
| case _PostTag() 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);case _: | ||||||
|   return null; |   return null; | ||||||
|  |  | ||||||
| @@ -205,9 +205,9 @@ return $default(_that.id,_that.slug,_that.name,_that.posts);case _: | |||||||
| /// @nodoc | /// @nodoc | ||||||
| @JsonSerializable() | @JsonSerializable() | ||||||
|  |  | ||||||
| class _PostTag implements PostTag { | class _SnPostTag implements SnPostTag { | ||||||
|   const _PostTag({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 []}): _posts = posts; | ||||||
|   factory _PostTag.fromJson(Map<String, dynamic> json) => _$PostTagFromJson(json); |   factory _SnPostTag.fromJson(Map<String, dynamic> json) => _$SnPostTagFromJson(json); | ||||||
|  |  | ||||||
| @override final  String id; | @override final  String id; | ||||||
| @override final  String slug; | @override final  String slug; | ||||||
| @@ -220,20 +220,20 @@ class _PostTag implements PostTag { | |||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| /// Create a copy of PostTag | /// 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 @JsonKey(includeFromJson: false, includeToJson: false) | @override @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
| @pragma('vm:prefer-inline') | @pragma('vm:prefer-inline') | ||||||
| _$PostTagCopyWith<_PostTag> get copyWith => __$PostTagCopyWithImpl<_PostTag>(this, _$identity); | _$SnPostTagCopyWith<_SnPostTag> get copyWith => __$SnPostTagCopyWithImpl<_SnPostTag>(this, _$identity); | ||||||
|  |  | ||||||
| @override | @override | ||||||
| Map<String, dynamic> toJson() { | Map<String, dynamic> toJson() { | ||||||
|   return _$PostTagToJson(this, ); |   return _$SnPostTagToJson(this, ); | ||||||
| } | } | ||||||
|  |  | ||||||
| @override | @override | ||||||
| bool operator ==(Object other) { | bool operator ==(Object other) { | ||||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is _PostTag&&(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)); | ||||||
| } | } | ||||||
|  |  | ||||||
| @JsonKey(includeFromJson: false, includeToJson: false) | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
| @@ -242,15 +242,15 @@ int get hashCode => Object.hash(runtimeType,id,slug,name,const DeepCollectionEqu | |||||||
|  |  | ||||||
| @override | @override | ||||||
| String toString() { | String toString() { | ||||||
|   return 'PostTag(id: $id, slug: $slug, name: $name, posts: $posts)'; |   return 'SnPostTag(id: $id, slug: $slug, name: $name, posts: $posts)'; | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
| /// @nodoc | /// @nodoc | ||||||
| abstract mixin class _$PostTagCopyWith<$Res> implements $PostTagCopyWith<$Res> { | abstract mixin class _$SnPostTagCopyWith<$Res> implements $SnPostTagCopyWith<$Res> { | ||||||
|   factory _$PostTagCopyWith(_PostTag value, $Res Function(_PostTag) _then) = __$PostTagCopyWithImpl; |   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 | ||||||
| @@ -261,17 +261,17 @@ $Res call({ | |||||||
|  |  | ||||||
| } | } | ||||||
| /// @nodoc | /// @nodoc | ||||||
| class __$PostTagCopyWithImpl<$Res> | class __$SnPostTagCopyWithImpl<$Res> | ||||||
|     implements _$PostTagCopyWith<$Res> { |     implements _$SnPostTagCopyWith<$Res> { | ||||||
|   __$PostTagCopyWithImpl(this._self, this._then); |   __$SnPostTagCopyWithImpl(this._self, this._then); | ||||||
|  |  | ||||||
|   final _PostTag _self; |   final _SnPostTag _self; | ||||||
|   final $Res Function(_PostTag) _then; |   final $Res Function(_SnPostTag) _then; | ||||||
|  |  | ||||||
| /// Create a copy of PostTag | /// 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,}) { | ||||||
|   return _then(_PostTag( |   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 | ||||||
|   | |||||||
| @@ -6,7 +6,7 @@ part of 'post_tag.dart'; | |||||||
| // JsonSerializableGenerator | // JsonSerializableGenerator | ||||||
| // ************************************************************************** | // ************************************************************************** | ||||||
|  |  | ||||||
| _PostTag _$PostTagFromJson(Map<String, dynamic> json) => _PostTag( | _SnPostTag _$SnPostTagFromJson(Map<String, dynamic> json) => _SnPostTag( | ||||||
|   id: json['id'] as String, |   id: json['id'] as String, | ||||||
|   slug: json['slug'] as String, |   slug: json['slug'] as String, | ||||||
|   name: json['name'] as String?, |   name: json['name'] as String?, | ||||||
| @@ -17,9 +17,10 @@ _PostTag _$PostTagFromJson(Map<String, dynamic> json) => _PostTag( | |||||||
|       const [], |       const [], | ||||||
| ); | ); | ||||||
|  |  | ||||||
| Map<String, dynamic> _$PostTagToJson(_PostTag instance) => <String, dynamic>{ | Map<String, dynamic> _$SnPostTagToJson(_SnPostTag instance) => | ||||||
|   'id': instance.id, |     <String, dynamic>{ | ||||||
|   'slug': instance.slug, |       'id': instance.id, | ||||||
|   'name': instance.name, |       'slug': instance.slug, | ||||||
|   'posts': instance.posts.map((e) => e.toJson()).toList(), |       'name': instance.name, | ||||||
| }; |       'posts': instance.posts.map((e) => e.toJson()).toList(), | ||||||
|  |     }; | ||||||
|   | |||||||
| @@ -35,6 +35,7 @@ sealed class SnStickerPack with _$SnStickerPack { | |||||||
|     required DateTime createdAt, |     required DateTime createdAt, | ||||||
|     required DateTime updatedAt, |     required DateTime updatedAt, | ||||||
|     required DateTime? deletedAt, |     required DateTime? deletedAt, | ||||||
|  |     @Default([]) List<SnSticker> stickers, | ||||||
|   }) = _SnStickerPack; |   }) = _SnStickerPack; | ||||||
|  |  | ||||||
|   factory SnStickerPack.fromJson(Map<String, dynamic> json) => |   factory SnStickerPack.fromJson(Map<String, dynamic> json) => | ||||||
|   | |||||||
| @@ -338,7 +338,7 @@ $SnStickerPackCopyWith<$Res>? get pack { | |||||||
| /// @nodoc | /// @nodoc | ||||||
| mixin _$SnStickerPack { | mixin _$SnStickerPack { | ||||||
|  |  | ||||||
|  String get id; String get name; String get description; String get prefix; String get publisherId; SnPublisher? get publisher; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; |  String get id; String get name; String get description; String get prefix; String get publisherId; SnPublisher? get publisher; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; List<SnSticker> get stickers; | ||||||
| /// Create a copy of SnStickerPack | /// Create a copy of SnStickerPack | ||||||
| /// 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) | ||||||
| @@ -351,16 +351,16 @@ $SnStickerPackCopyWith<SnStickerPack> get copyWith => _$SnStickerPackCopyWithImp | |||||||
|  |  | ||||||
| @override | @override | ||||||
| bool operator ==(Object other) { | bool operator ==(Object other) { | ||||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is SnStickerPack&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&(identical(other.prefix, prefix) || other.prefix == prefix)&&(identical(other.publisherId, publisherId) || other.publisherId == publisherId)&&(identical(other.publisher, publisher) || other.publisher == publisher)&&(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 SnStickerPack&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&(identical(other.prefix, prefix) || other.prefix == prefix)&&(identical(other.publisherId, publisherId) || other.publisherId == publisherId)&&(identical(other.publisher, publisher) || other.publisher == publisher)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&const DeepCollectionEquality().equals(other.stickers, stickers)); | ||||||
| } | } | ||||||
|  |  | ||||||
| @JsonKey(includeFromJson: false, includeToJson: false) | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
| @override | @override | ||||||
| int get hashCode => Object.hash(runtimeType,id,name,description,prefix,publisherId,publisher,createdAt,updatedAt,deletedAt); | int get hashCode => Object.hash(runtimeType,id,name,description,prefix,publisherId,publisher,createdAt,updatedAt,deletedAt,const DeepCollectionEquality().hash(stickers)); | ||||||
|  |  | ||||||
| @override | @override | ||||||
| String toString() { | String toString() { | ||||||
|   return 'SnStickerPack(id: $id, name: $name, description: $description, prefix: $prefix, publisherId: $publisherId, publisher: $publisher, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; |   return 'SnStickerPack(id: $id, name: $name, description: $description, prefix: $prefix, publisherId: $publisherId, publisher: $publisher, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, stickers: $stickers)'; | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -371,7 +371,7 @@ abstract mixin class $SnStickerPackCopyWith<$Res>  { | |||||||
|   factory $SnStickerPackCopyWith(SnStickerPack value, $Res Function(SnStickerPack) _then) = _$SnStickerPackCopyWithImpl; |   factory $SnStickerPackCopyWith(SnStickerPack value, $Res Function(SnStickerPack) _then) = _$SnStickerPackCopyWithImpl; | ||||||
| @useResult | @useResult | ||||||
| $Res call({ | $Res call({ | ||||||
|  String id, String name, String description, String prefix, String publisherId, SnPublisher? publisher, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt |  String id, String name, String description, String prefix, String publisherId, SnPublisher? publisher, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, List<SnSticker> stickers | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -388,7 +388,7 @@ class _$SnStickerPackCopyWithImpl<$Res> | |||||||
|  |  | ||||||
| /// Create a copy of SnStickerPack | /// Create a copy of SnStickerPack | ||||||
| /// with the given fields replaced by the non-null parameter values. | /// with the given fields replaced by the non-null parameter values. | ||||||
| @pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? name = null,Object? description = null,Object? prefix = null,Object? publisherId = null,Object? publisher = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) { | @pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? name = null,Object? description = null,Object? prefix = null,Object? publisherId = null,Object? publisher = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? stickers = 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,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable | as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable | ||||||
| @@ -399,7 +399,8 @@ as String,publisher: freezed == publisher ? _self.publisher : publisher // ignor | |||||||
| as SnPublisher?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable | as SnPublisher?,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?,stickers: null == stickers ? _self.stickers : stickers // ignore: cast_nullable_to_non_nullable | ||||||
|  | as List<SnSticker>, | ||||||
|   )); |   )); | ||||||
| } | } | ||||||
| /// Create a copy of SnStickerPack | /// Create a copy of SnStickerPack | ||||||
| @@ -493,10 +494,10 @@ return $default(_that);case _: | |||||||
| /// } | /// } | ||||||
| /// ``` | /// ``` | ||||||
|  |  | ||||||
| @optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id,  String name,  String description,  String prefix,  String publisherId,  SnPublisher? publisher,  DateTime createdAt,  DateTime updatedAt,  DateTime? deletedAt)?  $default,{required TResult orElse(),}) {final _that = this; | @optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id,  String name,  String description,  String prefix,  String publisherId,  SnPublisher? publisher,  DateTime createdAt,  DateTime updatedAt,  DateTime? deletedAt,  List<SnSticker> stickers)?  $default,{required TResult orElse(),}) {final _that = this; | ||||||
| switch (_that) { | switch (_that) { | ||||||
| case _SnStickerPack() when $default != null: | case _SnStickerPack() when $default != null: | ||||||
| return $default(_that.id,_that.name,_that.description,_that.prefix,_that.publisherId,_that.publisher,_that.createdAt,_that.updatedAt,_that.deletedAt);case _: | return $default(_that.id,_that.name,_that.description,_that.prefix,_that.publisherId,_that.publisher,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.stickers);case _: | ||||||
|   return orElse(); |   return orElse(); | ||||||
|  |  | ||||||
| } | } | ||||||
| @@ -514,10 +515,10 @@ return $default(_that.id,_that.name,_that.description,_that.prefix,_that.publish | |||||||
| /// } | /// } | ||||||
| /// ``` | /// ``` | ||||||
|  |  | ||||||
| @optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id,  String name,  String description,  String prefix,  String publisherId,  SnPublisher? publisher,  DateTime createdAt,  DateTime updatedAt,  DateTime? deletedAt)  $default,) {final _that = this; | @optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id,  String name,  String description,  String prefix,  String publisherId,  SnPublisher? publisher,  DateTime createdAt,  DateTime updatedAt,  DateTime? deletedAt,  List<SnSticker> stickers)  $default,) {final _that = this; | ||||||
| switch (_that) { | switch (_that) { | ||||||
| case _SnStickerPack(): | case _SnStickerPack(): | ||||||
| return $default(_that.id,_that.name,_that.description,_that.prefix,_that.publisherId,_that.publisher,_that.createdAt,_that.updatedAt,_that.deletedAt);} | return $default(_that.id,_that.name,_that.description,_that.prefix,_that.publisherId,_that.publisher,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.stickers);} | ||||||
| } | } | ||||||
| /// A variant of `when` that fallback to returning `null` | /// A variant of `when` that fallback to returning `null` | ||||||
| /// | /// | ||||||
| @@ -531,10 +532,10 @@ return $default(_that.id,_that.name,_that.description,_that.prefix,_that.publish | |||||||
| /// } | /// } | ||||||
| /// ``` | /// ``` | ||||||
|  |  | ||||||
| @optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id,  String name,  String description,  String prefix,  String publisherId,  SnPublisher? publisher,  DateTime createdAt,  DateTime updatedAt,  DateTime? deletedAt)?  $default,) {final _that = this; | @optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id,  String name,  String description,  String prefix,  String publisherId,  SnPublisher? publisher,  DateTime createdAt,  DateTime updatedAt,  DateTime? deletedAt,  List<SnSticker> stickers)?  $default,) {final _that = this; | ||||||
| switch (_that) { | switch (_that) { | ||||||
| case _SnStickerPack() when $default != null: | case _SnStickerPack() when $default != null: | ||||||
| return $default(_that.id,_that.name,_that.description,_that.prefix,_that.publisherId,_that.publisher,_that.createdAt,_that.updatedAt,_that.deletedAt);case _: | return $default(_that.id,_that.name,_that.description,_that.prefix,_that.publisherId,_that.publisher,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.stickers);case _: | ||||||
|   return null; |   return null; | ||||||
|  |  | ||||||
| } | } | ||||||
| @@ -546,7 +547,7 @@ return $default(_that.id,_that.name,_that.description,_that.prefix,_that.publish | |||||||
| @JsonSerializable() | @JsonSerializable() | ||||||
|  |  | ||||||
| class _SnStickerPack implements SnStickerPack { | class _SnStickerPack implements SnStickerPack { | ||||||
|   const _SnStickerPack({required this.id, required this.name, required this.description, required this.prefix, required this.publisherId, required this.publisher, required this.createdAt, required this.updatedAt, required this.deletedAt}); |   const _SnStickerPack({required this.id, required this.name, required this.description, required this.prefix, required this.publisherId, required this.publisher, required this.createdAt, required this.updatedAt, required this.deletedAt, final  List<SnSticker> stickers = const []}): _stickers = stickers; | ||||||
|   factory _SnStickerPack.fromJson(Map<String, dynamic> json) => _$SnStickerPackFromJson(json); |   factory _SnStickerPack.fromJson(Map<String, dynamic> json) => _$SnStickerPackFromJson(json); | ||||||
|  |  | ||||||
| @override final  String id; | @override final  String id; | ||||||
| @@ -558,6 +559,13 @@ class _SnStickerPack implements SnStickerPack { | |||||||
| @override final  DateTime createdAt; | @override final  DateTime createdAt; | ||||||
| @override final  DateTime updatedAt; | @override final  DateTime updatedAt; | ||||||
| @override final  DateTime? deletedAt; | @override final  DateTime? deletedAt; | ||||||
|  |  final  List<SnSticker> _stickers; | ||||||
|  | @override@JsonKey() List<SnSticker> get stickers { | ||||||
|  |   if (_stickers is EqualUnmodifiableListView) return _stickers; | ||||||
|  |   // ignore: implicit_dynamic_type | ||||||
|  |   return EqualUnmodifiableListView(_stickers); | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
| /// Create a copy of SnStickerPack | /// Create a copy of SnStickerPack | ||||||
| /// with the given fields replaced by the non-null parameter values. | /// with the given fields replaced by the non-null parameter values. | ||||||
| @@ -572,16 +580,16 @@ Map<String, dynamic> toJson() { | |||||||
|  |  | ||||||
| @override | @override | ||||||
| bool operator ==(Object other) { | bool operator ==(Object other) { | ||||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnStickerPack&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&(identical(other.prefix, prefix) || other.prefix == prefix)&&(identical(other.publisherId, publisherId) || other.publisherId == publisherId)&&(identical(other.publisher, publisher) || other.publisher == publisher)&&(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 _SnStickerPack&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&(identical(other.prefix, prefix) || other.prefix == prefix)&&(identical(other.publisherId, publisherId) || other.publisherId == publisherId)&&(identical(other.publisher, publisher) || other.publisher == publisher)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&const DeepCollectionEquality().equals(other._stickers, _stickers)); | ||||||
| } | } | ||||||
|  |  | ||||||
| @JsonKey(includeFromJson: false, includeToJson: false) | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
| @override | @override | ||||||
| int get hashCode => Object.hash(runtimeType,id,name,description,prefix,publisherId,publisher,createdAt,updatedAt,deletedAt); | int get hashCode => Object.hash(runtimeType,id,name,description,prefix,publisherId,publisher,createdAt,updatedAt,deletedAt,const DeepCollectionEquality().hash(_stickers)); | ||||||
|  |  | ||||||
| @override | @override | ||||||
| String toString() { | String toString() { | ||||||
|   return 'SnStickerPack(id: $id, name: $name, description: $description, prefix: $prefix, publisherId: $publisherId, publisher: $publisher, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; |   return 'SnStickerPack(id: $id, name: $name, description: $description, prefix: $prefix, publisherId: $publisherId, publisher: $publisher, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, stickers: $stickers)'; | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -592,7 +600,7 @@ abstract mixin class _$SnStickerPackCopyWith<$Res> implements $SnStickerPackCopy | |||||||
|   factory _$SnStickerPackCopyWith(_SnStickerPack value, $Res Function(_SnStickerPack) _then) = __$SnStickerPackCopyWithImpl; |   factory _$SnStickerPackCopyWith(_SnStickerPack value, $Res Function(_SnStickerPack) _then) = __$SnStickerPackCopyWithImpl; | ||||||
| @override @useResult | @override @useResult | ||||||
| $Res call({ | $Res call({ | ||||||
|  String id, String name, String description, String prefix, String publisherId, SnPublisher? publisher, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt |  String id, String name, String description, String prefix, String publisherId, SnPublisher? publisher, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, List<SnSticker> stickers | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -609,7 +617,7 @@ class __$SnStickerPackCopyWithImpl<$Res> | |||||||
|  |  | ||||||
| /// Create a copy of SnStickerPack | /// Create a copy of SnStickerPack | ||||||
| /// with the given fields replaced by the non-null parameter values. | /// with the given fields replaced by the non-null parameter values. | ||||||
| @override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? name = null,Object? description = null,Object? prefix = null,Object? publisherId = null,Object? publisher = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) { | @override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? name = null,Object? description = null,Object? prefix = null,Object? publisherId = null,Object? publisher = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? stickers = null,}) { | ||||||
|   return _then(_SnStickerPack( |   return _then(_SnStickerPack( | ||||||
| id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable | id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable | ||||||
| as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable | as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable | ||||||
| @@ -620,7 +628,8 @@ as String,publisher: freezed == publisher ? _self.publisher : publisher // ignor | |||||||
| as SnPublisher?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable | as SnPublisher?,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?,stickers: null == stickers ? _self._stickers : stickers // ignore: cast_nullable_to_non_nullable | ||||||
|  | as List<SnSticker>, | ||||||
|   )); |   )); | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -54,6 +54,11 @@ _SnStickerPack _$SnStickerPackFromJson(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), | ||||||
|  |       stickers: | ||||||
|  |           (json['stickers'] as List<dynamic>?) | ||||||
|  |               ?.map((e) => SnSticker.fromJson(e as Map<String, dynamic>)) | ||||||
|  |               .toList() ?? | ||||||
|  |           const [], | ||||||
|     ); |     ); | ||||||
|  |  | ||||||
| Map<String, dynamic> _$SnStickerPackToJson(_SnStickerPack instance) => | Map<String, dynamic> _$SnStickerPackToJson(_SnStickerPack instance) => | ||||||
| @@ -67,4 +72,5 @@ Map<String, dynamic> _$SnStickerPackToJson(_SnStickerPack 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(), | ||||||
|  |       'stickers': instance.stickers.map((e) => e.toJson()).toList(), | ||||||
|     }; |     }; | ||||||
|   | |||||||
| @@ -25,6 +25,32 @@ sealed class SnAccount with _$SnAccount { | |||||||
|       _$SnAccountFromJson(json); |       _$SnAccountFromJson(json); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @freezed | ||||||
|  | sealed class ProfileLink with _$ProfileLink { | ||||||
|  |   const factory ProfileLink({required String name, required String url}) = | ||||||
|  |       _ProfileLink; | ||||||
|  |  | ||||||
|  |   factory ProfileLink.fromJson(Map<String, dynamic> json) => | ||||||
|  |       _$ProfileLinkFromJson(json); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class ProfileLinkConverter | ||||||
|  |     implements JsonConverter<List<ProfileLink>, dynamic> { | ||||||
|  |   const ProfileLinkConverter(); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   List<ProfileLink> fromJson(dynamic json) { | ||||||
|  |     return json is List<dynamic> | ||||||
|  |         ? json.map((e) => ProfileLink.fromJson(e)).cast<ProfileLink>().toList() | ||||||
|  |         : <ProfileLink>[]; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   List<dynamic> toJson(List<ProfileLink> object) { | ||||||
|  |     return object.map((e) => e.toJson()).toList(); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
| @freezed | @freezed | ||||||
| sealed class SnAccountProfile with _$SnAccountProfile { | sealed class SnAccountProfile with _$SnAccountProfile { | ||||||
|   const factory SnAccountProfile({ |   const factory SnAccountProfile({ | ||||||
| @@ -38,6 +64,7 @@ sealed class SnAccountProfile with _$SnAccountProfile { | |||||||
|     @Default('') String location, |     @Default('') String location, | ||||||
|     @Default('') String timeZone, |     @Default('') String timeZone, | ||||||
|     DateTime? birthday, |     DateTime? birthday, | ||||||
|  |     @ProfileLinkConverter() @Default([]) List<ProfileLink> links, | ||||||
|     DateTime? lastSeenAt, |     DateTime? lastSeenAt, | ||||||
|     SnAccountBadge? activeBadge, |     SnAccountBadge? activeBadge, | ||||||
|     required int experience, |     required int experience, | ||||||
|   | |||||||
| @@ -347,10 +347,270 @@ $SnWalletSubscriptionRefCopyWith<$Res>? get perkSubscription { | |||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | mixin _$ProfileLink { | ||||||
|  |  | ||||||
|  |  String get name; String get url; | ||||||
|  | /// Create a copy of ProfileLink | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
|  | $ProfileLinkCopyWith<ProfileLink> get copyWith => _$ProfileLinkCopyWithImpl<ProfileLink>(this as ProfileLink, _$identity); | ||||||
|  |  | ||||||
|  |   /// Serializes this ProfileLink to a JSON map. | ||||||
|  |   Map<String, dynamic> toJson(); | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | bool operator ==(Object other) { | ||||||
|  |   return identical(this, other) || (other.runtimeType == runtimeType&&other is ProfileLink&&(identical(other.name, name) || other.name == name)&&(identical(other.url, url) || other.url == url)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  | @override | ||||||
|  | int get hashCode => Object.hash(runtimeType,name,url); | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | String toString() { | ||||||
|  |   return 'ProfileLink(name: $name, url: $url)'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | abstract mixin class $ProfileLinkCopyWith<$Res>  { | ||||||
|  |   factory $ProfileLinkCopyWith(ProfileLink value, $Res Function(ProfileLink) _then) = _$ProfileLinkCopyWithImpl; | ||||||
|  | @useResult | ||||||
|  | $Res call({ | ||||||
|  |  String name, String url | ||||||
|  | }); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  | /// @nodoc | ||||||
|  | class _$ProfileLinkCopyWithImpl<$Res> | ||||||
|  |     implements $ProfileLinkCopyWith<$Res> { | ||||||
|  |   _$ProfileLinkCopyWithImpl(this._self, this._then); | ||||||
|  |  | ||||||
|  |   final ProfileLink _self; | ||||||
|  |   final $Res Function(ProfileLink) _then; | ||||||
|  |  | ||||||
|  | /// Create a copy of ProfileLink | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @pragma('vm:prefer-inline') @override $Res call({Object? name = null,Object? url = null,}) { | ||||||
|  |   return _then(_self.copyWith( | ||||||
|  | name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,url: null == url ? _self.url : url // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String, | ||||||
|  |   )); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | /// Adds pattern-matching-related methods to [ProfileLink]. | ||||||
|  | extension ProfileLinkPatterns on ProfileLink { | ||||||
|  | /// 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( _ProfileLink value)?  $default,{required TResult orElse(),}){ | ||||||
|  | final _that = this; | ||||||
|  | switch (_that) { | ||||||
|  | case _ProfileLink() 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( _ProfileLink value)  $default,){ | ||||||
|  | final _that = this; | ||||||
|  | switch (_that) { | ||||||
|  | case _ProfileLink(): | ||||||
|  | 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( _ProfileLink value)?  $default,){ | ||||||
|  | final _that = this; | ||||||
|  | switch (_that) { | ||||||
|  | case _ProfileLink() when $default != null: | ||||||
|  | return $default(_that);case _: | ||||||
|  |   return null; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  | /// A variant of `when` that fallback to an `orElse` callback. | ||||||
|  | /// | ||||||
|  | /// It is equivalent to doing: | ||||||
|  | /// ```dart | ||||||
|  | /// switch (sealedClass) { | ||||||
|  | ///   case Subclass(:final field): | ||||||
|  | ///     return ...; | ||||||
|  | ///   case _: | ||||||
|  | ///     return orElse(); | ||||||
|  | /// } | ||||||
|  | /// ``` | ||||||
|  |  | ||||||
|  | @optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String name,  String url)?  $default,{required TResult orElse(),}) {final _that = this; | ||||||
|  | switch (_that) { | ||||||
|  | case _ProfileLink() when $default != null: | ||||||
|  | return $default(_that.name,_that.url);case _: | ||||||
|  |   return orElse(); | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  | /// A `switch`-like method, using callbacks. | ||||||
|  | /// | ||||||
|  | /// As opposed to `map`, this offers destructuring. | ||||||
|  | /// It is equivalent to doing: | ||||||
|  | /// ```dart | ||||||
|  | /// switch (sealedClass) { | ||||||
|  | ///   case Subclass(:final field): | ||||||
|  | ///     return ...; | ||||||
|  | ///   case Subclass2(:final field2): | ||||||
|  | ///     return ...; | ||||||
|  | /// } | ||||||
|  | /// ``` | ||||||
|  |  | ||||||
|  | @optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String name,  String url)  $default,) {final _that = this; | ||||||
|  | switch (_that) { | ||||||
|  | case _ProfileLink(): | ||||||
|  | return $default(_that.name,_that.url);} | ||||||
|  | } | ||||||
|  | /// A variant of `when` that fallback to returning `null` | ||||||
|  | /// | ||||||
|  | /// It is equivalent to doing: | ||||||
|  | /// ```dart | ||||||
|  | /// switch (sealedClass) { | ||||||
|  | ///   case Subclass(:final field): | ||||||
|  | ///     return ...; | ||||||
|  | ///   case _: | ||||||
|  | ///     return null; | ||||||
|  | /// } | ||||||
|  | /// ``` | ||||||
|  |  | ||||||
|  | @optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String name,  String url)?  $default,) {final _that = this; | ||||||
|  | switch (_that) { | ||||||
|  | case _ProfileLink() when $default != null: | ||||||
|  | return $default(_that.name,_that.url);case _: | ||||||
|  |   return null; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | @JsonSerializable() | ||||||
|  |  | ||||||
|  | class _ProfileLink implements ProfileLink { | ||||||
|  |   const _ProfileLink({required this.name, required this.url}); | ||||||
|  |   factory _ProfileLink.fromJson(Map<String, dynamic> json) => _$ProfileLinkFromJson(json); | ||||||
|  |  | ||||||
|  | @override final  String name; | ||||||
|  | @override final  String url; | ||||||
|  |  | ||||||
|  | /// Create a copy of ProfileLink | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
|  | _$ProfileLinkCopyWith<_ProfileLink> get copyWith => __$ProfileLinkCopyWithImpl<_ProfileLink>(this, _$identity); | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | Map<String, dynamic> toJson() { | ||||||
|  |   return _$ProfileLinkToJson(this, ); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | bool operator ==(Object other) { | ||||||
|  |   return identical(this, other) || (other.runtimeType == runtimeType&&other is _ProfileLink&&(identical(other.name, name) || other.name == name)&&(identical(other.url, url) || other.url == url)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  | @override | ||||||
|  | int get hashCode => Object.hash(runtimeType,name,url); | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | String toString() { | ||||||
|  |   return 'ProfileLink(name: $name, url: $url)'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | abstract mixin class _$ProfileLinkCopyWith<$Res> implements $ProfileLinkCopyWith<$Res> { | ||||||
|  |   factory _$ProfileLinkCopyWith(_ProfileLink value, $Res Function(_ProfileLink) _then) = __$ProfileLinkCopyWithImpl; | ||||||
|  | @override @useResult | ||||||
|  | $Res call({ | ||||||
|  |  String name, String url | ||||||
|  | }); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  | /// @nodoc | ||||||
|  | class __$ProfileLinkCopyWithImpl<$Res> | ||||||
|  |     implements _$ProfileLinkCopyWith<$Res> { | ||||||
|  |   __$ProfileLinkCopyWithImpl(this._self, this._then); | ||||||
|  |  | ||||||
|  |   final _ProfileLink _self; | ||||||
|  |   final $Res Function(_ProfileLink) _then; | ||||||
|  |  | ||||||
|  | /// Create a copy of ProfileLink | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override @pragma('vm:prefer-inline') $Res call({Object? name = null,Object? url = null,}) { | ||||||
|  |   return _then(_ProfileLink( | ||||||
|  | name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,url: null == url ? _self.url : url // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String, | ||||||
|  |   )); | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
| /// @nodoc | /// @nodoc | ||||||
| mixin _$SnAccountProfile { | mixin _$SnAccountProfile { | ||||||
|  |  | ||||||
|  String get id; String get firstName; String get middleName; String get lastName; String get bio; String get gender; String get pronouns; String get location; String get timeZone; DateTime? get birthday; DateTime? get lastSeenAt; SnAccountBadge? get activeBadge; int get experience; int get level; double get levelingProgress; SnCloudFile? get picture; SnCloudFile? get background; SnVerificationMark? get verification; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; |  String get id; String get firstName; String get middleName; String get lastName; String get bio; String get gender; String get pronouns; String get location; String get timeZone; DateTime? get birthday;@ProfileLinkConverter() List<ProfileLink> get links; DateTime? get lastSeenAt; SnAccountBadge? get activeBadge; int get experience; int get level; double get levelingProgress; SnCloudFile? get picture; SnCloudFile? get background; SnVerificationMark? get verification; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; | ||||||
| /// Create a copy of SnAccountProfile | /// Create a copy of SnAccountProfile | ||||||
| /// 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) | ||||||
| @@ -363,16 +623,16 @@ $SnAccountProfileCopyWith<SnAccountProfile> get copyWith => _$SnAccountProfileCo | |||||||
|  |  | ||||||
| @override | @override | ||||||
| bool operator ==(Object other) { | bool operator ==(Object other) { | ||||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is SnAccountProfile&&(identical(other.id, id) || other.id == id)&&(identical(other.firstName, firstName) || other.firstName == firstName)&&(identical(other.middleName, middleName) || other.middleName == middleName)&&(identical(other.lastName, lastName) || other.lastName == lastName)&&(identical(other.bio, bio) || other.bio == bio)&&(identical(other.gender, gender) || other.gender == gender)&&(identical(other.pronouns, pronouns) || other.pronouns == pronouns)&&(identical(other.location, location) || other.location == location)&&(identical(other.timeZone, timeZone) || other.timeZone == timeZone)&&(identical(other.birthday, birthday) || other.birthday == birthday)&&(identical(other.lastSeenAt, lastSeenAt) || other.lastSeenAt == lastSeenAt)&&(identical(other.activeBadge, activeBadge) || other.activeBadge == activeBadge)&&(identical(other.experience, experience) || other.experience == experience)&&(identical(other.level, level) || other.level == level)&&(identical(other.levelingProgress, levelingProgress) || other.levelingProgress == levelingProgress)&&(identical(other.picture, picture) || other.picture == picture)&&(identical(other.background, background) || other.background == background)&&(identical(other.verification, verification) || other.verification == verification)&&(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 SnAccountProfile&&(identical(other.id, id) || other.id == id)&&(identical(other.firstName, firstName) || other.firstName == firstName)&&(identical(other.middleName, middleName) || other.middleName == middleName)&&(identical(other.lastName, lastName) || other.lastName == lastName)&&(identical(other.bio, bio) || other.bio == bio)&&(identical(other.gender, gender) || other.gender == gender)&&(identical(other.pronouns, pronouns) || other.pronouns == pronouns)&&(identical(other.location, location) || other.location == location)&&(identical(other.timeZone, timeZone) || other.timeZone == timeZone)&&(identical(other.birthday, birthday) || other.birthday == birthday)&&const DeepCollectionEquality().equals(other.links, links)&&(identical(other.lastSeenAt, lastSeenAt) || other.lastSeenAt == lastSeenAt)&&(identical(other.activeBadge, activeBadge) || other.activeBadge == activeBadge)&&(identical(other.experience, experience) || other.experience == experience)&&(identical(other.level, level) || other.level == level)&&(identical(other.levelingProgress, levelingProgress) || other.levelingProgress == levelingProgress)&&(identical(other.picture, picture) || other.picture == picture)&&(identical(other.background, background) || other.background == background)&&(identical(other.verification, verification) || other.verification == verification)&&(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,firstName,middleName,lastName,bio,gender,pronouns,location,timeZone,birthday,lastSeenAt,activeBadge,experience,level,levelingProgress,picture,background,verification,createdAt,updatedAt,deletedAt]); | int get hashCode => Object.hashAll([runtimeType,id,firstName,middleName,lastName,bio,gender,pronouns,location,timeZone,birthday,const DeepCollectionEquality().hash(links),lastSeenAt,activeBadge,experience,level,levelingProgress,picture,background,verification,createdAt,updatedAt,deletedAt]); | ||||||
|  |  | ||||||
| @override | @override | ||||||
| String toString() { | String toString() { | ||||||
|   return 'SnAccountProfile(id: $id, firstName: $firstName, middleName: $middleName, lastName: $lastName, bio: $bio, gender: $gender, pronouns: $pronouns, location: $location, timeZone: $timeZone, birthday: $birthday, lastSeenAt: $lastSeenAt, activeBadge: $activeBadge, experience: $experience, level: $level, levelingProgress: $levelingProgress, picture: $picture, background: $background, verification: $verification, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; |   return 'SnAccountProfile(id: $id, firstName: $firstName, middleName: $middleName, lastName: $lastName, bio: $bio, gender: $gender, pronouns: $pronouns, location: $location, timeZone: $timeZone, birthday: $birthday, links: $links, lastSeenAt: $lastSeenAt, activeBadge: $activeBadge, experience: $experience, level: $level, levelingProgress: $levelingProgress, picture: $picture, background: $background, verification: $verification, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -383,7 +643,7 @@ abstract mixin class $SnAccountProfileCopyWith<$Res>  { | |||||||
|   factory $SnAccountProfileCopyWith(SnAccountProfile value, $Res Function(SnAccountProfile) _then) = _$SnAccountProfileCopyWithImpl; |   factory $SnAccountProfileCopyWith(SnAccountProfile value, $Res Function(SnAccountProfile) _then) = _$SnAccountProfileCopyWithImpl; | ||||||
| @useResult | @useResult | ||||||
| $Res call({ | $Res call({ | ||||||
|  String id, String firstName, String middleName, String lastName, String bio, String gender, String pronouns, String location, String timeZone, DateTime? birthday, DateTime? lastSeenAt, SnAccountBadge? activeBadge, int experience, int level, double levelingProgress, SnCloudFile? picture, SnCloudFile? background, SnVerificationMark? verification, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt |  String id, String firstName, String middleName, String lastName, String bio, String gender, String pronouns, String location, String timeZone, DateTime? birthday,@ProfileLinkConverter() List<ProfileLink> links, DateTime? lastSeenAt, SnAccountBadge? activeBadge, int experience, int level, double levelingProgress, SnCloudFile? picture, SnCloudFile? background, SnVerificationMark? verification, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -400,7 +660,7 @@ class _$SnAccountProfileCopyWithImpl<$Res> | |||||||
|  |  | ||||||
| /// Create a copy of SnAccountProfile | /// Create a copy of SnAccountProfile | ||||||
| /// 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? firstName = null,Object? middleName = null,Object? lastName = null,Object? bio = null,Object? gender = null,Object? pronouns = null,Object? location = null,Object? timeZone = null,Object? birthday = freezed,Object? lastSeenAt = freezed,Object? activeBadge = freezed,Object? experience = null,Object? level = null,Object? levelingProgress = null,Object? picture = freezed,Object? background = freezed,Object? verification = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) { | @pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? firstName = null,Object? middleName = null,Object? lastName = null,Object? bio = null,Object? gender = null,Object? pronouns = null,Object? location = null,Object? timeZone = null,Object? birthday = freezed,Object? links = null,Object? lastSeenAt = freezed,Object? activeBadge = freezed,Object? experience = null,Object? level = null,Object? levelingProgress = null,Object? picture = freezed,Object? background = freezed,Object? verification = freezed,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,firstName: null == firstName ? _self.firstName : firstName // ignore: cast_nullable_to_non_nullable | as String,firstName: null == firstName ? _self.firstName : firstName // ignore: cast_nullable_to_non_nullable | ||||||
| @@ -412,7 +672,8 @@ as String,pronouns: null == pronouns ? _self.pronouns : pronouns // ignore: cast | |||||||
| as String,location: null == location ? _self.location : location // ignore: cast_nullable_to_non_nullable | as String,location: null == location ? _self.location : location // ignore: cast_nullable_to_non_nullable | ||||||
| as String,timeZone: null == timeZone ? _self.timeZone : timeZone // ignore: cast_nullable_to_non_nullable | as String,timeZone: null == timeZone ? _self.timeZone : timeZone // ignore: cast_nullable_to_non_nullable | ||||||
| as String,birthday: freezed == birthday ? _self.birthday : birthday // ignore: cast_nullable_to_non_nullable | as String,birthday: freezed == birthday ? _self.birthday : birthday // ignore: cast_nullable_to_non_nullable | ||||||
| as DateTime?,lastSeenAt: freezed == lastSeenAt ? _self.lastSeenAt : lastSeenAt // ignore: cast_nullable_to_non_nullable | as DateTime?,links: null == links ? _self.links : links // ignore: cast_nullable_to_non_nullable | ||||||
|  | as List<ProfileLink>,lastSeenAt: freezed == lastSeenAt ? _self.lastSeenAt : lastSeenAt // ignore: cast_nullable_to_non_nullable | ||||||
| as DateTime?,activeBadge: freezed == activeBadge ? _self.activeBadge : activeBadge // ignore: cast_nullable_to_non_nullable | as DateTime?,activeBadge: freezed == activeBadge ? _self.activeBadge : activeBadge // ignore: cast_nullable_to_non_nullable | ||||||
| as SnAccountBadge?,experience: null == experience ? _self.experience : experience // ignore: cast_nullable_to_non_nullable | as SnAccountBadge?,experience: null == experience ? _self.experience : experience // ignore: cast_nullable_to_non_nullable | ||||||
| as int,level: null == level ? _self.level : level // ignore: cast_nullable_to_non_nullable | as int,level: null == level ? _self.level : level // ignore: cast_nullable_to_non_nullable | ||||||
| @@ -553,10 +814,10 @@ return $default(_that);case _: | |||||||
| /// } | /// } | ||||||
| /// ``` | /// ``` | ||||||
|  |  | ||||||
| @optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id,  String firstName,  String middleName,  String lastName,  String bio,  String gender,  String pronouns,  String location,  String timeZone,  DateTime? birthday,  DateTime? lastSeenAt,  SnAccountBadge? activeBadge,  int experience,  int level,  double levelingProgress,  SnCloudFile? picture,  SnCloudFile? background,  SnVerificationMark? verification,  DateTime createdAt,  DateTime updatedAt,  DateTime? deletedAt)?  $default,{required TResult orElse(),}) {final _that = this; | @optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id,  String firstName,  String middleName,  String lastName,  String bio,  String gender,  String pronouns,  String location,  String timeZone,  DateTime? birthday, @ProfileLinkConverter()  List<ProfileLink> links,  DateTime? lastSeenAt,  SnAccountBadge? activeBadge,  int experience,  int level,  double levelingProgress,  SnCloudFile? picture,  SnCloudFile? background,  SnVerificationMark? verification,  DateTime createdAt,  DateTime updatedAt,  DateTime? deletedAt)?  $default,{required TResult orElse(),}) {final _that = this; | ||||||
| switch (_that) { | switch (_that) { | ||||||
| case _SnAccountProfile() when $default != null: | case _SnAccountProfile() when $default != null: | ||||||
| return $default(_that.id,_that.firstName,_that.middleName,_that.lastName,_that.bio,_that.gender,_that.pronouns,_that.location,_that.timeZone,_that.birthday,_that.lastSeenAt,_that.activeBadge,_that.experience,_that.level,_that.levelingProgress,_that.picture,_that.background,_that.verification,_that.createdAt,_that.updatedAt,_that.deletedAt);case _: | return $default(_that.id,_that.firstName,_that.middleName,_that.lastName,_that.bio,_that.gender,_that.pronouns,_that.location,_that.timeZone,_that.birthday,_that.links,_that.lastSeenAt,_that.activeBadge,_that.experience,_that.level,_that.levelingProgress,_that.picture,_that.background,_that.verification,_that.createdAt,_that.updatedAt,_that.deletedAt);case _: | ||||||
|   return orElse(); |   return orElse(); | ||||||
|  |  | ||||||
| } | } | ||||||
| @@ -574,10 +835,10 @@ return $default(_that.id,_that.firstName,_that.middleName,_that.lastName,_that.b | |||||||
| /// } | /// } | ||||||
| /// ``` | /// ``` | ||||||
|  |  | ||||||
| @optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id,  String firstName,  String middleName,  String lastName,  String bio,  String gender,  String pronouns,  String location,  String timeZone,  DateTime? birthday,  DateTime? lastSeenAt,  SnAccountBadge? activeBadge,  int experience,  int level,  double levelingProgress,  SnCloudFile? picture,  SnCloudFile? background,  SnVerificationMark? verification,  DateTime createdAt,  DateTime updatedAt,  DateTime? deletedAt)  $default,) {final _that = this; | @optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id,  String firstName,  String middleName,  String lastName,  String bio,  String gender,  String pronouns,  String location,  String timeZone,  DateTime? birthday, @ProfileLinkConverter()  List<ProfileLink> links,  DateTime? lastSeenAt,  SnAccountBadge? activeBadge,  int experience,  int level,  double levelingProgress,  SnCloudFile? picture,  SnCloudFile? background,  SnVerificationMark? verification,  DateTime createdAt,  DateTime updatedAt,  DateTime? deletedAt)  $default,) {final _that = this; | ||||||
| switch (_that) { | switch (_that) { | ||||||
| case _SnAccountProfile(): | case _SnAccountProfile(): | ||||||
| return $default(_that.id,_that.firstName,_that.middleName,_that.lastName,_that.bio,_that.gender,_that.pronouns,_that.location,_that.timeZone,_that.birthday,_that.lastSeenAt,_that.activeBadge,_that.experience,_that.level,_that.levelingProgress,_that.picture,_that.background,_that.verification,_that.createdAt,_that.updatedAt,_that.deletedAt);} | return $default(_that.id,_that.firstName,_that.middleName,_that.lastName,_that.bio,_that.gender,_that.pronouns,_that.location,_that.timeZone,_that.birthday,_that.links,_that.lastSeenAt,_that.activeBadge,_that.experience,_that.level,_that.levelingProgress,_that.picture,_that.background,_that.verification,_that.createdAt,_that.updatedAt,_that.deletedAt);} | ||||||
| } | } | ||||||
| /// A variant of `when` that fallback to returning `null` | /// A variant of `when` that fallback to returning `null` | ||||||
| /// | /// | ||||||
| @@ -591,10 +852,10 @@ return $default(_that.id,_that.firstName,_that.middleName,_that.lastName,_that.b | |||||||
| /// } | /// } | ||||||
| /// ``` | /// ``` | ||||||
|  |  | ||||||
| @optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id,  String firstName,  String middleName,  String lastName,  String bio,  String gender,  String pronouns,  String location,  String timeZone,  DateTime? birthday,  DateTime? lastSeenAt,  SnAccountBadge? activeBadge,  int experience,  int level,  double levelingProgress,  SnCloudFile? picture,  SnCloudFile? background,  SnVerificationMark? verification,  DateTime createdAt,  DateTime updatedAt,  DateTime? deletedAt)?  $default,) {final _that = this; | @optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id,  String firstName,  String middleName,  String lastName,  String bio,  String gender,  String pronouns,  String location,  String timeZone,  DateTime? birthday, @ProfileLinkConverter()  List<ProfileLink> links,  DateTime? lastSeenAt,  SnAccountBadge? activeBadge,  int experience,  int level,  double levelingProgress,  SnCloudFile? picture,  SnCloudFile? background,  SnVerificationMark? verification,  DateTime createdAt,  DateTime updatedAt,  DateTime? deletedAt)?  $default,) {final _that = this; | ||||||
| switch (_that) { | switch (_that) { | ||||||
| case _SnAccountProfile() when $default != null: | case _SnAccountProfile() when $default != null: | ||||||
| return $default(_that.id,_that.firstName,_that.middleName,_that.lastName,_that.bio,_that.gender,_that.pronouns,_that.location,_that.timeZone,_that.birthday,_that.lastSeenAt,_that.activeBadge,_that.experience,_that.level,_that.levelingProgress,_that.picture,_that.background,_that.verification,_that.createdAt,_that.updatedAt,_that.deletedAt);case _: | return $default(_that.id,_that.firstName,_that.middleName,_that.lastName,_that.bio,_that.gender,_that.pronouns,_that.location,_that.timeZone,_that.birthday,_that.links,_that.lastSeenAt,_that.activeBadge,_that.experience,_that.level,_that.levelingProgress,_that.picture,_that.background,_that.verification,_that.createdAt,_that.updatedAt,_that.deletedAt);case _: | ||||||
|   return null; |   return null; | ||||||
|  |  | ||||||
| } | } | ||||||
| @@ -606,7 +867,7 @@ return $default(_that.id,_that.firstName,_that.middleName,_that.lastName,_that.b | |||||||
| @JsonSerializable() | @JsonSerializable() | ||||||
|  |  | ||||||
| class _SnAccountProfile implements SnAccountProfile { | class _SnAccountProfile implements SnAccountProfile { | ||||||
|   const _SnAccountProfile({required this.id, this.firstName = '', this.middleName = '', this.lastName = '', this.bio = '', this.gender = '', this.pronouns = '', this.location = '', this.timeZone = '', this.birthday, this.lastSeenAt, this.activeBadge, required this.experience, required this.level, required this.levelingProgress, required this.picture, required this.background, required this.verification, required this.createdAt, required this.updatedAt, required this.deletedAt}); |   const _SnAccountProfile({required this.id, this.firstName = '', this.middleName = '', this.lastName = '', this.bio = '', this.gender = '', this.pronouns = '', this.location = '', this.timeZone = '', this.birthday, @ProfileLinkConverter() final  List<ProfileLink> links = const [], this.lastSeenAt, this.activeBadge, required this.experience, required this.level, required this.levelingProgress, required this.picture, required this.background, required this.verification, required this.createdAt, required this.updatedAt, required this.deletedAt}): _links = links; | ||||||
|   factory _SnAccountProfile.fromJson(Map<String, dynamic> json) => _$SnAccountProfileFromJson(json); |   factory _SnAccountProfile.fromJson(Map<String, dynamic> json) => _$SnAccountProfileFromJson(json); | ||||||
|  |  | ||||||
| @override final  String id; | @override final  String id; | ||||||
| @@ -619,6 +880,13 @@ class _SnAccountProfile implements SnAccountProfile { | |||||||
| @override@JsonKey() final  String location; | @override@JsonKey() final  String location; | ||||||
| @override@JsonKey() final  String timeZone; | @override@JsonKey() final  String timeZone; | ||||||
| @override final  DateTime? birthday; | @override final  DateTime? birthday; | ||||||
|  |  final  List<ProfileLink> _links; | ||||||
|  | @override@JsonKey()@ProfileLinkConverter() List<ProfileLink> get links { | ||||||
|  |   if (_links is EqualUnmodifiableListView) return _links; | ||||||
|  |   // ignore: implicit_dynamic_type | ||||||
|  |   return EqualUnmodifiableListView(_links); | ||||||
|  | } | ||||||
|  |  | ||||||
| @override final  DateTime? lastSeenAt; | @override final  DateTime? lastSeenAt; | ||||||
| @override final  SnAccountBadge? activeBadge; | @override final  SnAccountBadge? activeBadge; | ||||||
| @override final  int experience; | @override final  int experience; | ||||||
| @@ -644,16 +912,16 @@ Map<String, dynamic> toJson() { | |||||||
|  |  | ||||||
| @override | @override | ||||||
| bool operator ==(Object other) { | bool operator ==(Object other) { | ||||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnAccountProfile&&(identical(other.id, id) || other.id == id)&&(identical(other.firstName, firstName) || other.firstName == firstName)&&(identical(other.middleName, middleName) || other.middleName == middleName)&&(identical(other.lastName, lastName) || other.lastName == lastName)&&(identical(other.bio, bio) || other.bio == bio)&&(identical(other.gender, gender) || other.gender == gender)&&(identical(other.pronouns, pronouns) || other.pronouns == pronouns)&&(identical(other.location, location) || other.location == location)&&(identical(other.timeZone, timeZone) || other.timeZone == timeZone)&&(identical(other.birthday, birthday) || other.birthday == birthday)&&(identical(other.lastSeenAt, lastSeenAt) || other.lastSeenAt == lastSeenAt)&&(identical(other.activeBadge, activeBadge) || other.activeBadge == activeBadge)&&(identical(other.experience, experience) || other.experience == experience)&&(identical(other.level, level) || other.level == level)&&(identical(other.levelingProgress, levelingProgress) || other.levelingProgress == levelingProgress)&&(identical(other.picture, picture) || other.picture == picture)&&(identical(other.background, background) || other.background == background)&&(identical(other.verification, verification) || other.verification == verification)&&(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 _SnAccountProfile&&(identical(other.id, id) || other.id == id)&&(identical(other.firstName, firstName) || other.firstName == firstName)&&(identical(other.middleName, middleName) || other.middleName == middleName)&&(identical(other.lastName, lastName) || other.lastName == lastName)&&(identical(other.bio, bio) || other.bio == bio)&&(identical(other.gender, gender) || other.gender == gender)&&(identical(other.pronouns, pronouns) || other.pronouns == pronouns)&&(identical(other.location, location) || other.location == location)&&(identical(other.timeZone, timeZone) || other.timeZone == timeZone)&&(identical(other.birthday, birthday) || other.birthday == birthday)&&const DeepCollectionEquality().equals(other._links, _links)&&(identical(other.lastSeenAt, lastSeenAt) || other.lastSeenAt == lastSeenAt)&&(identical(other.activeBadge, activeBadge) || other.activeBadge == activeBadge)&&(identical(other.experience, experience) || other.experience == experience)&&(identical(other.level, level) || other.level == level)&&(identical(other.levelingProgress, levelingProgress) || other.levelingProgress == levelingProgress)&&(identical(other.picture, picture) || other.picture == picture)&&(identical(other.background, background) || other.background == background)&&(identical(other.verification, verification) || other.verification == verification)&&(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,firstName,middleName,lastName,bio,gender,pronouns,location,timeZone,birthday,lastSeenAt,activeBadge,experience,level,levelingProgress,picture,background,verification,createdAt,updatedAt,deletedAt]); | int get hashCode => Object.hashAll([runtimeType,id,firstName,middleName,lastName,bio,gender,pronouns,location,timeZone,birthday,const DeepCollectionEquality().hash(_links),lastSeenAt,activeBadge,experience,level,levelingProgress,picture,background,verification,createdAt,updatedAt,deletedAt]); | ||||||
|  |  | ||||||
| @override | @override | ||||||
| String toString() { | String toString() { | ||||||
|   return 'SnAccountProfile(id: $id, firstName: $firstName, middleName: $middleName, lastName: $lastName, bio: $bio, gender: $gender, pronouns: $pronouns, location: $location, timeZone: $timeZone, birthday: $birthday, lastSeenAt: $lastSeenAt, activeBadge: $activeBadge, experience: $experience, level: $level, levelingProgress: $levelingProgress, picture: $picture, background: $background, verification: $verification, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; |   return 'SnAccountProfile(id: $id, firstName: $firstName, middleName: $middleName, lastName: $lastName, bio: $bio, gender: $gender, pronouns: $pronouns, location: $location, timeZone: $timeZone, birthday: $birthday, links: $links, lastSeenAt: $lastSeenAt, activeBadge: $activeBadge, experience: $experience, level: $level, levelingProgress: $levelingProgress, picture: $picture, background: $background, verification: $verification, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -664,7 +932,7 @@ abstract mixin class _$SnAccountProfileCopyWith<$Res> implements $SnAccountProfi | |||||||
|   factory _$SnAccountProfileCopyWith(_SnAccountProfile value, $Res Function(_SnAccountProfile) _then) = __$SnAccountProfileCopyWithImpl; |   factory _$SnAccountProfileCopyWith(_SnAccountProfile value, $Res Function(_SnAccountProfile) _then) = __$SnAccountProfileCopyWithImpl; | ||||||
| @override @useResult | @override @useResult | ||||||
| $Res call({ | $Res call({ | ||||||
|  String id, String firstName, String middleName, String lastName, String bio, String gender, String pronouns, String location, String timeZone, DateTime? birthday, DateTime? lastSeenAt, SnAccountBadge? activeBadge, int experience, int level, double levelingProgress, SnCloudFile? picture, SnCloudFile? background, SnVerificationMark? verification, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt |  String id, String firstName, String middleName, String lastName, String bio, String gender, String pronouns, String location, String timeZone, DateTime? birthday,@ProfileLinkConverter() List<ProfileLink> links, DateTime? lastSeenAt, SnAccountBadge? activeBadge, int experience, int level, double levelingProgress, SnCloudFile? picture, SnCloudFile? background, SnVerificationMark? verification, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -681,7 +949,7 @@ class __$SnAccountProfileCopyWithImpl<$Res> | |||||||
|  |  | ||||||
| /// Create a copy of SnAccountProfile | /// Create a copy of SnAccountProfile | ||||||
| /// 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? firstName = null,Object? middleName = null,Object? lastName = null,Object? bio = null,Object? gender = null,Object? pronouns = null,Object? location = null,Object? timeZone = null,Object? birthday = freezed,Object? lastSeenAt = freezed,Object? activeBadge = freezed,Object? experience = null,Object? level = null,Object? levelingProgress = null,Object? picture = freezed,Object? background = freezed,Object? verification = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) { | @override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? firstName = null,Object? middleName = null,Object? lastName = null,Object? bio = null,Object? gender = null,Object? pronouns = null,Object? location = null,Object? timeZone = null,Object? birthday = freezed,Object? links = null,Object? lastSeenAt = freezed,Object? activeBadge = freezed,Object? experience = null,Object? level = null,Object? levelingProgress = null,Object? picture = freezed,Object? background = freezed,Object? verification = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) { | ||||||
|   return _then(_SnAccountProfile( |   return _then(_SnAccountProfile( | ||||||
| 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,firstName: null == firstName ? _self.firstName : firstName // ignore: cast_nullable_to_non_nullable | as String,firstName: null == firstName ? _self.firstName : firstName // ignore: cast_nullable_to_non_nullable | ||||||
| @@ -693,7 +961,8 @@ as String,pronouns: null == pronouns ? _self.pronouns : pronouns // ignore: cast | |||||||
| as String,location: null == location ? _self.location : location // ignore: cast_nullable_to_non_nullable | as String,location: null == location ? _self.location : location // ignore: cast_nullable_to_non_nullable | ||||||
| as String,timeZone: null == timeZone ? _self.timeZone : timeZone // ignore: cast_nullable_to_non_nullable | as String,timeZone: null == timeZone ? _self.timeZone : timeZone // ignore: cast_nullable_to_non_nullable | ||||||
| as String,birthday: freezed == birthday ? _self.birthday : birthday // ignore: cast_nullable_to_non_nullable | as String,birthday: freezed == birthday ? _self.birthday : birthday // ignore: cast_nullable_to_non_nullable | ||||||
| as DateTime?,lastSeenAt: freezed == lastSeenAt ? _self.lastSeenAt : lastSeenAt // ignore: cast_nullable_to_non_nullable | as DateTime?,links: null == links ? _self._links : links // ignore: cast_nullable_to_non_nullable | ||||||
|  | as List<ProfileLink>,lastSeenAt: freezed == lastSeenAt ? _self.lastSeenAt : lastSeenAt // ignore: cast_nullable_to_non_nullable | ||||||
| as DateTime?,activeBadge: freezed == activeBadge ? _self.activeBadge : activeBadge // ignore: cast_nullable_to_non_nullable | as DateTime?,activeBadge: freezed == activeBadge ? _self.activeBadge : activeBadge // ignore: cast_nullable_to_non_nullable | ||||||
| as SnAccountBadge?,experience: null == experience ? _self.experience : experience // ignore: cast_nullable_to_non_nullable | as SnAccountBadge?,experience: null == experience ? _self.experience : experience // ignore: cast_nullable_to_non_nullable | ||||||
| as int,level: null == level ? _self.level : level // ignore: cast_nullable_to_non_nullable | as int,level: null == level ? _self.level : level // ignore: cast_nullable_to_non_nullable | ||||||
|   | |||||||
| @@ -47,6 +47,12 @@ Map<String, dynamic> _$SnAccountToJson(_SnAccount instance) => | |||||||
|       'deleted_at': instance.deletedAt?.toIso8601String(), |       'deleted_at': instance.deletedAt?.toIso8601String(), | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|  | _ProfileLink _$ProfileLinkFromJson(Map<String, dynamic> json) => | ||||||
|  |     _ProfileLink(name: json['name'] as String, url: json['url'] as String); | ||||||
|  |  | ||||||
|  | Map<String, dynamic> _$ProfileLinkToJson(_ProfileLink instance) => | ||||||
|  |     <String, dynamic>{'name': instance.name, 'url': instance.url}; | ||||||
|  |  | ||||||
| _SnAccountProfile _$SnAccountProfileFromJson(Map<String, dynamic> json) => | _SnAccountProfile _$SnAccountProfileFromJson(Map<String, dynamic> json) => | ||||||
|     _SnAccountProfile( |     _SnAccountProfile( | ||||||
|       id: json['id'] as String, |       id: json['id'] as String, | ||||||
| @@ -62,6 +68,10 @@ _SnAccountProfile _$SnAccountProfileFromJson(Map<String, dynamic> json) => | |||||||
|           json['birthday'] == null |           json['birthday'] == null | ||||||
|               ? null |               ? null | ||||||
|               : DateTime.parse(json['birthday'] as String), |               : DateTime.parse(json['birthday'] as String), | ||||||
|  |       links: | ||||||
|  |           json['links'] == null | ||||||
|  |               ? const [] | ||||||
|  |               : const ProfileLinkConverter().fromJson(json['links']), | ||||||
|       lastSeenAt: |       lastSeenAt: | ||||||
|           json['last_seen_at'] == null |           json['last_seen_at'] == null | ||||||
|               ? null |               ? null | ||||||
| @@ -111,6 +121,7 @@ Map<String, dynamic> _$SnAccountProfileToJson(_SnAccountProfile instance) => | |||||||
|       'location': instance.location, |       'location': instance.location, | ||||||
|       'time_zone': instance.timeZone, |       'time_zone': instance.timeZone, | ||||||
|       'birthday': instance.birthday?.toIso8601String(), |       'birthday': instance.birthday?.toIso8601String(), | ||||||
|  |       'links': const ProfileLinkConverter().toJson(instance.links), | ||||||
|       'last_seen_at': instance.lastSeenAt?.toIso8601String(), |       'last_seen_at': instance.lastSeenAt?.toIso8601String(), | ||||||
|       'active_badge': instance.activeBadge?.toJson(), |       'active_badge': instance.activeBadge?.toJson(), | ||||||
|       'experience': instance.experience, |       'experience': instance.experience, | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| import 'dart:developer'; | import 'dart:developer'; | ||||||
|  |  | ||||||
|  | import 'package:firebase_analytics/firebase_analytics.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/user.dart'; | ||||||
| @@ -17,6 +18,7 @@ class UserInfoNotifier extends StateNotifier<AsyncValue<SnAccount?>> { | |||||||
|       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) { | ||||||
|       log( |       log( | ||||||
|         "[UserInfo] Failed to fetch user info...", |         "[UserInfo] Failed to fetch user info...", | ||||||
| @@ -33,6 +35,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,7 +1,6 @@ | |||||||
| import 'dart:async'; | import 'dart:async'; | ||||||
| import 'dart:convert'; | import 'dart:convert'; | ||||||
| import 'dart:developer'; | import 'dart:developer'; | ||||||
|  |  | ||||||
| import 'package:flutter/foundation.dart'; | import 'package:flutter/foundation.dart'; | ||||||
| import 'package:flutter_riverpod/flutter_riverpod.dart'; | import 'package:flutter_riverpod/flutter_riverpod.dart'; | ||||||
| import 'package:freezed_annotation/freezed_annotation.dart'; | import 'package:freezed_annotation/freezed_annotation.dart'; | ||||||
| @@ -18,6 +17,8 @@ sealed class WebSocketState with _$WebSocketState { | |||||||
|   const factory WebSocketState.connected() = _Connected; |   const factory WebSocketState.connected() = _Connected; | ||||||
|   const factory WebSocketState.connecting() = _Connecting; |   const factory WebSocketState.connecting() = _Connecting; | ||||||
|   const factory WebSocketState.disconnected() = _Disconnected; |   const factory WebSocketState.disconnected() = _Disconnected; | ||||||
|  |   const factory WebSocketState.serverDown() = _ServerDown; | ||||||
|  |   const factory WebSocketState.duplicateDevice() = _DuplicateDevice; | ||||||
|   const factory WebSocketState.error(String message) = _Error; |   const factory WebSocketState.error(String message) = _Error; | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -49,7 +50,7 @@ class WebSocketService { | |||||||
|   Timer? _heartbeatTimer; |   Timer? _heartbeatTimer; | ||||||
|  |  | ||||||
|   DateTime? _heartbeatAt; |   DateTime? _heartbeatAt; | ||||||
|   Duration? _heartbeatDelay; |   Duration? heartbeatDelay; | ||||||
|  |  | ||||||
|   Stream<WebSocketPacket> get dataStream => _streamController.stream; |   Stream<WebSocketPacket> get dataStream => _streamController.stream; | ||||||
|   Stream<WebSocketState> get statusStream => _statusStreamController.stream; |   Stream<WebSocketState> get statusStream => _statusStreamController.stream; | ||||||
| @@ -81,15 +82,20 @@ class WebSocketService { | |||||||
|           final dataStr = |           final dataStr = | ||||||
|               data is Uint8List ? utf8.decode(data) : data.toString(); |               data is Uint8List ? utf8.decode(data) : data.toString(); | ||||||
|           final packet = WebSocketPacket.fromJson(jsonDecode(dataStr)); |           final packet = WebSocketPacket.fromJson(jsonDecode(dataStr)); | ||||||
|  |           if (packet.type == 'error.dupe') { | ||||||
|  |             _statusStreamController.sink.add(WebSocketState.duplicateDevice()); | ||||||
|  |             _channel!.sink.close(); | ||||||
|  |             return; | ||||||
|  |           } | ||||||
|           _streamController.sink.add(packet); |           _streamController.sink.add(packet); | ||||||
|           log( |           log( | ||||||
|             "[WebSocket] Received packet: ${packet.type} ${packet.errorMessage}", |             "[WebSocket] Received packet: ${packet.type} ${packet.errorMessage}", | ||||||
|           ); |           ); | ||||||
|           if (packet.type == 'pong' && _heartbeatAt != null) { |           if (packet.type == 'pong' && _heartbeatAt != null) { | ||||||
|             var now = DateTime.now(); |             var now = DateTime.now(); | ||||||
|             _heartbeatDelay = now.difference(_heartbeatAt!); |             heartbeatDelay = now.difference(_heartbeatAt!); | ||||||
|             log( |             log( | ||||||
|               "[WebSocket] Server respond last heartbeat for ${_heartbeatDelay!.inMilliseconds} ms", |               "[WebSocket] Server respond last heartbeat for ${heartbeatDelay!.inMilliseconds} ms", | ||||||
|             ); |             ); | ||||||
|           } |           } | ||||||
|         }, |         }, | ||||||
|   | |||||||
| @@ -61,13 +61,15 @@ extension WebSocketStatePatterns on WebSocketState { | |||||||
| /// } | /// } | ||||||
| /// ``` | /// ``` | ||||||
|  |  | ||||||
| @optionalTypeArgs TResult maybeMap<TResult extends Object?>({TResult Function( _Connected value)?  connected,TResult Function( _Connecting value)?  connecting,TResult Function( _Disconnected value)?  disconnected,TResult Function( _Error value)?  error,required TResult orElse(),}){ | @optionalTypeArgs TResult maybeMap<TResult extends Object?>({TResult Function( _Connected value)?  connected,TResult Function( _Connecting value)?  connecting,TResult Function( _Disconnected value)?  disconnected,TResult Function( _ServerDown value)?  serverDown,TResult Function( _DuplicateDevice value)?  duplicateDevice,TResult Function( _Error value)?  error,required TResult orElse(),}){ | ||||||
| final _that = this; | final _that = this; | ||||||
| switch (_that) { | switch (_that) { | ||||||
| case _Connected() when connected != null: | case _Connected() when connected != null: | ||||||
| return connected(_that);case _Connecting() when connecting != null: | return connected(_that);case _Connecting() when connecting != null: | ||||||
| return connecting(_that);case _Disconnected() when disconnected != null: | return connecting(_that);case _Disconnected() when disconnected != null: | ||||||
| return disconnected(_that);case _Error() when error != null: | return disconnected(_that);case _ServerDown() when serverDown != null: | ||||||
|  | return serverDown(_that);case _DuplicateDevice() when duplicateDevice != null: | ||||||
|  | return duplicateDevice(_that);case _Error() when error != null: | ||||||
| return error(_that);case _: | return error(_that);case _: | ||||||
|   return orElse(); |   return orElse(); | ||||||
|  |  | ||||||
| @@ -86,13 +88,15 @@ return error(_that);case _: | |||||||
| /// } | /// } | ||||||
| /// ``` | /// ``` | ||||||
|  |  | ||||||
| @optionalTypeArgs TResult map<TResult extends Object?>({required TResult Function( _Connected value)  connected,required TResult Function( _Connecting value)  connecting,required TResult Function( _Disconnected value)  disconnected,required TResult Function( _Error value)  error,}){ | @optionalTypeArgs TResult map<TResult extends Object?>({required TResult Function( _Connected value)  connected,required TResult Function( _Connecting value)  connecting,required TResult Function( _Disconnected value)  disconnected,required TResult Function( _ServerDown value)  serverDown,required TResult Function( _DuplicateDevice value)  duplicateDevice,required TResult Function( _Error value)  error,}){ | ||||||
| final _that = this; | final _that = this; | ||||||
| switch (_that) { | switch (_that) { | ||||||
| case _Connected(): | case _Connected(): | ||||||
| return connected(_that);case _Connecting(): | return connected(_that);case _Connecting(): | ||||||
| return connecting(_that);case _Disconnected(): | return connecting(_that);case _Disconnected(): | ||||||
| return disconnected(_that);case _Error(): | return disconnected(_that);case _ServerDown(): | ||||||
|  | return serverDown(_that);case _DuplicateDevice(): | ||||||
|  | return duplicateDevice(_that);case _Error(): | ||||||
| return error(_that);} | return error(_that);} | ||||||
| } | } | ||||||
| /// A variant of `map` that fallback to returning `null`. | /// A variant of `map` that fallback to returning `null`. | ||||||
| @@ -107,13 +111,15 @@ return error(_that);} | |||||||
| /// } | /// } | ||||||
| /// ``` | /// ``` | ||||||
|  |  | ||||||
| @optionalTypeArgs TResult? mapOrNull<TResult extends Object?>({TResult? Function( _Connected value)?  connected,TResult? Function( _Connecting value)?  connecting,TResult? Function( _Disconnected value)?  disconnected,TResult? Function( _Error value)?  error,}){ | @optionalTypeArgs TResult? mapOrNull<TResult extends Object?>({TResult? Function( _Connected value)?  connected,TResult? Function( _Connecting value)?  connecting,TResult? Function( _Disconnected value)?  disconnected,TResult? Function( _ServerDown value)?  serverDown,TResult? Function( _DuplicateDevice value)?  duplicateDevice,TResult? Function( _Error value)?  error,}){ | ||||||
| final _that = this; | final _that = this; | ||||||
| switch (_that) { | switch (_that) { | ||||||
| case _Connected() when connected != null: | case _Connected() when connected != null: | ||||||
| return connected(_that);case _Connecting() when connecting != null: | return connected(_that);case _Connecting() when connecting != null: | ||||||
| return connecting(_that);case _Disconnected() when disconnected != null: | return connecting(_that);case _Disconnected() when disconnected != null: | ||||||
| return disconnected(_that);case _Error() when error != null: | return disconnected(_that);case _ServerDown() when serverDown != null: | ||||||
|  | return serverDown(_that);case _DuplicateDevice() when duplicateDevice != null: | ||||||
|  | return duplicateDevice(_that);case _Error() when error != null: | ||||||
| return error(_that);case _: | return error(_that);case _: | ||||||
|   return null; |   return null; | ||||||
|  |  | ||||||
| @@ -131,12 +137,14 @@ return error(_that);case _: | |||||||
| /// } | /// } | ||||||
| /// ``` | /// ``` | ||||||
|  |  | ||||||
| @optionalTypeArgs TResult maybeWhen<TResult extends Object?>({TResult Function()?  connected,TResult Function()?  connecting,TResult Function()?  disconnected,TResult Function( String message)?  error,required TResult orElse(),}) {final _that = this; | @optionalTypeArgs TResult maybeWhen<TResult extends Object?>({TResult Function()?  connected,TResult Function()?  connecting,TResult Function()?  disconnected,TResult Function()?  serverDown,TResult Function()?  duplicateDevice,TResult Function( String message)?  error,required TResult orElse(),}) {final _that = this; | ||||||
| switch (_that) { | switch (_that) { | ||||||
| case _Connected() when connected != null: | case _Connected() when connected != null: | ||||||
| return connected();case _Connecting() when connecting != null: | return connected();case _Connecting() when connecting != null: | ||||||
| return connecting();case _Disconnected() when disconnected != null: | return connecting();case _Disconnected() when disconnected != null: | ||||||
| return disconnected();case _Error() when error != null: | return disconnected();case _ServerDown() when serverDown != null: | ||||||
|  | return serverDown();case _DuplicateDevice() when duplicateDevice != null: | ||||||
|  | return duplicateDevice();case _Error() when error != null: | ||||||
| return error(_that.message);case _: | return error(_that.message);case _: | ||||||
|   return orElse(); |   return orElse(); | ||||||
|  |  | ||||||
| @@ -155,12 +163,14 @@ return error(_that.message);case _: | |||||||
| /// } | /// } | ||||||
| /// ``` | /// ``` | ||||||
|  |  | ||||||
| @optionalTypeArgs TResult when<TResult extends Object?>({required TResult Function()  connected,required TResult Function()  connecting,required TResult Function()  disconnected,required TResult Function( String message)  error,}) {final _that = this; | @optionalTypeArgs TResult when<TResult extends Object?>({required TResult Function()  connected,required TResult Function()  connecting,required TResult Function()  disconnected,required TResult Function()  serverDown,required TResult Function()  duplicateDevice,required TResult Function( String message)  error,}) {final _that = this; | ||||||
| switch (_that) { | switch (_that) { | ||||||
| case _Connected(): | case _Connected(): | ||||||
| return connected();case _Connecting(): | return connected();case _Connecting(): | ||||||
| return connecting();case _Disconnected(): | return connecting();case _Disconnected(): | ||||||
| return disconnected();case _Error(): | return disconnected();case _ServerDown(): | ||||||
|  | return serverDown();case _DuplicateDevice(): | ||||||
|  | return duplicateDevice();case _Error(): | ||||||
| return error(_that.message);} | return error(_that.message);} | ||||||
| } | } | ||||||
| /// A variant of `when` that fallback to returning `null` | /// A variant of `when` that fallback to returning `null` | ||||||
| @@ -175,12 +185,14 @@ return error(_that.message);} | |||||||
| /// } | /// } | ||||||
| /// ``` | /// ``` | ||||||
|  |  | ||||||
| @optionalTypeArgs TResult? whenOrNull<TResult extends Object?>({TResult? Function()?  connected,TResult? Function()?  connecting,TResult? Function()?  disconnected,TResult? Function( String message)?  error,}) {final _that = this; | @optionalTypeArgs TResult? whenOrNull<TResult extends Object?>({TResult? Function()?  connected,TResult? Function()?  connecting,TResult? Function()?  disconnected,TResult? Function()?  serverDown,TResult? Function()?  duplicateDevice,TResult? Function( String message)?  error,}) {final _that = this; | ||||||
| switch (_that) { | switch (_that) { | ||||||
| case _Connected() when connected != null: | case _Connected() when connected != null: | ||||||
| return connected();case _Connecting() when connecting != null: | return connected();case _Connecting() when connecting != null: | ||||||
| return connecting();case _Disconnected() when disconnected != null: | return connecting();case _Disconnected() when disconnected != null: | ||||||
| return disconnected();case _Error() when error != null: | return disconnected();case _ServerDown() when serverDown != null: | ||||||
|  | return serverDown();case _DuplicateDevice() when duplicateDevice != null: | ||||||
|  | return duplicateDevice();case _Error() when error != null: | ||||||
| return error(_that.message);case _: | return error(_that.message);case _: | ||||||
|   return null; |   return null; | ||||||
|  |  | ||||||
| @@ -303,6 +315,82 @@ String toString({ DiagnosticLevel minLevel = DiagnosticLevel.info }) { | |||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class _ServerDown with DiagnosticableTreeMixin implements WebSocketState { | ||||||
|  |   const _ServerDown(); | ||||||
|  |    | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | void debugFillProperties(DiagnosticPropertiesBuilder properties) { | ||||||
|  |   properties | ||||||
|  |     ..add(DiagnosticsProperty('type', 'WebSocketState.serverDown')) | ||||||
|  |     ; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | bool operator ==(Object other) { | ||||||
|  |   return identical(this, other) || (other.runtimeType == runtimeType&&other is _ServerDown); | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | int get hashCode => runtimeType.hashCode; | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | String toString({ DiagnosticLevel minLevel = DiagnosticLevel.info }) { | ||||||
|  |   return 'WebSocketState.serverDown()'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class _DuplicateDevice with DiagnosticableTreeMixin implements WebSocketState { | ||||||
|  |   const _DuplicateDevice(); | ||||||
|  |    | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | void debugFillProperties(DiagnosticPropertiesBuilder properties) { | ||||||
|  |   properties | ||||||
|  |     ..add(DiagnosticsProperty('type', 'WebSocketState.duplicateDevice')) | ||||||
|  |     ; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | bool operator ==(Object other) { | ||||||
|  |   return identical(this, other) || (other.runtimeType == runtimeType&&other is _DuplicateDevice); | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | int get hashCode => runtimeType.hashCode; | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | String toString({ DiagnosticLevel minLevel = DiagnosticLevel.info }) { | ||||||
|  |   return 'WebSocketState.duplicateDevice()'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| /// @nodoc | /// @nodoc | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,3 +1,5 @@ | |||||||
|  | import 'package:firebase_analytics/firebase_analytics.dart'; | ||||||
|  |  | ||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
| import 'package:go_router/go_router.dart'; | import 'package:go_router/go_router.dart'; | ||||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
| @@ -7,6 +9,7 @@ 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_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'; | ||||||
| import 'package:island/screens/tabs.dart'; | import 'package:island/screens/tabs.dart'; | ||||||
| @@ -28,6 +31,8 @@ import 'package:island/screens/creators/hub.dart'; | |||||||
| import 'package:island/screens/creators/posts/post_manage_list.dart'; | import 'package:island/screens/creators/posts/post_manage_list.dart'; | ||||||
| import 'package:island/screens/creators/stickers/stickers.dart'; | import 'package:island/screens/creators/stickers/stickers.dart'; | ||||||
| import 'package:island/screens/creators/stickers/pack_detail.dart'; | import 'package:island/screens/creators/stickers/pack_detail.dart'; | ||||||
|  | import 'package:island/screens/stickers/marketplace.dart'; | ||||||
|  | import 'package:island/screens/stickers/pack_detail.dart'; | ||||||
| import 'package:island/screens/creators/poll/poll_list.dart'; | import 'package:island/screens/creators/poll/poll_list.dart'; | ||||||
| import 'package:island/screens/creators/publishers.dart'; | import 'package:island/screens/creators/publishers.dart'; | ||||||
| import 'package:island/screens/creators/webfeed/webfeed_list.dart'; | import 'package:island/screens/creators/webfeed/webfeed_list.dart'; | ||||||
| @@ -56,6 +61,9 @@ final routerProvider = Provider<GoRouter>((ref) { | |||||||
|   return GoRouter( |   return GoRouter( | ||||||
|     navigatorKey: rootNavigatorKey, |     navigatorKey: rootNavigatorKey, | ||||||
|     initialLocation: '/', |     initialLocation: '/', | ||||||
|  |     observers: [ | ||||||
|  |       FirebaseAnalyticsObserver(analytics: FirebaseAnalytics.instance), | ||||||
|  |     ], | ||||||
|     routes: [ |     routes: [ | ||||||
|       ShellRoute( |       ShellRoute( | ||||||
|         navigatorKey: _shellNavigatorKey, |         navigatorKey: _shellNavigatorKey, | ||||||
| @@ -320,15 +328,6 @@ final routerProvider = Provider<GoRouter>((ref) { | |||||||
|             builder: (context, state) => const AboutScreen(), |             builder: (context, state) => const AboutScreen(), | ||||||
|           ), |           ), | ||||||
|  |  | ||||||
|           GoRoute( |  | ||||||
|             name: 'reportDetail', |  | ||||||
|             path: '/safety/reports/me/:id', |  | ||||||
|             builder: (context, state) { |  | ||||||
|               final id = state.pathParameters['id']!; |  | ||||||
|               return AbuseReportDetailScreen(reportId: id); |  | ||||||
|             }, |  | ||||||
|           ), |  | ||||||
|  |  | ||||||
|           // Main tabs with TabsScreen shell |           // Main tabs with TabsScreen shell | ||||||
|           ShellRoute( |           ShellRoute( | ||||||
|             navigatorKey: _tabsShellKey, |             navigatorKey: _tabsShellKey, | ||||||
| @@ -355,6 +354,25 @@ final routerProvider = Provider<GoRouter>((ref) { | |||||||
|                   return PostDetailScreen(id: id); |                   return PostDetailScreen(id: id); | ||||||
|                 }, |                 }, | ||||||
|               ), |               ), | ||||||
|  |               GoRoute( | ||||||
|  |                 name: 'postCategoryDetail', | ||||||
|  |                 path: '/posts/categories/:slug', | ||||||
|  |                 builder: (context, state) { | ||||||
|  |                   final slug = state.pathParameters['slug']!; | ||||||
|  |                   return PostCategoryDetailScreen(slug: slug, isCategory: true); | ||||||
|  |                 }, | ||||||
|  |               ), | ||||||
|  |               GoRoute( | ||||||
|  |                 name: 'postTagDetail', | ||||||
|  |                 path: '/posts/tags/:slug', | ||||||
|  |                 builder: (context, state) { | ||||||
|  |                   final slug = state.pathParameters['slug']!; | ||||||
|  |                   return PostCategoryDetailScreen( | ||||||
|  |                     slug: slug, | ||||||
|  |                     isCategory: false, | ||||||
|  |                   ); | ||||||
|  |                 }, | ||||||
|  |               ), | ||||||
|               GoRoute( |               GoRoute( | ||||||
|                 name: 'publisherProfile', |                 name: 'publisherProfile', | ||||||
|                 path: '/publishers/:name', |                 path: '/publishers/:name', | ||||||
| @@ -451,6 +469,23 @@ final routerProvider = Provider<GoRouter>((ref) { | |||||||
|                     path: '/account', |                     path: '/account', | ||||||
|                     builder: (context, state) => const AccountScreen(), |                     builder: (context, state) => const AccountScreen(), | ||||||
|                   ), |                   ), | ||||||
|  |                   // Sticker marketplace (user-facing, no publisher) | ||||||
|  |                   GoRoute( | ||||||
|  |                     name: 'stickerMarketplace', | ||||||
|  |                     path: '/stickers', | ||||||
|  |                     builder: | ||||||
|  |                         (context, state) => const MarketplaceStickersScreen(), | ||||||
|  |                     routes: [ | ||||||
|  |                       GoRoute( | ||||||
|  |                         name: 'stickerPackDetail', | ||||||
|  |                         path: ':packId', | ||||||
|  |                         builder: (context, state) { | ||||||
|  |                           final packId = state.pathParameters['packId']!; | ||||||
|  |                           return MarketplaceStickerPackDetailScreen(id: packId); | ||||||
|  |                         }, | ||||||
|  |                       ), | ||||||
|  |                     ], | ||||||
|  |                   ), | ||||||
|                   GoRoute( |                   GoRoute( | ||||||
|                     name: 'notifications', |                     name: 'notifications', | ||||||
|                     path: '/account/notifications', |                     path: '/account/notifications', | ||||||
| @@ -486,6 +521,14 @@ final routerProvider = Provider<GoRouter>((ref) { | |||||||
|                     path: '/safety/reports/me', |                     path: '/safety/reports/me', | ||||||
|                     builder: (context, state) => const AbuseReportListScreen(), |                     builder: (context, state) => const AbuseReportListScreen(), | ||||||
|                   ), |                   ), | ||||||
|  |                   GoRoute( | ||||||
|  |                     name: 'reportDetail', | ||||||
|  |                     path: '/safety/reports/me/:id', | ||||||
|  |                     builder: (context, state) { | ||||||
|  |                       final id = state.pathParameters['id']!; | ||||||
|  |                       return AbuseReportDetailScreen(reportId: id); | ||||||
|  |                     }, | ||||||
|  |                   ), | ||||||
|                 ], |                 ], | ||||||
|               ), |               ), | ||||||
|  |  | ||||||
|   | |||||||
| @@ -7,10 +7,12 @@ import 'package:flutter/services.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/services/udid.native.dart'; | import 'package:island/services/udid.native.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'; | ||||||
| import 'package:package_info_plus/package_info_plus.dart'; | import 'package:package_info_plus/package_info_plus.dart'; | ||||||
| import 'package:styled_widget/styled_widget.dart'; | import 'package:styled_widget/styled_widget.dart'; | ||||||
|  | import 'package:island/services/update_service.dart'; | ||||||
| import 'package:url_launcher/url_launcher.dart'; | import 'package:url_launcher/url_launcher.dart'; | ||||||
| import 'package:easy_localization/easy_localization.dart'; | import 'package:easy_localization/easy_localization.dart'; | ||||||
| import 'package:url_launcher/url_launcher_string.dart'; | import 'package:url_launcher/url_launcher_string.dart'; | ||||||
| @@ -100,196 +102,226 @@ class _AboutScreenState extends ConsumerState<AboutScreen> { | |||||||
|               ? const Center(child: CircularProgressIndicator()) |               ? const Center(child: CircularProgressIndicator()) | ||||||
|               : _errorMessage != null |               : _errorMessage != null | ||||||
|               ? Center(child: Text(_errorMessage!)) |               ? Center(child: Text(_errorMessage!)) | ||||||
|               : SingleChildScrollView( |               : Center( | ||||||
|                 child: Column( |                 child: ConstrainedBox( | ||||||
|                   crossAxisAlignment: CrossAxisAlignment.center, |                   constraints: const BoxConstraints(maxWidth: 540), | ||||||
|                   children: [ |                   child: SingleChildScrollView( | ||||||
|                     const SizedBox(height: 24), |                     child: Column( | ||||||
|                     // App Icon and Name |                       crossAxisAlignment: CrossAxisAlignment.center, | ||||||
|                     CircleAvatar( |  | ||||||
|                       radius: 50, |  | ||||||
|                       backgroundColor: theme.colorScheme.primary.withOpacity( |  | ||||||
|                         0.1, |  | ||||||
|                       ), |  | ||||||
|                       child: Image.asset( |  | ||||||
|                         'assets/icons/icon.png', |  | ||||||
|                         width: 56, |  | ||||||
|                         height: 56, |  | ||||||
|                       ), |  | ||||||
|                     ), |  | ||||||
|                     const SizedBox(height: 16), |  | ||||||
|                     Text( |  | ||||||
|                       _packageInfo.appName, |  | ||||||
|                       style: theme.textTheme.headlineSmall?.copyWith( |  | ||||||
|                         fontWeight: FontWeight.bold, |  | ||||||
|                       ), |  | ||||||
|                     ), |  | ||||||
|                     Text( |  | ||||||
|                       'aboutScreenVersionInfo'.tr( |  | ||||||
|                         args: [_packageInfo.version, _packageInfo.buildNumber], |  | ||||||
|                       ), |  | ||||||
|                       style: theme.textTheme.bodyMedium?.copyWith( |  | ||||||
|                         color: theme.textTheme.bodySmall?.color, |  | ||||||
|                       ), |  | ||||||
|                     ), |  | ||||||
|                     const SizedBox(height: 32), |  | ||||||
|  |  | ||||||
|                     // App Info Card |  | ||||||
|                     _buildSection( |  | ||||||
|                       context, |  | ||||||
|                       title: 'aboutScreenAppInfoSectionTitle'.tr(), |  | ||||||
|                       children: [ |                       children: [ | ||||||
|                         _buildInfoItem( |                         const SizedBox(height: 24), | ||||||
|                           context, |                         // App Icon and Name | ||||||
|                           icon: Symbols.info, |                         CircleAvatar( | ||||||
|                           label: 'aboutScreenPackageNameLabel'.tr(), |                           radius: 50, | ||||||
|                           value: _packageInfo.packageName, |                           backgroundColor: theme.colorScheme.primary | ||||||
|                         ), |                               .withOpacity(0.1), | ||||||
|                         _buildInfoItem( |                           child: Image.asset( | ||||||
|                           context, |                             'assets/icons/icon.png', | ||||||
|                           icon: Symbols.update, |                             width: 56, | ||||||
|                           label: 'aboutScreenVersionLabel'.tr(), |                             height: 56, | ||||||
|                           value: _packageInfo.version, |  | ||||||
|                         ), |  | ||||||
|                         _buildInfoItem( |  | ||||||
|                           context, |  | ||||||
|                           icon: Symbols.build, |  | ||||||
|                           label: 'aboutScreenBuildNumberLabel'.tr(), |  | ||||||
|                           value: _packageInfo.buildNumber, |  | ||||||
|                         ), |  | ||||||
|                       ], |  | ||||||
|                     ), |  | ||||||
|  |  | ||||||
|                     if (_deviceInfo != null) const SizedBox(height: 16), |  | ||||||
|  |  | ||||||
|                     if (_deviceInfo != null) |  | ||||||
|                       _buildSection( |  | ||||||
|                         context, |  | ||||||
|                         title: 'Device Information', |  | ||||||
|                         children: [ |  | ||||||
|                           _buildInfoItem( |  | ||||||
|                             context, |  | ||||||
|                             icon: Symbols.label, |  | ||||||
|                             label: 'aboutDeviceName'.tr(), |  | ||||||
|                             value: _deviceInfo?.data['name'], |  | ||||||
|                           ), |                           ), | ||||||
|                           _buildInfoItem( |                         ), | ||||||
|                             context, |                         const SizedBox(height: 16), | ||||||
|                             icon: Symbols.fingerprint, |                         Text( | ||||||
|                             label: 'aboutDeviceIdentifier'.tr(), |                           _packageInfo.appName, | ||||||
|                             value: _deviceUdid ?? 'N/A', |                           style: theme.textTheme.headlineSmall?.copyWith( | ||||||
|                             copyable: true, |                             fontWeight: FontWeight.bold, | ||||||
|                           ), |                           ), | ||||||
|                         ], |  | ||||||
|                       ), |  | ||||||
|  |  | ||||||
|                     const SizedBox(height: 16), |  | ||||||
|  |  | ||||||
|                     // Links Card |  | ||||||
|                     _buildSection( |  | ||||||
|                       context, |  | ||||||
|                       title: 'aboutScreenLinksSectionTitle'.tr(), |  | ||||||
|                       children: [ |  | ||||||
|                         _buildListTile( |  | ||||||
|                           context, |  | ||||||
|                           icon: Symbols.privacy_tip, |  | ||||||
|                           title: 'aboutScreenPrivacyPolicyTitle'.tr(), |  | ||||||
|                           onTap: |  | ||||||
|                               () => _launchURL( |  | ||||||
|                                 'https://solsynth.dev/terms/privacy-policy', |  | ||||||
|                               ), |  | ||||||
|                         ), |                         ), | ||||||
|                         _buildListTile( |                         Text( | ||||||
|                           context, |                           'aboutScreenVersionInfo'.tr( | ||||||
|                           icon: Symbols.description, |                             args: [ | ||||||
|                           title: 'aboutScreenTermsOfServiceTitle'.tr(), |                               _packageInfo.version, | ||||||
|                           onTap: |                               _packageInfo.buildNumber, | ||||||
|                               () => _launchURL( |                             ], | ||||||
|                                 'https://solsynth.dev/terms/basic-law', |  | ||||||
|                               ), |  | ||||||
|                         ), |  | ||||||
|                         _buildListTile( |  | ||||||
|                           context, |  | ||||||
|                           icon: Symbols.code, |  | ||||||
|                           title: 'aboutScreenOpenSourceLicensesTitle'.tr(), |  | ||||||
|                           onTap: () { |  | ||||||
|                             showLicensePage( |  | ||||||
|                               context: context, |  | ||||||
|                               applicationName: _packageInfo.appName, |  | ||||||
|                               applicationVersion: |  | ||||||
|                                   'Version ${_packageInfo.version}', |  | ||||||
|                             ); |  | ||||||
|                           }, |  | ||||||
|                         ), |  | ||||||
|                       ], |  | ||||||
|                     ), |  | ||||||
|  |  | ||||||
|                     const SizedBox(height: 16), |  | ||||||
|  |  | ||||||
|                     // Developer Info |  | ||||||
|                     _buildSection( |  | ||||||
|                       context, |  | ||||||
|                       title: 'aboutScreenDeveloperSectionTitle'.tr(), |  | ||||||
|                       children: [ |  | ||||||
|                         _buildListTile( |  | ||||||
|                           context, |  | ||||||
|                           icon: Symbols.email, |  | ||||||
|                           title: 'aboutScreenContactUsTitle'.tr(), |  | ||||||
|                           subtitle: 'lily@solsynth.dev', |  | ||||||
|                           onTap: () => _launchURL('mailto:lily@solsynth.dev'), |  | ||||||
|                         ), |  | ||||||
|                         _buildListTile( |  | ||||||
|                           context, |  | ||||||
|                           icon: Symbols.copyright, |  | ||||||
|                           title: 'aboutScreenLicenseTitle'.tr(), |  | ||||||
|                           subtitle: 'aboutScreenLicenseContent'.tr( |  | ||||||
|                             args: [DateTime.now().year.toString()], |  | ||||||
|                           ), |                           ), | ||||||
|                           onTap: |                           style: theme.textTheme.bodyMedium?.copyWith( | ||||||
|                               () => _launchURL( |                             color: theme.textTheme.bodySmall?.color, | ||||||
|                                 'https://github.com/Solsynth/Solian/blob/v3/LICENSE.txt', |  | ||||||
|                               ), |  | ||||||
|                         ), |  | ||||||
|                         if (kIsWeb || !(Platform.isMacOS || Platform.isIOS)) |  | ||||||
|                           _buildListTile( |  | ||||||
|                             context, |  | ||||||
|                             icon: Symbols.favorite, |  | ||||||
|                             title: 'donate'.tr(), |  | ||||||
|                             subtitle: 'donateDescription'.tr(), |  | ||||||
|                             onTap: () { |  | ||||||
|                               launchUrlString( |  | ||||||
|                                 'https://afdian.com/@littlesheep', |  | ||||||
|                               ); |  | ||||||
|                             }, |  | ||||||
|                           ), |                           ), | ||||||
|                       ], |                         ), | ||||||
|                     ), |                         const SizedBox(height: 32), | ||||||
|  |  | ||||||
|                     const SizedBox(height: 32), |                         // App Info Card | ||||||
|  |                         _buildSection( | ||||||
|                     // Copyright |                           context, | ||||||
|                     Padding( |                           title: 'aboutScreenAppInfoSectionTitle'.tr(), | ||||||
|                       padding: const EdgeInsets.all(16.0), |                           children: [ | ||||||
|                       child: Column( |                             _buildInfoItem( | ||||||
|                         children: [ |                               context, | ||||||
|                           Text( |                               icon: Symbols.info, | ||||||
|                             'aboutScreenCopyright'.tr( |                               label: 'aboutScreenPackageNameLabel'.tr(), | ||||||
|                               args: [DateTime.now().year.toString()], |                               value: _packageInfo.packageName, | ||||||
|                             ), |                             ), | ||||||
|                             style: theme.textTheme.bodySmall, |                             _buildInfoItem( | ||||||
|                             textAlign: TextAlign.center, |                               context, | ||||||
|                           ), |                               icon: Symbols.update, | ||||||
|                           const Gap(1), |                               label: 'aboutScreenVersionLabel'.tr(), | ||||||
|                           Text( |                               value: _packageInfo.version, | ||||||
|                             'aboutScreenMadeWith'.tr(), |                             ), | ||||||
|                             textAlign: TextAlign.center, |                             _buildInfoItem( | ||||||
|                           ).fontSize(10).opacity(0.8), |                               context, | ||||||
|                         ], |                               icon: Symbols.build, | ||||||
|                       ), |                               label: 'aboutScreenBuildNumberLabel'.tr(), | ||||||
|                     ), |                               value: _packageInfo.buildNumber, | ||||||
|  |                             ), | ||||||
|  |                           ], | ||||||
|  |                         ), | ||||||
|  |  | ||||||
|                     Gap(MediaQuery.of(context).padding.bottom + 16), |                         if (_deviceInfo != null) const SizedBox(height: 16), | ||||||
|                   ], |  | ||||||
|  |                         if (_deviceInfo != null) | ||||||
|  |                           _buildSection( | ||||||
|  |                             context, | ||||||
|  |                             title: 'Device Information', | ||||||
|  |                             children: [ | ||||||
|  |                               _buildInfoItem( | ||||||
|  |                                 context, | ||||||
|  |                                 icon: Symbols.label, | ||||||
|  |                                 label: 'aboutDeviceName'.tr(), | ||||||
|  |                                 value: _deviceInfo?.data['name'], | ||||||
|  |                               ), | ||||||
|  |                               _buildInfoItem( | ||||||
|  |                                 context, | ||||||
|  |                                 icon: Symbols.fingerprint, | ||||||
|  |                                 label: 'aboutDeviceIdentifier'.tr(), | ||||||
|  |                                 value: _deviceUdid ?? 'N/A', | ||||||
|  |                                 copyable: true, | ||||||
|  |                               ), | ||||||
|  |                             ], | ||||||
|  |                           ), | ||||||
|  |  | ||||||
|  |                         const SizedBox(height: 16), | ||||||
|  |  | ||||||
|  |                         // Links Card | ||||||
|  |                         _buildSection( | ||||||
|  |                           context, | ||||||
|  |                           title: 'aboutScreenLinksSectionTitle'.tr(), | ||||||
|  |                           children: [ | ||||||
|  |                             _buildListTile( | ||||||
|  |                               context, | ||||||
|  |                               icon: Symbols.system_update, | ||||||
|  |                               title: 'Check for updates', | ||||||
|  |                               onTap: () async { | ||||||
|  |                                 // Fetch latest release and show the unified sheet | ||||||
|  |                                 final svc = UpdateService(); | ||||||
|  |                                 // Reuse service fetch + compare to decide content | ||||||
|  |                                 showLoadingModal(context); | ||||||
|  |                                 final release = await svc.fetchLatestRelease(); | ||||||
|  |                                 if (!context.mounted) return; | ||||||
|  |                                 hideLoadingModal(context); | ||||||
|  |                                 if (release != null) { | ||||||
|  |                                   await svc.showUpdateSheet(context, release); | ||||||
|  |                                 } else { | ||||||
|  |                                   showInfoAlert( | ||||||
|  |                                     'Currently cannot get update from the GitHub.', | ||||||
|  |                                     'Unable to check for updates', | ||||||
|  |                                   ); | ||||||
|  |                                 } | ||||||
|  |                               }, | ||||||
|  |                             ), | ||||||
|  |                             _buildListTile( | ||||||
|  |                               context, | ||||||
|  |                               icon: Symbols.privacy_tip, | ||||||
|  |                               title: 'aboutScreenPrivacyPolicyTitle'.tr(), | ||||||
|  |                               onTap: | ||||||
|  |                                   () => _launchURL( | ||||||
|  |                                     'https://solsynth.dev/terms/privacy-policy', | ||||||
|  |                                   ), | ||||||
|  |                             ), | ||||||
|  |                             _buildListTile( | ||||||
|  |                               context, | ||||||
|  |                               icon: Symbols.description, | ||||||
|  |                               title: 'aboutScreenTermsOfServiceTitle'.tr(), | ||||||
|  |                               onTap: | ||||||
|  |                                   () => _launchURL( | ||||||
|  |                                     'https://solsynth.dev/terms/user-agreement', | ||||||
|  |                                   ), | ||||||
|  |                             ), | ||||||
|  |                             _buildListTile( | ||||||
|  |                               context, | ||||||
|  |                               icon: Symbols.code, | ||||||
|  |                               title: 'aboutScreenOpenSourceLicensesTitle'.tr(), | ||||||
|  |                               onTap: () { | ||||||
|  |                                 showLicensePage( | ||||||
|  |                                   context: context, | ||||||
|  |                                   applicationName: _packageInfo.appName, | ||||||
|  |                                   applicationVersion: | ||||||
|  |                                       'Version ${_packageInfo.version}', | ||||||
|  |                                 ); | ||||||
|  |                               }, | ||||||
|  |                             ), | ||||||
|  |                           ], | ||||||
|  |                         ), | ||||||
|  |  | ||||||
|  |                         const SizedBox(height: 16), | ||||||
|  |  | ||||||
|  |                         // Developer Info | ||||||
|  |                         _buildSection( | ||||||
|  |                           context, | ||||||
|  |                           title: 'aboutScreenDeveloperSectionTitle'.tr(), | ||||||
|  |                           children: [ | ||||||
|  |                             _buildListTile( | ||||||
|  |                               context, | ||||||
|  |                               icon: Symbols.email, | ||||||
|  |                               title: 'aboutScreenContactUsTitle'.tr(), | ||||||
|  |                               subtitle: 'lily@solsynth.dev', | ||||||
|  |                               onTap: | ||||||
|  |                                   () => _launchURL('mailto:lily@solsynth.dev'), | ||||||
|  |                             ), | ||||||
|  |                             _buildListTile( | ||||||
|  |                               context, | ||||||
|  |                               icon: Symbols.copyright, | ||||||
|  |                               title: 'aboutScreenLicenseTitle'.tr(), | ||||||
|  |                               subtitle: 'aboutScreenLicenseContent'.tr( | ||||||
|  |                                 args: [DateTime.now().year.toString()], | ||||||
|  |                               ), | ||||||
|  |                               onTap: | ||||||
|  |                                   () => _launchURL( | ||||||
|  |                                     'https://github.com/Solsynth/Solian/blob/v3/LICENSE.txt', | ||||||
|  |                                   ), | ||||||
|  |                             ), | ||||||
|  |                             if (kIsWeb || !(Platform.isMacOS || Platform.isIOS)) | ||||||
|  |                               _buildListTile( | ||||||
|  |                                 context, | ||||||
|  |                                 icon: Symbols.favorite, | ||||||
|  |                                 title: 'donate'.tr(), | ||||||
|  |                                 subtitle: 'donateDescription'.tr(), | ||||||
|  |                                 onTap: () { | ||||||
|  |                                   launchUrlString( | ||||||
|  |                                     'https://afdian.com/@littlesheep', | ||||||
|  |                                   ); | ||||||
|  |                                 }, | ||||||
|  |                               ), | ||||||
|  |                           ], | ||||||
|  |                         ), | ||||||
|  |  | ||||||
|  |                         const SizedBox(height: 32), | ||||||
|  |  | ||||||
|  |                         // Copyright | ||||||
|  |                         Padding( | ||||||
|  |                           padding: const EdgeInsets.all(16.0), | ||||||
|  |                           child: Column( | ||||||
|  |                             children: [ | ||||||
|  |                               Text( | ||||||
|  |                                 'aboutScreenCopyright'.tr( | ||||||
|  |                                   args: [DateTime.now().year.toString()], | ||||||
|  |                                 ), | ||||||
|  |                                 style: theme.textTheme.bodySmall, | ||||||
|  |                                 textAlign: TextAlign.center, | ||||||
|  |                               ), | ||||||
|  |                               const Gap(1), | ||||||
|  |                               Text( | ||||||
|  |                                 'aboutScreenMadeWith'.tr(), | ||||||
|  |                                 textAlign: TextAlign.center, | ||||||
|  |                               ).fontSize(10).opacity(0.8), | ||||||
|  |                             ], | ||||||
|  |                           ), | ||||||
|  |                         ), | ||||||
|  |  | ||||||
|  |                         Gap(MediaQuery.of(context).padding.bottom + 16), | ||||||
|  |                       ], | ||||||
|  |                     ), | ||||||
|  |                   ), | ||||||
|                 ), |                 ), | ||||||
|               ), |               ), | ||||||
|     ); |     ); | ||||||
|   | |||||||
| @@ -1,12 +1,8 @@ | |||||||
| import 'package:easy_localization/easy_localization.dart'; | import 'package:easy_localization/easy_localization.dart'; | ||||||
| import 'package:flutter/foundation.dart'; |  | ||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
| import 'package:go_router/go_router.dart'; | import 'package:go_router/go_router.dart'; | ||||||
| import 'package:flutter/services.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/message.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'; | ||||||
| @@ -15,6 +11,7 @@ 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/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: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'; | ||||||
|  |  | ||||||
| @@ -189,7 +186,6 @@ class AccountScreen extends HookConsumerWidget { | |||||||
|                 ), |                 ), | ||||||
|               ], |               ], | ||||||
|             ).padding(horizontal: 8), |             ).padding(horizontal: 8), | ||||||
|             const Gap(8), |  | ||||||
|             ListTile( |             ListTile( | ||||||
|               minTileHeight: 48, |               minTileHeight: 48, | ||||||
|               leading: const Icon(Symbols.notifications), |               leading: const Icon(Symbols.notifications), | ||||||
| @@ -230,7 +226,17 @@ class AccountScreen extends HookConsumerWidget { | |||||||
|             ), |             ), | ||||||
|             ListTile( |             ListTile( | ||||||
|               minTileHeight: 48, |               minTileHeight: 48, | ||||||
|               title: Text('abuseReports').tr(), |               leading: const Icon(Symbols.emoji_emotions), | ||||||
|  |               trailing: const Icon(Symbols.chevron_right), | ||||||
|  |               contentPadding: EdgeInsets.symmetric(horizontal: 24), | ||||||
|  |               title: Text('stickers').tr(), | ||||||
|  |               onTap: () { | ||||||
|  |                 context.pushNamed('stickerMarketplace'); | ||||||
|  |               }, | ||||||
|  |             ), | ||||||
|  |             ListTile( | ||||||
|  |               minTileHeight: 48, | ||||||
|  |               title: Text('abuseReport').tr(), | ||||||
|               contentPadding: const EdgeInsets.symmetric(horizontal: 24), |               contentPadding: const EdgeInsets.symmetric(horizontal: 24), | ||||||
|               leading: const Icon(Symbols.gavel), |               leading: const Icon(Symbols.gavel), | ||||||
|               trailing: const Icon(Symbols.chevron_right), |               trailing: const Icon(Symbols.chevron_right), | ||||||
| @@ -267,30 +273,6 @@ class AccountScreen extends HookConsumerWidget { | |||||||
|                 context.pushNamed('accountSettings'); |                 context.pushNamed('accountSettings'); | ||||||
|               }, |               }, | ||||||
|             ), |             ), | ||||||
|             if (kDebugMode) const Divider(height: 1).padding(vertical: 8), |  | ||||||
|             if (kDebugMode) |  | ||||||
|               ListTile( |  | ||||||
|                 minTileHeight: 48, |  | ||||||
|                 leading: const Icon(Symbols.copy_all), |  | ||||||
|                 trailing: const Icon(Symbols.chevron_right), |  | ||||||
|                 contentPadding: EdgeInsets.symmetric(horizontal: 24), |  | ||||||
|                 title: Text('Copy access token'), |  | ||||||
|                 onTap: () async { |  | ||||||
|                   final tk = ref.watch(tokenProvider); |  | ||||||
|                   Clipboard.setData(ClipboardData(text: tk!.token)); |  | ||||||
|                 }, |  | ||||||
|               ), |  | ||||||
|             if (kDebugMode) |  | ||||||
|               ListTile( |  | ||||||
|                 minTileHeight: 48, |  | ||||||
|                 leading: const Icon(Symbols.delete), |  | ||||||
|                 trailing: const Icon(Symbols.chevron_right), |  | ||||||
|                 contentPadding: EdgeInsets.symmetric(horizontal: 24), |  | ||||||
|                 title: Text('Reset database'), |  | ||||||
|                 onTap: () async { |  | ||||||
|                   resetDatabase(ref); |  | ||||||
|                 }, |  | ||||||
|               ), |  | ||||||
|             const Divider(height: 1).padding(vertical: 8), |             const Divider(height: 1).padding(vertical: 8), | ||||||
|             ListTile( |             ListTile( | ||||||
|               minTileHeight: 48, |               minTileHeight: 48, | ||||||
| @@ -302,6 +284,19 @@ class AccountScreen extends HookConsumerWidget { | |||||||
|                 context.pushNamed('about'); |                 context.pushNamed('about'); | ||||||
|               }, |               }, | ||||||
|             ), |             ), | ||||||
|  |             ListTile( | ||||||
|  |               minTileHeight: 48, | ||||||
|  |               leading: const Icon(Symbols.bug_report), | ||||||
|  |               trailing: const Icon(Symbols.chevron_right), | ||||||
|  |               contentPadding: EdgeInsets.symmetric(horizontal: 24), | ||||||
|  |               title: Text('debugOptions').tr(), | ||||||
|  |               onTap: () { | ||||||
|  |                 showModalBottomSheet( | ||||||
|  |                   context: context, | ||||||
|  |                   builder: (context) => DebugSheet(), | ||||||
|  |                 ); | ||||||
|  |               }, | ||||||
|  |             ), | ||||||
|             ListTile( |             ListTile( | ||||||
|               minTileHeight: 48, |               minTileHeight: 48, | ||||||
|               leading: const Icon(Symbols.logout), |               leading: const Icon(Symbols.logout), | ||||||
|   | |||||||
| @@ -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,9 +3,11 @@ import 'package:dropdown_button2/dropdown_button2.dart'; | |||||||
| import 'package:easy_localization/easy_localization.dart'; | import 'package:easy_localization/easy_localization.dart'; | ||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
| import 'package:flutter_hooks/flutter_hooks.dart'; | import 'package:flutter_hooks/flutter_hooks.dart'; | ||||||
|  | import 'package:gap/gap.dart'; | ||||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
| import 'package: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/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'; | ||||||
| @@ -94,6 +96,7 @@ class UpdateProfileScreen extends HookConsumerWidget { | |||||||
|     final usernameController = useTextEditingController(text: user.value!.name); |     final usernameController = useTextEditingController(text: user.value!.name); | ||||||
|     final nicknameController = useTextEditingController(text: user.value!.nick); |     final nicknameController = useTextEditingController(text: user.value!.nick); | ||||||
|     final language = useState(user.value!.language); |     final language = useState(user.value!.language); | ||||||
|  |     final links = useState<List<ProfileLink>>(user.value!.profile.links); | ||||||
|  |  | ||||||
|     void updateBasicInfo() async { |     void updateBasicInfo() async { | ||||||
|       if (!formKeyBasicInfo.currentState!.validate()) return; |       if (!formKeyBasicInfo.currentState!.validate()) return; | ||||||
| @@ -165,6 +168,7 @@ 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, | ||||||
|           }, |           }, | ||||||
|         ); |         ); | ||||||
|         final userNotifier = ref.read(userInfoProvider.notifier); |         final userNotifier = ref.read(userInfoProvider.notifier); | ||||||
| @@ -558,6 +562,73 @@ class UpdateProfileScreen extends HookConsumerWidget { | |||||||
|                       ), |                       ), | ||||||
|                     ), |                     ), | ||||||
|                   ), |                   ), | ||||||
|  |                   Text('links').tr().bold().fontSize(18).padding(top: 16), | ||||||
|  |                   Column( | ||||||
|  |                     spacing: 8, | ||||||
|  |                     children: [ | ||||||
|  |                       for (var i = 0; i < links.value.length; i++) | ||||||
|  |                         Row( | ||||||
|  |                           crossAxisAlignment: CrossAxisAlignment.end, | ||||||
|  |                           children: [ | ||||||
|  |                             Expanded( | ||||||
|  |                               child: TextFormField( | ||||||
|  |                                 initialValue: links.value[i].name, | ||||||
|  |                                 decoration: InputDecoration( | ||||||
|  |                                   labelText: 'linkKey'.tr(), | ||||||
|  |                                   isDense: true, | ||||||
|  |                                 ), | ||||||
|  |                                 onChanged: (value) { | ||||||
|  |                                   links.value[i] = links.value[i].copyWith( | ||||||
|  |                                     name: value, | ||||||
|  |                                   ); | ||||||
|  |                                 }, | ||||||
|  |                                 onTapOutside: | ||||||
|  |                                     (_) => | ||||||
|  |                                         FocusManager.instance.primaryFocus | ||||||
|  |                                             ?.unfocus(), | ||||||
|  |                               ), | ||||||
|  |                             ), | ||||||
|  |                             const Gap(8), | ||||||
|  |                             Expanded( | ||||||
|  |                               child: TextFormField( | ||||||
|  |                                 initialValue: links.value[i].url, | ||||||
|  |                                 decoration: InputDecoration( | ||||||
|  |                                   labelText: 'linkValue'.tr(), | ||||||
|  |                                   isDense: true, | ||||||
|  |                                 ), | ||||||
|  |                                 onChanged: (value) { | ||||||
|  |                                   links.value[i] = links.value[i].copyWith( | ||||||
|  |                                     url: value, | ||||||
|  |                                   ); | ||||||
|  |                                 }, | ||||||
|  |                                 onTapOutside: | ||||||
|  |                                     (_) => | ||||||
|  |                                         FocusManager.instance.primaryFocus | ||||||
|  |                                             ?.unfocus(), | ||||||
|  |                               ), | ||||||
|  |                             ), | ||||||
|  |                             IconButton( | ||||||
|  |                               icon: const Icon(Symbols.delete), | ||||||
|  |                               onPressed: () { | ||||||
|  |                                 links.value = List.from(links.value) | ||||||
|  |                                   ..removeAt(i); | ||||||
|  |                               }, | ||||||
|  |                             ), | ||||||
|  |                           ], | ||||||
|  |                         ), | ||||||
|  |                       Align( | ||||||
|  |                         alignment: Alignment.centerRight, | ||||||
|  |                         child: FilledButton.icon( | ||||||
|  |                           onPressed: () { | ||||||
|  |                             links.value = List.from(links.value) | ||||||
|  |                               ..add(ProfileLink(name: '', url: '')); | ||||||
|  |                           }, | ||||||
|  |                           label: Text('addLink').tr(), | ||||||
|  |                           icon: const Icon(Symbols.add), | ||||||
|  |                         ).padding(top: 8), | ||||||
|  |                       ), | ||||||
|  |                     ], | ||||||
|  |                   ), | ||||||
|                   Align( |                   Align( | ||||||
|                     alignment: Alignment.centerRight, |                     alignment: Alignment.centerRight, | ||||||
|                     child: TextButton.icon( |                     child: TextButton.icon( | ||||||
|   | |||||||
| @@ -1,6 +1,8 @@ | |||||||
| import 'package:dio/dio.dart'; | import 'package:dio/dio.dart'; | ||||||
| import 'package:easy_localization/easy_localization.dart'; | import 'package:easy_localization/easy_localization.dart'; | ||||||
|  | import 'package:flutter/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'; | ||||||
| @@ -13,6 +15,7 @@ import 'package:island/pods/network.dart'; | |||||||
| import 'package:island/pods/userinfo.dart'; | import 'package:island/pods/userinfo.dart'; | ||||||
| import 'package:island/services/color.dart'; | import 'package:island/services/color.dart'; | ||||||
| import 'package:island/services/responsive.dart'; | import 'package:island/services/responsive.dart'; | ||||||
|  | import 'package:island/services/text.dart'; | ||||||
| import 'package:island/services/time.dart'; | import 'package:island/services/time.dart'; | ||||||
| import 'package:island/services/timezone/native.dart'; | import 'package:island/services/timezone/native.dart'; | ||||||
| import 'package:island/widgets/account/account_name.dart'; | import 'package:island/widgets/account/account_name.dart'; | ||||||
| @@ -30,6 +33,7 @@ import 'package:palette_generator/palette_generator.dart'; | |||||||
| import 'package:riverpod_annotation/riverpod_annotation.dart'; | import 'package:riverpod_annotation/riverpod_annotation.dart'; | ||||||
| import 'package:share_plus/share_plus.dart'; | import 'package:share_plus/share_plus.dart'; | ||||||
| import 'package:styled_widget/styled_widget.dart'; | import 'package:styled_widget/styled_widget.dart'; | ||||||
|  | import 'package:url_launcher/url_launcher_string.dart'; | ||||||
|  |  | ||||||
| part 'profile.g.dart'; | part 'profile.g.dart'; | ||||||
|  |  | ||||||
| @@ -194,6 +198,15 @@ class AccountProfileScreen extends HookConsumerWidget { | |||||||
|  |  | ||||||
|     List<Widget> buildSubcolumn(SnAccount data) { |     List<Widget> buildSubcolumn(SnAccount data) { | ||||||
|       return [ |       return [ | ||||||
|  |         Row( | ||||||
|  |           spacing: 6, | ||||||
|  |           children: [ | ||||||
|  |             const Icon(Symbols.join, size: 17, fill: 1), | ||||||
|  |             Text( | ||||||
|  |               'joinedAt'.tr(args: [data.createdAt.formatCustom('yyyy-MM-dd')]), | ||||||
|  |             ), | ||||||
|  |           ], | ||||||
|  |         ), | ||||||
|         if (data.profile.birthday != null) |         if (data.profile.birthday != null) | ||||||
|           Row( |           Row( | ||||||
|             spacing: 6, |             spacing: 6, | ||||||
| @@ -250,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), | ||||||
| @@ -320,7 +337,7 @@ class AccountProfileScreen extends HookConsumerWidget { | |||||||
|               spacing: 2, |               spacing: 2, | ||||||
|               children: buildSubcolumn(data), |               children: buildSubcolumn(data), | ||||||
|             ), |             ), | ||||||
|           if (data.profile.timeZone.isNotEmpty) |           if (data.profile.timeZone.isNotEmpty && !kIsWeb) | ||||||
|             Column( |             Column( | ||||||
|               crossAxisAlignment: CrossAxisAlignment.start, |               crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|               children: [ |               children: [ | ||||||
| @@ -350,6 +367,32 @@ class AccountProfileScreen extends HookConsumerWidget { | |||||||
|       ).padding(horizontal: 24, vertical: 16), |       ).padding(horizontal: 24, vertical: 16), | ||||||
|     ); |     ); | ||||||
|  |  | ||||||
|  |     Widget accountProfileLinks(SnAccount data) => Card( | ||||||
|  |       child: Column( | ||||||
|  |         crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|  |         children: [ | ||||||
|  |           Text('links').tr().bold().padding(horizontal: 24, top: 12, bottom: 4), | ||||||
|  |           for (final link in data.profile.links) | ||||||
|  |             ListTile( | ||||||
|  |               title: Text(link.name.capitalizeEachWord()), | ||||||
|  |               subtitle: Text(link.url), | ||||||
|  |               contentPadding: EdgeInsets.symmetric(horizontal: 24), | ||||||
|  |               trailing: const Icon(Symbols.chevron_right), | ||||||
|  |               shape: RoundedRectangleBorder( | ||||||
|  |                 borderRadius: const BorderRadius.all(Radius.circular(8)), | ||||||
|  |               ), | ||||||
|  |               onTap: () { | ||||||
|  |                 if (!link.url.startsWith('http') && !link.url.contains('://')) { | ||||||
|  |                   launchUrlString('https://${link.url}'); | ||||||
|  |                 } else { | ||||||
|  |                   launchUrlString(link.url); | ||||||
|  |                 } | ||||||
|  |               }, | ||||||
|  |             ), | ||||||
|  |         ], | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |  | ||||||
|     Widget accountAction(SnAccount data) => Card( |     Widget accountAction(SnAccount data) => Card( | ||||||
|       child: Column( |       child: Column( | ||||||
|         children: [ |         children: [ | ||||||
| @@ -452,7 +495,7 @@ class AccountProfileScreen extends HookConsumerWidget { | |||||||
|             ], |             ], | ||||||
|           ), |           ), | ||||||
|         ], |         ], | ||||||
|       ).padding(horizontal: 16, vertical: 8), |       ).padding(horizontal: 16, vertical: 12), | ||||||
|     ); |     ); | ||||||
|  |  | ||||||
|     return account.when( |     return account.when( | ||||||
| @@ -509,9 +552,11 @@ class AccountProfileScreen extends HookConsumerWidget { | |||||||
|                               SliverToBoxAdapter(child: accountBasicInfo(data)), |                               SliverToBoxAdapter(child: accountBasicInfo(data)), | ||||||
|                               if (data.badges.isNotEmpty) |                               if (data.badges.isNotEmpty) | ||||||
|                                 SliverToBoxAdapter( |                                 SliverToBoxAdapter( | ||||||
|                                   child: BadgeList( |                                   child: Card( | ||||||
|                                     badges: data.badges, |                                     child: BadgeList( | ||||||
|                                   ).padding(horizontal: 24, bottom: 24), |                                       badges: data.badges, | ||||||
|  |                                     ).padding(horizontal: 26, vertical: 20), | ||||||
|  |                                   ).padding(left: 2, right: 4), | ||||||
|                                 ), |                                 ), | ||||||
|                               SliverToBoxAdapter( |                               SliverToBoxAdapter( | ||||||
|                                 child: Column( |                                 child: Column( | ||||||
| @@ -521,9 +566,10 @@ class AccountProfileScreen extends HookConsumerWidget { | |||||||
|                                       level: data.profile.level, |                                       level: data.profile.level, | ||||||
|                                       experience: data.profile.experience, |                                       experience: data.profile.experience, | ||||||
|                                       progress: data.profile.levelingProgress, |                                       progress: data.profile.levelingProgress, | ||||||
|                                     ), |                                     ).padding(left: 2, right: 4), | ||||||
|                                     if (data.profile.verification != null) |                                     if (data.profile.verification != null) | ||||||
|                                       Card( |                                       Card( | ||||||
|  |                                         margin: EdgeInsets.zero, | ||||||
|                                         child: VerificationStatusCard( |                                         child: VerificationStatusCard( | ||||||
|                                           mark: data.profile.verification!, |                                           mark: data.profile.verification!, | ||||||
|                                         ), |                                         ), | ||||||
| @@ -534,6 +580,10 @@ class AccountProfileScreen extends HookConsumerWidget { | |||||||
|                               SliverToBoxAdapter( |                               SliverToBoxAdapter( | ||||||
|                                 child: accountProfileBio(data).padding(top: 4), |                                 child: accountProfileBio(data).padding(top: 4), | ||||||
|                               ), |                               ), | ||||||
|  |                               if (data.profile.links.isNotEmpty) | ||||||
|  |                                 SliverToBoxAdapter( | ||||||
|  |                                   child: accountProfileLinks(data), | ||||||
|  |                                 ), | ||||||
|                               SliverToBoxAdapter( |                               SliverToBoxAdapter( | ||||||
|                                 child: accountProfileDetail(data), |                                 child: accountProfileDetail(data), | ||||||
|                               ), |                               ), | ||||||
| @@ -544,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( | ||||||
| @@ -604,9 +654,11 @@ class AccountProfileScreen extends HookConsumerWidget { | |||||||
|                         SliverToBoxAdapter(child: accountBasicInfo(data)), |                         SliverToBoxAdapter(child: accountBasicInfo(data)), | ||||||
|                         if (data.badges.isNotEmpty) |                         if (data.badges.isNotEmpty) | ||||||
|                           SliverToBoxAdapter( |                           SliverToBoxAdapter( | ||||||
|                             child: BadgeList( |                             child: Card( | ||||||
|                               badges: data.badges, |                               child: BadgeList( | ||||||
|                             ).padding(horizontal: 24, bottom: 24), |                                 badges: data.badges, | ||||||
|  |                               ).padding(horizontal: 26, vertical: 20), | ||||||
|  |                             ).padding(horizontal: 4), | ||||||
|                           ), |                           ), | ||||||
|                         SliverToBoxAdapter( |                         SliverToBoxAdapter( | ||||||
|                           child: Column( |                           child: Column( | ||||||
| @@ -628,12 +680,18 @@ class AccountProfileScreen extends HookConsumerWidget { | |||||||
|                         SliverToBoxAdapter( |                         SliverToBoxAdapter( | ||||||
|                           child: accountProfileBio(data).padding(horizontal: 4), |                           child: accountProfileBio(data).padding(horizontal: 4), | ||||||
|                         ), |                         ), | ||||||
|  |                         if (data.profile.links.isNotEmpty) | ||||||
|  |                           SliverToBoxAdapter( | ||||||
|  |                             child: accountProfileLinks( | ||||||
|  |                               data, | ||||||
|  |                             ).padding(horizontal: 4), | ||||||
|  |                           ), | ||||||
|                         SliverToBoxAdapter( |                         SliverToBoxAdapter( | ||||||
|                           child: accountProfileDetail( |                           child: accountProfileDetail( | ||||||
|                             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), | ||||||
|                           ), |                           ), | ||||||
|   | |||||||
| @@ -216,6 +216,7 @@ class RelationshipScreen extends HookConsumerWidget { | |||||||
|       final result = await showModalBottomSheet( |       final result = await showModalBottomSheet( | ||||||
|         context: context, |         context: context, | ||||||
|         useRootNavigator: true, |         useRootNavigator: true, | ||||||
|  |         isScrollControlled: true, | ||||||
|         builder: (context) => AccountPickerSheet(), |         builder: (context) => AccountPickerSheet(), | ||||||
|       ); |       ); | ||||||
|       if (result == null) return; |       if (result == null) return; | ||||||
|   | |||||||
| @@ -227,6 +227,7 @@ class ChatListScreen extends HookConsumerWidget { | |||||||
|       final result = await showModalBottomSheet( |       final result = await showModalBottomSheet( | ||||||
|         context: context, |         context: context, | ||||||
|         useRootNavigator: true, |         useRootNavigator: true, | ||||||
|  |         isScrollControlled: true, | ||||||
|         builder: (context) => const AccountPickerSheet(), |         builder: (context) => const AccountPickerSheet(), | ||||||
|       ); |       ); | ||||||
|       if (result == null) return; |       if (result == null) return; | ||||||
|   | |||||||
| @@ -35,6 +35,7 @@ import 'package:material_symbols_icons/symbols.dart'; | |||||||
| import 'package:riverpod_annotation/riverpod_annotation.dart'; | import 'package:riverpod_annotation/riverpod_annotation.dart'; | ||||||
| import 'chat.dart'; | import 'chat.dart'; | ||||||
| import 'package:island/widgets/chat/call_button.dart'; | import 'package:island/widgets/chat/call_button.dart'; | ||||||
|  | import 'package:island/widgets/stickers/picker.dart'; | ||||||
|  |  | ||||||
| part 'room.g.dart'; | part 'room.g.dart'; | ||||||
|  |  | ||||||
| @@ -338,7 +339,7 @@ class ChatRoomScreen extends HookConsumerWidget { | |||||||
|                             } |                             } | ||||||
|  |  | ||||||
|                             await apiClient.post( |                             await apiClient.post( | ||||||
|                               '/chat/${chatRoom.value!.id}/members/me', |                               '/sphere/chat/${chatRoom.value!.id}/members/me', | ||||||
|                             ); |                             ); | ||||||
|                             ref.invalidate(chatroomIdentityProvider(id)); |                             ref.invalidate(chatroomIdentityProvider(id)); | ||||||
|                           } catch (err) { |                           } catch (err) { | ||||||
| @@ -928,7 +929,7 @@ class ChatRoomScreen extends HookConsumerWidget { | |||||||
|                             if (attachment.isOnCloud) { |                             if (attachment.isOnCloud) { | ||||||
|                               final client = ref.watch(apiClientProvider); |                               final client = ref.watch(apiClientProvider); | ||||||
|                               await client.delete( |                               await client.delete( | ||||||
|                                 '/files/${attachment.data.id}', |                                 '/drive/files/${attachment.data.id}', | ||||||
|                               ); |                               ); | ||||||
|                             } |                             } | ||||||
|                             final clone = List.of(attachments.value); |                             final clone = List.of(attachments.value); | ||||||
| @@ -1066,15 +1067,19 @@ class _ChatInput extends HookConsumerWidget { | |||||||
|                 scrollDirection: Axis.horizontal, |                 scrollDirection: Axis.horizontal, | ||||||
|                 itemCount: attachments.length, |                 itemCount: attachments.length, | ||||||
|                 itemBuilder: (context, idx) { |                 itemBuilder: (context, idx) { | ||||||
|                   return AttachmentPreview( |                   return SizedBox( | ||||||
|                     item: attachments[idx], |                     height: 280, | ||||||
|                     onRequestUpload: () => onUploadAttachment(idx), |                     width: 280, | ||||||
|                     onDelete: () => onDeleteAttachment(idx), |                     child: AttachmentPreview( | ||||||
|                     onUpdate: (value) { |                       item: attachments[idx], | ||||||
|                       attachments[idx] = value; |                       onRequestUpload: () => onUploadAttachment(idx), | ||||||
|                       onAttachmentsChanged(attachments); |                       onDelete: () => onDeleteAttachment(idx), | ||||||
|                     }, |                       onUpdate: (value) { | ||||||
|                     onMove: (delta) => onMoveAttachment(idx, delta), |                         attachments[idx] = value; | ||||||
|  |                         onAttachmentsChanged(attachments); | ||||||
|  |                       }, | ||||||
|  |                       onMove: (delta) => onMoveAttachment(idx, delta), | ||||||
|  |                     ), | ||||||
|                   ); |                   ); | ||||||
|                 }, |                 }, | ||||||
|                 separatorBuilder: (_, _) => const Gap(8), |                 separatorBuilder: (_, _) => const Gap(8), | ||||||
| @@ -1129,31 +1134,76 @@ class _ChatInput extends HookConsumerWidget { | |||||||
|             padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 8), |             padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 8), | ||||||
|             child: Row( |             child: Row( | ||||||
|               children: [ |               children: [ | ||||||
|                 PopupMenuButton( |                 Row( | ||||||
|                   icon: const Icon(Symbols.photo_library), |                   mainAxisSize: MainAxisSize.min, | ||||||
|                   itemBuilder: |                   children: [ | ||||||
|                       (context) => [ |                     IconButton( | ||||||
|                         PopupMenuItem( |                       tooltip: 'stickers'.tr(), | ||||||
|                           onTap: () => onPickFile(true), |                       icon: const Icon(Symbols.emoji_symbols), | ||||||
|                           child: Row( |                       onPressed: () { | ||||||
|                             spacing: 12, |                         final size = MediaQuery.of(context).size; | ||||||
|                             children: [ |                         showStickerPickerPopover( | ||||||
|                               const Icon(Symbols.photo), |                           context, | ||||||
|                               Text('addPhoto').tr(), |                           Offset( | ||||||
|                             ], |                             20, | ||||||
|  |                             size.height - | ||||||
|  |                                 480 - | ||||||
|  |                                 MediaQuery.of(context).padding.bottom, | ||||||
|                           ), |                           ), | ||||||
|                         ), |                           onPick: (placeholder) { | ||||||
|                         PopupMenuItem( |                             // Insert placeholder at current cursor position | ||||||
|                           onTap: () => onPickFile(false), |                             final text = messageController.text; | ||||||
|                           child: Row( |                             final selection = messageController.selection; | ||||||
|                             spacing: 12, |                             final start = | ||||||
|                             children: [ |                                 selection.start >= 0 | ||||||
|                               const Icon(Symbols.video_call), |                                     ? selection.start | ||||||
|                               Text('addVideo').tr(), |                                     : text.length; | ||||||
|                             ], |                             final end = | ||||||
|                           ), |                                 selection.end >= 0 | ||||||
|                         ), |                                     ? selection.end | ||||||
|                       ], |                                     : text.length; | ||||||
|  |                             final newText = text.replaceRange( | ||||||
|  |                               start, | ||||||
|  |                               end, | ||||||
|  |                               placeholder, | ||||||
|  |                             ); | ||||||
|  |                             messageController.value = TextEditingValue( | ||||||
|  |                               text: newText, | ||||||
|  |                               selection: TextSelection.collapsed( | ||||||
|  |                                 offset: start + placeholder.length, | ||||||
|  |                               ), | ||||||
|  |                             ); | ||||||
|  |                           }, | ||||||
|  |                         ); | ||||||
|  |                       }, | ||||||
|  |                     ), | ||||||
|  |                     PopupMenuButton( | ||||||
|  |                       icon: const Icon(Symbols.photo_library), | ||||||
|  |                       itemBuilder: | ||||||
|  |                           (context) => [ | ||||||
|  |                             PopupMenuItem( | ||||||
|  |                               onTap: () => onPickFile(true), | ||||||
|  |                               child: Row( | ||||||
|  |                                 spacing: 12, | ||||||
|  |                                 children: [ | ||||||
|  |                                   const Icon(Symbols.photo), | ||||||
|  |                                   Text('addPhoto').tr(), | ||||||
|  |                                 ], | ||||||
|  |                               ), | ||||||
|  |                             ), | ||||||
|  |                             PopupMenuItem( | ||||||
|  |                               onTap: () => onPickFile(false), | ||||||
|  |                               child: Row( | ||||||
|  |                                 spacing: 12, | ||||||
|  |                                 children: [ | ||||||
|  |                                   const Icon(Symbols.video_call), | ||||||
|  |                                   Text('addVideo').tr(), | ||||||
|  |                                 ], | ||||||
|  |                               ), | ||||||
|  |                             ), | ||||||
|  |                           ], | ||||||
|  |                     ), | ||||||
|  |                   ], | ||||||
|                 ), |                 ), | ||||||
|                 Expanded( |                 Expanded( | ||||||
|                   child: RawKeyboardListener( |                   child: RawKeyboardListener( | ||||||
|   | |||||||
| @@ -589,6 +589,7 @@ class _ChatMemberListSheet extends HookConsumerWidget { | |||||||
|       final result = await showModalBottomSheet( |       final result = await showModalBottomSheet( | ||||||
|         context: context, |         context: context, | ||||||
|         useRootNavigator: true, |         useRootNavigator: true, | ||||||
|  |         isScrollControlled: true, | ||||||
|         builder: (context) => const AccountPickerSheet(), |         builder: (context) => const AccountPickerSheet(), | ||||||
|       ); |       ); | ||||||
|       if (result == null) return; |       if (result == null) return; | ||||||
| @@ -727,7 +728,7 @@ class _ChatMemberListSheet extends HookConsumerWidget { | |||||||
|                                       apiClientProvider, |                                       apiClientProvider, | ||||||
|                                     ); |                                     ); | ||||||
|                                     await apiClient.delete( |                                     await apiClient.delete( | ||||||
|                                       '/chat/$roomId/members/${member.accountId}', |                                       '/sphere/chat/$roomId/members/${member.accountId}', | ||||||
|                                     ); |                                     ); | ||||||
|                                     // Refresh both providers |                                     // Refresh both providers | ||||||
|                                     memberNotifier.reset(); |                                     memberNotifier.reset(); | ||||||
|   | |||||||
| @@ -382,7 +382,7 @@ class CreatorHubScreen extends HookConsumerWidget { | |||||||
|                           ), |                           ), | ||||||
|                           ListTile( |                           ListTile( | ||||||
|                             minTileHeight: 48, |                             minTileHeight: 48, | ||||||
|                             title: const Text('Polls'), |                             title: Text('polls').tr(), | ||||||
|                             trailing: const Icon(Symbols.chevron_right), |                             trailing: const Icon(Symbols.chevron_right), | ||||||
|                             leading: const Icon(Symbols.poll), |                             leading: const Icon(Symbols.poll), | ||||||
|                             contentPadding: const EdgeInsets.symmetric( |                             contentPadding: const EdgeInsets.symmetric( | ||||||
| @@ -419,7 +419,7 @@ class CreatorHubScreen extends HookConsumerWidget { | |||||||
|                           ), |                           ), | ||||||
|                           ListTile( |                           ListTile( | ||||||
|                             minTileHeight: 48, |                             minTileHeight: 48, | ||||||
|                             title: const Text('Web Feeds').tr(), |                             title: const Text('webFeeds').tr(), | ||||||
|                             trailing: const Icon(Symbols.chevron_right), |                             trailing: const Icon(Symbols.chevron_right), | ||||||
|                             leading: const Icon(Symbols.rss_feed), |                             leading: const Icon(Symbols.rss_feed), | ||||||
|                             contentPadding: const EdgeInsets.symmetric( |                             contentPadding: const EdgeInsets.symmetric( | ||||||
| @@ -659,7 +659,7 @@ class PublisherMemberNotifier extends StateNotifier<PublisherMemberState> { | |||||||
|  |  | ||||||
|     try { |     try { | ||||||
|       final response = await _apiClient.get( |       final response = await _apiClient.get( | ||||||
|         '/publishers/$publisherUname/members', |         '/sphere/publishers/$publisherUname/members', | ||||||
|         queryParameters: {'offset': offset, 'take': take}, |         queryParameters: {'offset': offset, 'take': take}, | ||||||
|       ); |       ); | ||||||
|  |  | ||||||
| @@ -708,6 +708,7 @@ class _PublisherMemberListSheet extends HookConsumerWidget { | |||||||
|  |  | ||||||
|     Future<void> invitePerson() async { |     Future<void> invitePerson() async { | ||||||
|       final result = await showModalBottomSheet( |       final result = await showModalBottomSheet( | ||||||
|  |         useRootNavigator: true, | ||||||
|         isScrollControlled: true, |         isScrollControlled: true, | ||||||
|         context: context, |         context: context, | ||||||
|         builder: (context) => const AccountPickerSheet(), |         builder: (context) => const AccountPickerSheet(), | ||||||
| @@ -719,6 +720,9 @@ class _PublisherMemberListSheet extends HookConsumerWidget { | |||||||
|           '/publishers/$publisherUname/invites', |           '/publishers/$publisherUname/invites', | ||||||
|           data: {'related_user_id': result.id, 'role': 0}, |           data: {'related_user_id': result.id, 'role': 0}, | ||||||
|         ); |         ); | ||||||
|  |         // Refresh both providers | ||||||
|  |         memberNotifier.reset(); | ||||||
|  |         await memberNotifier.loadMore(); | ||||||
|         ref.invalidate(memberListProvider); |         ref.invalidate(memberListProvider); | ||||||
|       } catch (err) { |       } catch (err) { | ||||||
|         showErrorAlert(err); |         showErrorAlert(err); | ||||||
| @@ -822,6 +826,9 @@ class _PublisherMemberListSheet extends HookConsumerWidget { | |||||||
|                                       ), |                                       ), | ||||||
|                                 ).then((value) { |                                 ).then((value) { | ||||||
|                                   if (value != null) { |                                   if (value != null) { | ||||||
|  |                                     // Refresh both providers | ||||||
|  |                                     memberNotifier.reset(); | ||||||
|  |                                     memberNotifier.loadMore(); | ||||||
|                                     ref.invalidate(memberListProvider); |                                     ref.invalidate(memberListProvider); | ||||||
|                                   } |                                   } | ||||||
|                                 }); |                                 }); | ||||||
| @@ -843,6 +850,9 @@ class _PublisherMemberListSheet extends HookConsumerWidget { | |||||||
|                                     await apiClient.delete( |                                     await apiClient.delete( | ||||||
|                                       '/publishers/$publisherUname/members/${member.accountId}', |                                       '/publishers/$publisherUname/members/${member.accountId}', | ||||||
|                                     ); |                                     ); | ||||||
|  |                                     // Refresh both providers | ||||||
|  |                                     memberNotifier.reset(); | ||||||
|  |                                     memberNotifier.loadMore(); | ||||||
|                                     ref.invalidate(memberListProvider); |                                     ref.invalidate(memberListProvider); | ||||||
|                                   } catch (err) { |                                   } catch (err) { | ||||||
|                                     showErrorAlert(err); |                                     showErrorAlert(err); | ||||||
|   | |||||||
| @@ -4,6 +4,8 @@ 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/poll.dart'; | import 'package:island/models/poll.dart'; | ||||||
| import 'package:island/pods/network.dart'; | import 'package:island/pods/network.dart'; | ||||||
|  | import 'package:island/widgets/app_scaffold.dart'; | ||||||
|  | import 'package:island/widgets/poll/poll_feedback.dart'; | ||||||
| import 'package:material_symbols_icons/symbols.dart'; | import 'package:material_symbols_icons/symbols.dart'; | ||||||
| import 'package:riverpod_annotation/riverpod_annotation.dart'; | import 'package:riverpod_annotation/riverpod_annotation.dart'; | ||||||
| import 'package:riverpod_paging_utils/riverpod_paging_utils.dart'; | import 'package:riverpod_paging_utils/riverpod_paging_utils.dart'; | ||||||
| @@ -12,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); | ||||||
|  |  | ||||||
| @@ -40,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; | ||||||
| @@ -53,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}); | ||||||
|  |  | ||||||
| @@ -62,14 +73,14 @@ 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); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context, WidgetRef ref) { |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|     return Scaffold( |     return AppScaffold( | ||||||
|       appBar: AppBar(title: const Text('Polls')), |       appBar: AppBar(title: const Text('Polls')), | ||||||
|       floatingActionButton: FloatingActionButton( |       floatingActionButton: FloatingActionButton( | ||||||
|         onPressed: () => _createPoll(context), |         onPressed: () => _createPoll(context), | ||||||
| @@ -90,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, | ||||||
|  |                       ); | ||||||
|                     }, |                     }, | ||||||
|                   ), |                   ), | ||||||
|             ), |             ), | ||||||
| @@ -104,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' | ||||||
| @@ -121,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, | ||||||
|                 ), |                 ), | ||||||
| @@ -137,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, | ||||||
|               ), |               ), | ||||||
|             ), |             ), | ||||||
| @@ -157,17 +172,19 @@ 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}, | ||||||
|                     ); |                     ); | ||||||
|                   }, |                   }, | ||||||
|                 ), |                 ), | ||||||
|               ], |               ], | ||||||
|         ), |         ), | ||||||
|         onTap: () { |         onTap: () { | ||||||
|           // Open editor for edit |           showModalBottomSheet( | ||||||
|           // Navigator push by path to keep consistency with rest of app: |             context: context, | ||||||
|           // Note: pub name string may be required in route; when absent, route may need query or pick later. |             useRootNavigator: true, | ||||||
|           // For safety, just do nothing if no publisher in list item. |             isScrollControlled: true, | ||||||
|  |             builder: (context) => PollFeedbackSheet(pollId: pollWithStats.id), | ||||||
|  |           ); | ||||||
|         }, |         }, | ||||||
|       ), |       ), | ||||||
|     ); |     ); | ||||||
|   | |||||||
| @@ -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); | ||||||
|   | |||||||
| @@ -58,7 +58,7 @@ class StickerPackDetailScreen extends HookConsumerWidget { | |||||||
|       try { |       try { | ||||||
|         showLoadingModal(context); |         showLoadingModal(context); | ||||||
|         final apiClient = ref.watch(apiClientProvider); |         final apiClient = ref.watch(apiClientProvider); | ||||||
|         await apiClient.delete('/stickers/$id/content/${sticker.id}'); |         await apiClient.delete('/sphere/stickers/$id/content/${sticker.id}'); | ||||||
|         ref.invalidate(stickerPackContentProvider(id)); |         ref.invalidate(stickerPackContentProvider(id)); | ||||||
|       } catch (err) { |       } catch (err) { | ||||||
|         showErrorAlert(err); |         showErrorAlert(err); | ||||||
| @@ -180,6 +180,7 @@ class StickerPackDetailScreen extends HookConsumerWidget { | |||||||
|                                               .pushNamed( |                                               .pushNamed( | ||||||
|                                                 'creatorStickerEdit', |                                                 'creatorStickerEdit', | ||||||
|                                                 pathParameters: { |                                                 pathParameters: { | ||||||
|  |                                                   'name': pubName, | ||||||
|                                                   'packId': id, |                                                   'packId': id, | ||||||
|                                                   'id': sticker.id, |                                                   'id': sticker.id, | ||||||
|                                                 }, |                                                 }, | ||||||
| @@ -297,7 +298,7 @@ class _StickerPackActionMenu extends HookConsumerWidget { | |||||||
|                 ).then((confirm) { |                 ).then((confirm) { | ||||||
|                   if (confirm) { |                   if (confirm) { | ||||||
|                     final client = ref.watch(apiClientProvider); |                     final client = ref.watch(apiClientProvider); | ||||||
|                     client.delete('/stickers/$packId'); |                     client.delete('/sphere/stickers/$packId'); | ||||||
|                     ref.invalidate(stickerPacksNotifierProvider); |                     ref.invalidate(stickerPacksNotifierProvider); | ||||||
|                     if (context.mounted) context.pop(true); |                     if (context.mounted) context.pop(true); | ||||||
|                   } |                   } | ||||||
| @@ -325,7 +326,7 @@ Future<SnSticker?> stickerPackSticker( | |||||||
|   if (query == null) return null; |   if (query == null) return null; | ||||||
|   final apiClient = ref.watch(apiClientProvider); |   final apiClient = ref.watch(apiClientProvider); | ||||||
|   final resp = await apiClient.get( |   final resp = await apiClient.get( | ||||||
|     '/stickers/${query.packId}/content/${query.id}', |     '/sphere/stickers/${query.packId}/content/${query.id}', | ||||||
|   ); |   ); | ||||||
|   if (resp.data == null) return null; |   if (resp.data == null) return null; | ||||||
|   return SnSticker.fromJson(resp.data); |   return SnSticker.fromJson(resp.data); | ||||||
| @@ -379,8 +380,8 @@ class EditStickersScreen extends HookConsumerWidget { | |||||||
|       try { |       try { | ||||||
|         final resp = await apiClient.request( |         final resp = await apiClient.request( | ||||||
|           id == null |           id == null | ||||||
|               ? '/stickers/$packId/content' |               ? '/sphere/stickers/$packId/content' | ||||||
|               : '/stickers/$packId/content/$id', |               : '/sphere/stickers/$packId/content/$id', | ||||||
|           data: {'slug': slugController.text, 'image_id': imageController.text}, |           data: {'slug': slugController.text, 'image_id': imageController.text}, | ||||||
|           options: Options(method: id == null ? 'POST' : 'PATCH'), |           options: Options(method: id == null ? 'POST' : 'PATCH'), | ||||||
|         ); |         ); | ||||||
|   | |||||||
| @@ -151,7 +151,7 @@ class _StickerPackContentProviderElement | |||||||
| } | } | ||||||
|  |  | ||||||
| String _$stickerPackStickerHash() => | String _$stickerPackStickerHash() => | ||||||
|     r'36f524c047e632236d5597aaaa8678ed86599602'; |     r'5c553666b3a63530bdebae4b7cd52f303c5ab3a0'; | ||||||
|  |  | ||||||
| /// See also [stickerPackSticker]. | /// See also [stickerPackSticker]. | ||||||
| @ProviderFor(stickerPackSticker) | @ProviderFor(stickerPackSticker) | ||||||
|   | |||||||
| @@ -31,7 +31,7 @@ class StickersScreen extends HookConsumerWidget { | |||||||
|               context |               context | ||||||
|                   .pushNamed( |                   .pushNamed( | ||||||
|                     'creatorStickerPackNew', |                     'creatorStickerPackNew', | ||||||
|                     queryParameters: {'name': pubName}, |                     pathParameters: {'name': pubName}, | ||||||
|                   ) |                   ) | ||||||
|                   .then((value) { |                   .then((value) { | ||||||
|                     if (value != null) { |                     if (value != null) { | ||||||
| @@ -187,10 +187,8 @@ class EditStickerPacksScreen extends HookConsumerWidget { | |||||||
|             'description': descriptionController.text, |             'description': descriptionController.text, | ||||||
|             'prefix': prefixController.text, |             'prefix': prefixController.text, | ||||||
|           }, |           }, | ||||||
|           options: Options( |           queryParameters: {'pub': pubName}, | ||||||
|             method: packId == null ? 'POST' : 'PATCH', |           options: Options(method: packId == null ? 'POST' : 'PATCH'), | ||||||
|             headers: {'X-Pub': pubName}, |  | ||||||
|           ), |  | ||||||
|         ); |         ); | ||||||
|         if (!context.mounted) return; |         if (!context.mounted) return; | ||||||
|         context.pop(SnStickerPack.fromJson(resp.data)); |         context.pop(SnStickerPack.fromJson(resp.data)); | ||||||
|   | |||||||
| @@ -114,10 +114,11 @@ class WebFeedEditScreen extends HookConsumerWidget { | |||||||
|  |  | ||||||
|     return feedAsync.when( |     return feedAsync.when( | ||||||
|       loading: |       loading: | ||||||
|           () => |           () => const AppScaffold( | ||||||
|               const Scaffold(body: Center(child: CircularProgressIndicator())), |             body: Center(child: CircularProgressIndicator()), | ||||||
|  |           ), | ||||||
|       error: |       error: | ||||||
|           (error, stack) => Scaffold( |           (error, stack) => AppScaffold( | ||||||
|             appBar: AppBar(title: const Text('Error')), |             appBar: AppBar(title: const Text('Error')), | ||||||
|             body: Center(child: Text('Error: $error')), |             body: Center(child: Text('Error: $error')), | ||||||
|           ), |           ), | ||||||
|   | |||||||
| @@ -18,7 +18,7 @@ part 'apps.g.dart'; | |||||||
| @riverpod | @riverpod | ||||||
| Future<List<CustomApp>> customApps(Ref ref, String publisherName) async { | Future<List<CustomApp>> customApps(Ref ref, String publisherName) async { | ||||||
|   final client = ref.watch(apiClientProvider); |   final client = ref.watch(apiClientProvider); | ||||||
|   final resp = await client.get('/developers/$publisherName/apps'); |   final resp = await client.get('/develop/developers/$publisherName/apps'); | ||||||
|   return resp.data.map((e) => CustomApp.fromJson(e)).cast<CustomApp>().toList(); |   return resp.data.map((e) => CustomApp.fromJson(e)).cast<CustomApp>().toList(); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -37,7 +37,10 @@ class CustomAppsScreen extends HookConsumerWidget { | |||||||
|           IconButton( |           IconButton( | ||||||
|             icon: const Icon(Symbols.add), |             icon: const Icon(Symbols.add), | ||||||
|             onPressed: () { |             onPressed: () { | ||||||
|               context.pushNamed('developerAppNew', pathParameters: {'name': publisherName}); |               context.pushNamed( | ||||||
|  |                 'developerAppNew', | ||||||
|  |                 pathParameters: {'name': publisherName}, | ||||||
|  |               ); | ||||||
|             }, |             }, | ||||||
|           ), |           ), | ||||||
|         ], |         ], | ||||||
| @@ -121,7 +124,13 @@ class CustomAppsScreen extends HookConsumerWidget { | |||||||
|                               ], |                               ], | ||||||
|                           onSelected: (value) { |                           onSelected: (value) { | ||||||
|                             if (value == 'edit') { |                             if (value == 'edit') { | ||||||
|                               context.pushNamed('developerAppEdit', pathParameters: {'name': publisherName, 'id': app.id}); |                               context.pushNamed( | ||||||
|  |                                 'developerAppEdit', | ||||||
|  |                                 pathParameters: { | ||||||
|  |                                   'name': publisherName, | ||||||
|  |                                   'id': app.id, | ||||||
|  |                                 }, | ||||||
|  |                               ); | ||||||
|                             } else if (value == 'delete') { |                             } else if (value == 'delete') { | ||||||
|                               showConfirmAlert( |                               showConfirmAlert( | ||||||
|                                 'deleteCustomAppHint'.tr(), |                                 'deleteCustomAppHint'.tr(), | ||||||
| @@ -130,7 +139,7 @@ class CustomAppsScreen extends HookConsumerWidget { | |||||||
|                                 if (confirm) { |                                 if (confirm) { | ||||||
|                                   final client = ref.read(apiClientProvider); |                                   final client = ref.read(apiClientProvider); | ||||||
|                                   client.delete( |                                   client.delete( | ||||||
|                                     '/developers/$publisherName/apps/${app.id}', |                                     '/develop/developers/$publisherName/apps/${app.id}', | ||||||
|                                   ); |                                   ); | ||||||
|                                   ref.invalidate( |                                   ref.invalidate( | ||||||
|                                     customAppsProvider(publisherName), |                                     customAppsProvider(publisherName), | ||||||
|   | |||||||
| @@ -6,7 +6,7 @@ part of 'apps.dart'; | |||||||
| // RiverpodGenerator | // RiverpodGenerator | ||||||
| // ************************************************************************** | // ************************************************************************** | ||||||
|  |  | ||||||
| String _$customAppsHash() => r'1dec11573b9d987c3adbdf4732b3781a6f40172a'; | String _$customAppsHash() => r'c6ac78060eb51a2b208a749a81ecbe0a9c608ce1'; | ||||||
|  |  | ||||||
| /// Copied from Dart SDK | /// Copied from Dart SDK | ||||||
| class _SystemHash { | class _SystemHash { | ||||||
|   | |||||||
| @@ -24,7 +24,7 @@ part 'edit_app.g.dart'; | |||||||
| @riverpod | @riverpod | ||||||
| Future<CustomApp?> customApp(Ref ref, String publisherName, String id) async { | Future<CustomApp?> customApp(Ref ref, String publisherName, String id) async { | ||||||
|   final client = ref.watch(apiClientProvider); |   final client = ref.watch(apiClientProvider); | ||||||
|   final resp = await client.get('/developers/$publisherName/apps/$id'); |   final resp = await client.get('/develop/developers/$publisherName/apps/$id'); | ||||||
|   return CustomApp.fromJson(resp.data); |   return CustomApp.fromJson(resp.data); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -282,9 +282,15 @@ class EditAppScreen extends HookConsumerWidget { | |||||||
|                 : null, |                 : null, | ||||||
|       }; |       }; | ||||||
|       if (isNew) { |       if (isNew) { | ||||||
|         await client.post('/developers/$publisherName/apps', data: data); |         await client.post( | ||||||
|  |           '/develop/developers/$publisherName/apps', | ||||||
|  |           data: data, | ||||||
|  |         ); | ||||||
|       } else { |       } else { | ||||||
|         await client.patch('/developers/$publisherName/apps/$id', data: data); |         await client.patch( | ||||||
|  |           '/develop/developers/$publisherName/apps/$id', | ||||||
|  |           data: data, | ||||||
|  |         ); | ||||||
|       } |       } | ||||||
|       ref.invalidate(customAppsProvider(publisherName)); |       ref.invalidate(customAppsProvider(publisherName)); | ||||||
|       if (context.mounted) { |       if (context.mounted) { | ||||||
|   | |||||||
| @@ -6,7 +6,7 @@ part of 'edit_app.dart'; | |||||||
| // RiverpodGenerator | // RiverpodGenerator | ||||||
| // ************************************************************************** | // ************************************************************************** | ||||||
|  |  | ||||||
| String _$customAppHash() => r'aa4d1fb803c47a99cbacf6d91481f4fce3fda457'; | String _$customAppHash() => r'42ad937b8439c793e3c5c35568bb5fa4da017df3'; | ||||||
|  |  | ||||||
| /// Copied from Dart SDK | /// Copied from Dart SDK | ||||||
| class _SystemHash { | class _SystemHash { | ||||||
|   | |||||||
| @@ -25,17 +25,17 @@ part 'hub.g.dart'; | |||||||
| Future<DeveloperStats?> developerStats(Ref ref, String? uname) async { | Future<DeveloperStats?> developerStats(Ref ref, String? uname) async { | ||||||
|   if (uname == null) return null; |   if (uname == null) return null; | ||||||
|   final apiClient = ref.watch(apiClientProvider); |   final apiClient = ref.watch(apiClientProvider); | ||||||
|   final resp = await apiClient.get('/sphere/developers/$uname/stats'); |   final resp = await apiClient.get('/develop/developers/$uname/stats'); | ||||||
|   return DeveloperStats.fromJson(resp.data); |   return DeveloperStats.fromJson(resp.data); | ||||||
| } | } | ||||||
|  |  | ||||||
| @riverpod | @riverpod | ||||||
| Future<List<SnPublisher>> developers(Ref ref) async { | Future<List<SnDeveloper>> developers(Ref ref) async { | ||||||
|   final client = ref.watch(apiClientProvider); |   final client = ref.watch(apiClientProvider); | ||||||
|   final resp = await client.get('/sphere/developers'); |   final resp = await client.get('/develop/developers'); | ||||||
|   return resp.data |   return resp.data | ||||||
|       .map((e) => SnPublisher.fromJson(e)) |       .map((e) => SnDeveloper.fromJson(e)) | ||||||
|       .cast<SnPublisher>() |       .cast<SnDeveloper>() | ||||||
|       .toList(); |       .toList(); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -74,25 +74,25 @@ class DeveloperHubScreen extends HookConsumerWidget { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     final developers = ref.watch(developersProvider); |     final developers = ref.watch(developersProvider); | ||||||
|     final currentDeveloper = useState<SnPublisher?>( |     final currentDeveloper = useState<SnDeveloper?>( | ||||||
|       developers.value?.firstOrNull, |       developers.value?.firstOrNull, | ||||||
|     ); |     ); | ||||||
|  |  | ||||||
|     final List<DropdownMenuItem<SnPublisher>> developersMenu = developers.when( |     final List<DropdownMenuItem<SnDeveloper>> developersMenu = developers.when( | ||||||
|       data: |       data: | ||||||
|           (data) => |           (data) => | ||||||
|               data |               data | ||||||
|                   .map( |                   .map( | ||||||
|                     (item) => DropdownMenuItem<SnPublisher>( |                     (item) => DropdownMenuItem<SnDeveloper>( | ||||||
|                       value: item, |                       value: item, | ||||||
|                       child: ListTile( |                       child: ListTile( | ||||||
|                         minTileHeight: 48, |                         minTileHeight: 48, | ||||||
|                         leading: ProfilePictureWidget( |                         leading: ProfilePictureWidget( | ||||||
|                           radius: 16, |                           radius: 16, | ||||||
|                           fileId: item.picture?.id, |                           fileId: item.publisher?.picture?.id, | ||||||
|                         ), |                         ), | ||||||
|                         title: Text(item.nick), |                         title: Text(item.publisher!.nick), | ||||||
|                         subtitle: Text('@${item.name}'), |                         subtitle: Text('@${item.publisher!.name}'), | ||||||
|                         trailing: |                         trailing: | ||||||
|                             currentDeveloper.value?.id == item.id |                             currentDeveloper.value?.id == item.id | ||||||
|                                 ? const Icon(Icons.check) |                                 ? const Icon(Icons.check) | ||||||
| @@ -107,7 +107,7 @@ class DeveloperHubScreen extends HookConsumerWidget { | |||||||
|     ); |     ); | ||||||
|  |  | ||||||
|     final developerStats = ref.watch( |     final developerStats = ref.watch( | ||||||
|       developerStatsProvider(currentDeveloper.value?.name), |       developerStatsProvider(currentDeveloper.value?.publisher?.name), | ||||||
|     ); |     ); | ||||||
|  |  | ||||||
|     return AppScaffold( |     return AppScaffold( | ||||||
| @@ -117,7 +117,7 @@ class DeveloperHubScreen extends HookConsumerWidget { | |||||||
|         title: Text('developerHub').tr(), |         title: Text('developerHub').tr(), | ||||||
|         actions: [ |         actions: [ | ||||||
|           DropdownButtonHideUnderline( |           DropdownButtonHideUnderline( | ||||||
|             child: DropdownButton2<SnPublisher>( |             child: DropdownButton2<SnDeveloper>( | ||||||
|               alignment: Alignment.centerRight, |               alignment: Alignment.centerRight, | ||||||
|               value: currentDeveloper.value, |               value: currentDeveloper.value, | ||||||
|               hint: CircleAvatar( |               hint: CircleAvatar( | ||||||
| @@ -139,7 +139,7 @@ class DeveloperHubScreen extends HookConsumerWidget { | |||||||
|                   ...developersMenu.map( |                   ...developersMenu.map( | ||||||
|                     (e) => ProfilePictureWidget( |                     (e) => ProfilePictureWidget( | ||||||
|                       radius: 16, |                       radius: 16, | ||||||
|                       fileId: e.value?.picture?.id, |                       fileId: e.value?.publisher?.picture?.id, | ||||||
|                     ).center().padding(right: 8), |                     ).center().padding(right: 8), | ||||||
|                   ), |                   ), | ||||||
|                 ]; |                 ]; | ||||||
| @@ -193,10 +193,12 @@ class DeveloperHubScreen extends HookConsumerWidget { | |||||||
|                           ...(developers.value?.map( |                           ...(developers.value?.map( | ||||||
|                                 (developer) => ListTile( |                                 (developer) => ListTile( | ||||||
|                                   leading: ProfilePictureWidget( |                                   leading: ProfilePictureWidget( | ||||||
|                                     file: developer.picture, |                                     file: developer.publisher?.picture, | ||||||
|  |                                   ), | ||||||
|  |                                   title: Text(developer.publisher!.nick), | ||||||
|  |                                   subtitle: Text( | ||||||
|  |                                     '@${developer.publisher!.name}', | ||||||
|                                   ), |                                   ), | ||||||
|                                   title: Text(developer.nick), |  | ||||||
|                                   subtitle: Text('@${developer.name}'), |  | ||||||
|                                   onTap: () { |                                   onTap: () { | ||||||
|                                     currentDeveloper.value = developer; |                                     currentDeveloper.value = developer; | ||||||
|                                   }, |                                   }, | ||||||
| @@ -243,7 +245,8 @@ class DeveloperHubScreen extends HookConsumerWidget { | |||||||
|                               context.pushNamed( |                               context.pushNamed( | ||||||
|                                 'developerApps', |                                 'developerApps', | ||||||
|                                 pathParameters: { |                                 pathParameters: { | ||||||
|                                   'name': currentDeveloper.value!.name, |                                   'name': | ||||||
|  |                                       currentDeveloper.value!.publisher!.name, | ||||||
|                                 }, |                                 }, | ||||||
|                               ); |                               ); | ||||||
|                             }, |                             }, | ||||||
| @@ -257,7 +260,9 @@ class DeveloperHubScreen extends HookConsumerWidget { | |||||||
|               error: err, |               error: err, | ||||||
|               onRetry: () { |               onRetry: () { | ||||||
|                 ref.invalidate( |                 ref.invalidate( | ||||||
|                   developerStatsProvider(currentDeveloper.value?.name), |                   developerStatsProvider( | ||||||
|  |                     currentDeveloper.value?.publisher!.name, | ||||||
|  |                   ), | ||||||
|                 ); |                 ); | ||||||
|               }, |               }, | ||||||
|             ), |             ), | ||||||
| @@ -336,7 +341,7 @@ class _DeveloperEnrollmentSheet extends HookConsumerWidget { | |||||||
|     Future<void> enroll(SnPublisher publisher) async { |     Future<void> enroll(SnPublisher publisher) async { | ||||||
|       try { |       try { | ||||||
|         final client = ref.read(apiClientProvider); |         final client = ref.read(apiClientProvider); | ||||||
|         await client.post('/sphere/developers/${publisher.name}/enroll'); |         await client.post('/develop/developers/${publisher.name}/enroll'); | ||||||
|         if (context.mounted) { |         if (context.mounted) { | ||||||
|           Navigator.pop(context, true); |           Navigator.pop(context, true); | ||||||
|         } |         } | ||||||
| @@ -354,7 +359,7 @@ class _DeveloperEnrollmentSheet extends HookConsumerWidget { | |||||||
|                     ? Center( |                     ? Center( | ||||||
|                       child: |                       child: | ||||||
|                           Text( |                           Text( | ||||||
|                             'noPublishersToEnroll', |                             'noDevelopersToEnroll', | ||||||
|                             textAlign: TextAlign.center, |                             textAlign: TextAlign.center, | ||||||
|                           ).tr(), |                           ).tr(), | ||||||
|                     ) |                     ) | ||||||
|   | |||||||
| @@ -6,7 +6,7 @@ part of 'hub.dart'; | |||||||
| // RiverpodGenerator | // RiverpodGenerator | ||||||
| // ************************************************************************** | // ************************************************************************** | ||||||
|  |  | ||||||
| String _$developerStatsHash() => r'baa708f3586e8987e221cc8ab825d759658c0f55'; | String _$developerStatsHash() => r'45546f29ec7cd1a9c3a4e0f4e39275e78bf34755'; | ||||||
|  |  | ||||||
| /// Copied from Dart SDK | /// Copied from Dart SDK | ||||||
| class _SystemHash { | class _SystemHash { | ||||||
| @@ -149,12 +149,12 @@ class _DeveloperStatsProviderElement | |||||||
|   String? get uname => (origin as DeveloperStatsProvider).uname; |   String? get uname => (origin as DeveloperStatsProvider).uname; | ||||||
| } | } | ||||||
|  |  | ||||||
| String _$developersHash() => r'f11335fdf553c661110281edeec70ef89c64727d'; | String _$developersHash() => r'252341098617ac398ce133994453f318dd3edbd2'; | ||||||
|  |  | ||||||
| /// See also [developers]. | /// See also [developers]. | ||||||
| @ProviderFor(developers) | @ProviderFor(developers) | ||||||
| final developersProvider = | final developersProvider = | ||||||
|     AutoDisposeFutureProvider<List<SnPublisher>>.internal( |     AutoDisposeFutureProvider<List<SnDeveloper>>.internal( | ||||||
|       developers, |       developers, | ||||||
|       name: r'developersProvider', |       name: r'developersProvider', | ||||||
|       debugGetCreateSourceHash: |       debugGetCreateSourceHash: | ||||||
| @@ -167,6 +167,6 @@ final developersProvider = | |||||||
|  |  | ||||||
| @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 DevelopersRef = AutoDisposeFutureProviderRef<List<SnPublisher>>; | typedef DevelopersRef = AutoDisposeFutureProviderRef<List<SnDeveloper>>; | ||||||
| // 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 | ||||||
|   | |||||||
| @@ -12,11 +12,11 @@ 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/services/responsive.dart'; | import 'package:island/services/responsive.dart'; | ||||||
| import 'package:island/widgets/account/event_calendar.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'; | ||||||
| import 'package:island/models/post.dart'; | import 'package:island/models/post.dart'; | ||||||
| import 'package:island/widgets/check_in.dart'; | import 'package:island/widgets/check_in.dart'; | ||||||
|  | import 'package:island/widgets/post/post_featured.dart'; | ||||||
| import 'package:island/widgets/post/post_item.dart'; | import 'package:island/widgets/post/post_item.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'; | ||||||
| @@ -70,15 +70,6 @@ class ExploreScreen extends HookConsumerWidget { | |||||||
|     final events = ref.watch(eventCalendarProvider(query.value)); |     final events = ref.watch(eventCalendarProvider(query.value)); | ||||||
|  |  | ||||||
|     final selectedDay = useState(now); |     final selectedDay = useState(now); | ||||||
|  |  | ||||||
|     void onMonthChanged(int year, int month) { |  | ||||||
|       query.value = EventCalendarQuery( |  | ||||||
|         uname: query.value.uname, |  | ||||||
|         year: year, |  | ||||||
|         month: month, |  | ||||||
|       ); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // Function to handle day selection for synchronizing between widgets |     // Function to handle day selection for synchronizing between widgets | ||||||
|     void onDaySelected(DateTime day) { |     void onDaySelected(DateTime day) { | ||||||
|       selectedDay.value = day; |       selectedDay.value = day; | ||||||
| @@ -218,21 +209,16 @@ class ExploreScreen extends HookConsumerWidget { | |||||||
|                               right: 12, |                               right: 12, | ||||||
|                               top: 16, |                               top: 16, | ||||||
|                             ), |                             ), | ||||||
|  |                             onChecked: () { | ||||||
|  |                               ref.invalidate( | ||||||
|  |                                 eventCalendarProvider(query.value), | ||||||
|  |                               ); | ||||||
|  |                             }, | ||||||
|                           ), |                           ), | ||||||
|                           Card( |                           PostFeaturedList().padding( | ||||||
|                             margin: EdgeInsets.only(left: 8, right: 12, top: 8), |                             left: 8, | ||||||
|                             child: Column( |                             right: 12, | ||||||
|                               children: [ |                             top: 8, | ||||||
|                                 // Use the reusable EventCalendarWidget |  | ||||||
|                                 EventCalendarWidget( |  | ||||||
|                                   events: events, |  | ||||||
|                                   initialDate: now, |  | ||||||
|                                   showEventDetails: true, |  | ||||||
|                                   onMonthChanged: onMonthChanged, |  | ||||||
|                                   onDaySelected: onDaySelected, |  | ||||||
|                                 ), |  | ||||||
|                               ], |  | ||||||
|                             ), |  | ||||||
|                           ), |                           ), | ||||||
|                           FortuneGraphWidget( |                           FortuneGraphWidget( | ||||||
|                             margin: EdgeInsets.only(left: 8, right: 12, top: 8), |                             margin: EdgeInsets.only(left: 8, right: 12, top: 8), | ||||||
| @@ -403,6 +389,10 @@ class _ActivityListView extends HookConsumerWidget { | |||||||
|               margin: EdgeInsets.only(left: 8, right: 8, bottom: 4), |               margin: EdgeInsets.only(left: 8, right: 8, bottom: 4), | ||||||
|             ), |             ), | ||||||
|           ), |           ), | ||||||
|  |         if (!contentOnly) | ||||||
|  |           SliverToBoxAdapter( | ||||||
|  |             child: PostFeaturedList().padding(horizontal: 8, bottom: 4, top: 4), | ||||||
|  |           ), | ||||||
|         SliverList.builder( |         SliverList.builder( | ||||||
|           itemCount: widgetCount, |           itemCount: widgetCount, | ||||||
|           itemBuilder: (context, index) { |           itemBuilder: (context, index) { | ||||||
|   | |||||||
| @@ -9,7 +9,9 @@ import 'package:gap/gap.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/models/poll.dart'; | import 'package:island/models/poll.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 | ||||||
| @@ -109,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, | ||||||
|                 ), |                 ), | ||||||
|               ] |               ] | ||||||
| @@ -190,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, | ||||||
|                   ), |                   ), | ||||||
|                 ]) |                 ]) | ||||||
| @@ -388,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); | ||||||
| @@ -413,13 +415,13 @@ class PollEditorScreen extends ConsumerWidget { | |||||||
|       }); |       }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     return Scaffold( |     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); | ||||||
|               }, |               }, | ||||||
| @@ -428,175 +430,175 @@ class PollEditorScreen extends ConsumerWidget { | |||||||
|           const Gap(8), |           const Gap(8), | ||||||
|         ], |         ], | ||||||
|       ), |       ), | ||||||
|       body: SafeArea( |       body: Column( | ||||||
|         child: Form( |         children: [ | ||||||
|           key: ValueKey(model.id), |           Expanded( | ||||||
|           child: ListView( |             child: Form( | ||||||
|             padding: const EdgeInsets.all(16), |               key: ValueKey(model.id), | ||||||
|             children: [ |               child: ListView( | ||||||
|               TextFormField( |                 padding: const EdgeInsets.all(16), | ||||||
|                 initialValue: model.title ?? '', |  | ||||||
|                 decoration: const InputDecoration( |  | ||||||
|                   labelText: 'Title', |  | ||||||
|                   border: OutlineInputBorder( |  | ||||||
|                     borderRadius: BorderRadius.all(Radius.circular(16)), |  | ||||||
|                   ), |  | ||||||
|                 ), |  | ||||||
|                 textInputAction: TextInputAction.next, |  | ||||||
|                 maxLength: 256, |  | ||||||
|                 onChanged: notifier.setTitle, |  | ||||||
|                 onTapOutside: |  | ||||||
|                     (_) => FocusManager.instance.primaryFocus?.unfocus(), |  | ||||||
|                 validator: (v) { |  | ||||||
|                   if (v == null || v.trim().isEmpty) { |  | ||||||
|                     return 'Title is required'; |  | ||||||
|                   } |  | ||||||
|                   return null; |  | ||||||
|                 }, |  | ||||||
|               ), |  | ||||||
|               const Gap(12), |  | ||||||
|               TextFormField( |  | ||||||
|                 initialValue: model.description ?? '', |  | ||||||
|                 decoration: const InputDecoration( |  | ||||||
|                   labelText: 'Description', |  | ||||||
|                   alignLabelWithHint: true, |  | ||||||
|                   border: OutlineInputBorder( |  | ||||||
|                     borderRadius: BorderRadius.all(Radius.circular(16)), |  | ||||||
|                   ), |  | ||||||
|                 ), |  | ||||||
|                 maxLines: 3, |  | ||||||
|                 maxLength: 4096, |  | ||||||
|                 onChanged: notifier.setDescription, |  | ||||||
|                 onTapOutside: |  | ||||||
|                     (_) => FocusManager.instance.primaryFocus?.unfocus(), |  | ||||||
|               ), |  | ||||||
|               const Gap(12), |  | ||||||
|               _EndDatePicker( |  | ||||||
|                 value: model.endedAt, |  | ||||||
|                 onChanged: notifier.setEndedAt, |  | ||||||
|               ), |  | ||||||
|               const Gap(24), |  | ||||||
|               Row( |  | ||||||
|                 children: [ |                 children: [ | ||||||
|                   Text( |                   TextFormField( | ||||||
|                     'Questions', |                     initialValue: model.title ?? '', | ||||||
|                     style: Theme.of(context).textTheme.titleLarge, |                     decoration: InputDecoration( | ||||||
|                   ), |                       labelText: 'title'.tr(), | ||||||
|                   const Spacer(), |                       border: OutlineInputBorder( | ||||||
|                   MenuAnchor( |                         borderRadius: BorderRadius.all(Radius.circular(16)), | ||||||
|                     builder: (context, controller, child) { |                       ), | ||||||
|                       return FilledButton.icon( |                     ), | ||||||
|                         onPressed: () { |                     textInputAction: TextInputAction.next, | ||||||
|                           controller.isOpen |                     maxLength: 256, | ||||||
|                               ? controller.close() |                     onChanged: notifier.setTitle, | ||||||
|                               : controller.open(); |                     onTapOutside: | ||||||
|                         }, |                         (_) => FocusManager.instance.primaryFocus?.unfocus(), | ||||||
|                         icon: const Icon(Icons.add), |                     validator: (v) { | ||||||
|                         label: const Text('Add question'), |                       if (v == null || v.trim().isEmpty) { | ||||||
|                       ); |                         return 'pollTitleRequired'.tr(); | ||||||
|  |                       } | ||||||
|  |                       return null; | ||||||
|                     }, |                     }, | ||||||
|                     menuChildren: |  | ||||||
|                         SnPollQuestionType.values |  | ||||||
|                             .map( |  | ||||||
|                               (t) => MenuItemButton( |  | ||||||
|                                 leadingIcon: Icon(_iconForType(t)), |  | ||||||
|                                 onPressed: () => notifier.addQuestion(t), |  | ||||||
|                                 child: Text(_labelForType(t)), |  | ||||||
|                               ), |  | ||||||
|                             ) |  | ||||||
|                             .toList(), |  | ||||||
|                   ), |                   ), | ||||||
|  |                   const Gap(12), | ||||||
|  |                   TextFormField( | ||||||
|  |                     initialValue: model.description ?? '', | ||||||
|  |                     decoration: InputDecoration( | ||||||
|  |                       labelText: 'description'.tr(), | ||||||
|  |                       alignLabelWithHint: true, | ||||||
|  |                       border: OutlineInputBorder( | ||||||
|  |                         borderRadius: BorderRadius.all(Radius.circular(16)), | ||||||
|  |                       ), | ||||||
|  |                     ), | ||||||
|  |                     maxLines: 3, | ||||||
|  |                     maxLength: 4096, | ||||||
|  |                     onChanged: notifier.setDescription, | ||||||
|  |                     onTapOutside: | ||||||
|  |                         (_) => FocusManager.instance.primaryFocus?.unfocus(), | ||||||
|  |                   ), | ||||||
|  |                   const Gap(12), | ||||||
|  |                   _EndDatePicker( | ||||||
|  |                     value: model.endedAt, | ||||||
|  |                     onChanged: notifier.setEndedAt, | ||||||
|  |                   ), | ||||||
|  |                   const Gap(24), | ||||||
|  |                   Row( | ||||||
|  |                     children: [ | ||||||
|  |                       Text( | ||||||
|  |                         'questions'.tr(), | ||||||
|  |                         style: Theme.of(context).textTheme.titleLarge, | ||||||
|  |                       ), | ||||||
|  |                       const Spacer(), | ||||||
|  |                       MenuAnchor( | ||||||
|  |                         builder: (context, controller, child) { | ||||||
|  |                           return FilledButton.icon( | ||||||
|  |                             onPressed: () { | ||||||
|  |                               controller.isOpen | ||||||
|  |                                   ? controller.close() | ||||||
|  |                                   : controller.open(); | ||||||
|  |                             }, | ||||||
|  |                             icon: const Icon(Icons.add), | ||||||
|  |                             label: Text('pollAddQuestion'.tr()), | ||||||
|  |                           ); | ||||||
|  |                         }, | ||||||
|  |                         menuChildren: | ||||||
|  |                             SnPollQuestionType.values | ||||||
|  |                                 .map( | ||||||
|  |                                   (t) => MenuItemButton( | ||||||
|  |                                     leadingIcon: Icon(_iconForType(t)), | ||||||
|  |                                     onPressed: () => notifier.addQuestion(t), | ||||||
|  |                                     child: Text(_labelForType(t)), | ||||||
|  |                                   ), | ||||||
|  |                                 ) | ||||||
|  |                                 .toList(), | ||||||
|  |                       ), | ||||||
|  |                     ], | ||||||
|  |                   ), | ||||||
|  |                   const Gap(8), | ||||||
|  |                   if (model.questions.isEmpty) | ||||||
|  |                     _EmptyState( | ||||||
|  |                       title: 'pollNoQuestionsYet'.tr(), | ||||||
|  |                       subtitle: | ||||||
|  |                           'pollNoQuestionsHint'.tr(), | ||||||
|  |                     ) | ||||||
|  |                   else | ||||||
|  |                     ReorderableListView.builder( | ||||||
|  |                       shrinkWrap: true, | ||||||
|  |                       physics: const NeverScrollableScrollPhysics(), | ||||||
|  |                       itemCount: model.questions.length, | ||||||
|  |                       onReorder: (oldIndex, newIndex) { | ||||||
|  |                         // Convert to stepwise moves using provided functions | ||||||
|  |                         if (newIndex > oldIndex) newIndex -= 1; | ||||||
|  |                         final steps = newIndex - oldIndex; | ||||||
|  |                         if (steps == 0) return; | ||||||
|  |                         if (steps > 0) { | ||||||
|  |                           for (int i = 0; i < steps; i++) { | ||||||
|  |                             notifier.moveQuestionDown(oldIndex + i); | ||||||
|  |                           } | ||||||
|  |                         } else { | ||||||
|  |                           for (int i = 0; i > steps; i--) { | ||||||
|  |                             notifier.moveQuestionUp(oldIndex + i); | ||||||
|  |                           } | ||||||
|  |                         } | ||||||
|  |                       }, | ||||||
|  |                       buildDefaultDragHandles: false, | ||||||
|  |                       itemBuilder: (context, index) { | ||||||
|  |                         final q = model.questions[index]; | ||||||
|  |                         return Card( | ||||||
|  |                           key: ValueKey('q_$index'), | ||||||
|  |                           margin: const EdgeInsets.symmetric(vertical: 8), | ||||||
|  |                           clipBehavior: Clip.antiAlias, | ||||||
|  |                           child: Column( | ||||||
|  |                             children: [ | ||||||
|  |                               _QuestionHeader( | ||||||
|  |                                 index: index, | ||||||
|  |                                 question: q, | ||||||
|  |                                 onMoveUp: | ||||||
|  |                                     index > 0 | ||||||
|  |                                         ? () => notifier.moveQuestionUp(index) | ||||||
|  |                                         : null, | ||||||
|  |                                 onMoveDown: | ||||||
|  |                                     index < model.questions.length - 1 | ||||||
|  |                                         ? () => notifier.moveQuestionDown(index) | ||||||
|  |                                         : null, | ||||||
|  |                                 onDelete: () => notifier.removeQuestion(index), | ||||||
|  |                               ), | ||||||
|  |                               const Divider(height: 1), | ||||||
|  |                               Padding( | ||||||
|  |                                 padding: const EdgeInsets.all(16), | ||||||
|  |                                 child: _QuestionEditor( | ||||||
|  |                                   index: index, | ||||||
|  |                                   question: q, | ||||||
|  |                                 ), | ||||||
|  |                               ), | ||||||
|  |                             ], | ||||||
|  |                           ), | ||||||
|  |                         ); | ||||||
|  |                       }, | ||||||
|  |                     ), | ||||||
|  |                   const Gap(96), | ||||||
|                 ], |                 ], | ||||||
|               ), |               ), | ||||||
|               const Gap(8), |             ), | ||||||
|               if (model.questions.isEmpty) |           ), | ||||||
|                 _EmptyState( |           Row( | ||||||
|                   title: 'No questions yet', |             children: [ | ||||||
|                   subtitle: 'Use "Add question" to start building your poll.', |               OutlinedButton.icon( | ||||||
|                 ) |                 onPressed: () { | ||||||
|               else |                   Navigator.of(context).maybePop(); | ||||||
|                 ReorderableListView.builder( |                 }, | ||||||
|                   shrinkWrap: true, |                 icon: const Icon(Icons.close), | ||||||
|                   physics: const NeverScrollableScrollPhysics(), |                 label: Text('cancel'.tr()), | ||||||
|                   itemCount: model.questions.length, |               ), | ||||||
|                   onReorder: (oldIndex, newIndex) { |               const Spacer(), | ||||||
|                     // Convert to stepwise moves using provided functions |               FilledButton.icon( | ||||||
|                     if (newIndex > oldIndex) newIndex -= 1; |                 onPressed: () { | ||||||
|                     final steps = newIndex - oldIndex; |                   _submitPoll(context, ref); | ||||||
|                     if (steps == 0) return; |                 }, | ||||||
|                     if (steps > 0) { |                 icon: const Icon(Icons.cloud_upload_outlined), | ||||||
|                       for (int i = 0; i < steps; i++) { |                 label: Text(model.id == null ? 'create'.tr() : 'update'.tr()), | ||||||
|                         notifier.moveQuestionDown(oldIndex + i); |               ), | ||||||
|                       } |  | ||||||
|                     } else { |  | ||||||
|                       for (int i = 0; i > steps; i--) { |  | ||||||
|                         notifier.moveQuestionUp(oldIndex + i); |  | ||||||
|                       } |  | ||||||
|                     } |  | ||||||
|                   }, |  | ||||||
|                   buildDefaultDragHandles: false, |  | ||||||
|                   itemBuilder: (context, index) { |  | ||||||
|                     final q = model.questions[index]; |  | ||||||
|                     return Card( |  | ||||||
|                       key: ValueKey('q_$index'), |  | ||||||
|                       margin: const EdgeInsets.symmetric(vertical: 8), |  | ||||||
|                       clipBehavior: Clip.antiAlias, |  | ||||||
|                       child: Column( |  | ||||||
|                         children: [ |  | ||||||
|                           _QuestionHeader( |  | ||||||
|                             index: index, |  | ||||||
|                             question: q, |  | ||||||
|                             onMoveUp: |  | ||||||
|                                 index > 0 |  | ||||||
|                                     ? () => notifier.moveQuestionUp(index) |  | ||||||
|                                     : null, |  | ||||||
|                             onMoveDown: |  | ||||||
|                                 index < model.questions.length - 1 |  | ||||||
|                                     ? () => notifier.moveQuestionDown(index) |  | ||||||
|                                     : null, |  | ||||||
|                             onDelete: () => notifier.removeQuestion(index), |  | ||||||
|                           ), |  | ||||||
|                           const Divider(height: 1), |  | ||||||
|                           Padding( |  | ||||||
|                             padding: const EdgeInsets.all(16), |  | ||||||
|                             child: _QuestionEditor(index: index, question: q), |  | ||||||
|                           ), |  | ||||||
|                         ], |  | ||||||
|                       ), |  | ||||||
|                     ); |  | ||||||
|                   }, |  | ||||||
|                 ), |  | ||||||
|               const Gap(96), |  | ||||||
|             ], |             ], | ||||||
|           ), |           ), | ||||||
|         ), |         ], | ||||||
|       ), |  | ||||||
|       bottomNavigationBar: Padding( |  | ||||||
|         padding: EdgeInsets.fromLTRB( |  | ||||||
|           16, |  | ||||||
|           8, |  | ||||||
|           16, |  | ||||||
|           16 + MediaQuery.of(context).padding.bottom, |  | ||||||
|         ), |  | ||||||
|         child: Row( |  | ||||||
|           children: [ |  | ||||||
|             OutlinedButton.icon( |  | ||||||
|               onPressed: () { |  | ||||||
|                 Navigator.of(context).maybePop(); |  | ||||||
|               }, |  | ||||||
|               icon: const Icon(Icons.close), |  | ||||||
|               label: const Text('Cancel'), |  | ||||||
|             ), |  | ||||||
|             const Spacer(), |  | ||||||
|             FilledButton.icon( |  | ||||||
|               onPressed: () { |  | ||||||
|                 _submitPoll(context, ref); |  | ||||||
|               }, |  | ||||||
|               icon: const Icon(Icons.cloud_upload_outlined), |  | ||||||
|               label: Text(model.id == null ? 'Create' : 'Update'), |  | ||||||
|             ), |  | ||||||
|           ], |  | ||||||
|         ), |  | ||||||
|       ), |       ), | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| @@ -636,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()), | ||||||
|               ), |               ), | ||||||
|             ], |             ], | ||||||
|           ), |           ), | ||||||
| @@ -672,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(); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -697,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)), | ||||||
|               ), |               ), | ||||||
| @@ -710,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!), | ||||||
| @@ -758,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()), | ||||||
|                   ), |                   ), | ||||||
|               ], |               ], | ||||||
|             ), |             ), | ||||||
| @@ -798,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, | ||||||
|       ), |       ), | ||||||
| @@ -807,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, | ||||||
| @@ -852,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( | ||||||
| @@ -866,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)), | ||||||
|             ), |             ), | ||||||
| @@ -878,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; | ||||||
|           }, |           }, | ||||||
| @@ -886,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)), | ||||||
|             ), |             ), | ||||||
| @@ -901,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), | ||||||
| @@ -910,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()), | ||||||
|             ), |             ), | ||||||
|           ), |           ), | ||||||
|         ], |         ], | ||||||
| @@ -936,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)), | ||||||
|         ), |         ), | ||||||
| @@ -986,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)), | ||||||
|                       ), |                       ), | ||||||
| @@ -1002,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), | ||||||
| @@ -1011,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) | ||||||
| @@ -1022,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), | ||||||
|                   ), |                   ), | ||||||
| @@ -1047,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)), | ||||||
|         ), |         ), | ||||||
| @@ -1081,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), | ||||||
|               ], |               ], | ||||||
|             ), |             ), | ||||||
|           ), |           ), | ||||||
|   | |||||||
| @@ -205,17 +205,7 @@ class PostComposeScreen extends HookConsumerWidget { | |||||||
|       showModalBottomSheet( |       showModalBottomSheet( | ||||||
|         context: context, |         context: context, | ||||||
|         isScrollControlled: true, |         isScrollControlled: true, | ||||||
|         builder: |         builder: (context) => ComposeSettingsSheet(state: state), | ||||||
|             (context) => ComposeSettingsSheet( |  | ||||||
|               titleController: state.titleController, |  | ||||||
|               descriptionController: state.descriptionController, |  | ||||||
|               visibility: state.visibility, |  | ||||||
|               tagsController: state.tagsController, |  | ||||||
|               categoriesController: state.categoriesController, |  | ||||||
|               onVisibilityChanged: () { |  | ||||||
|                 // Trigger rebuild if needed |  | ||||||
|               }, |  | ||||||
|             ), |  | ||||||
|       ); |       ); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -369,31 +359,73 @@ class PostComposeScreen extends HookConsumerWidget { | |||||||
|  |  | ||||||
|                     // Post content form |                     // Post content form | ||||||
|                     Expanded( |                     Expanded( | ||||||
|                       child: SingleChildScrollView( |                       child: KeyboardListener( | ||||||
|                         padding: const EdgeInsets.symmetric(vertical: 12), |                         focusNode: FocusNode(), | ||||||
|                         child: Column( |                         onKeyEvent: | ||||||
|                           crossAxisAlignment: CrossAxisAlignment.start, |                             (event) => ComposeLogic.handleKeyPress( | ||||||
|                           children: [ |                               event, | ||||||
|                             // Content field with borderless design |                               state, | ||||||
|                             RawKeyboardListener( |                               ref, | ||||||
|                               focusNode: FocusNode(), |                               context, | ||||||
|                               onKey: |                               originalPost: originalPost, | ||||||
|                                   (event) => ComposeLogic.handleKeyPress( |                               repliedPost: repliedPost, | ||||||
|                                     event, |                               forwardedPost: forwardedPost, | ||||||
|                                     state, |                             ), | ||||||
|                                     ref, |                         child: SingleChildScrollView( | ||||||
|                                     context, |                           padding: const EdgeInsets.symmetric(vertical: 16), | ||||||
|                                     originalPost: originalPost, |                           child: Column( | ||||||
|                                     repliedPost: repliedPost, |                             crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|                                     forwardedPost: forwardedPost, |                             children: [ | ||||||
|  |                               TextField( | ||||||
|  |                                 controller: state.titleController, | ||||||
|  |                                 decoration: InputDecoration( | ||||||
|  |                                   hintText: 'postTitle'.tr(), | ||||||
|  |                                   border: InputBorder.none, | ||||||
|  |                                   isCollapsed: true, | ||||||
|  |                                   contentPadding: const EdgeInsets.symmetric( | ||||||
|  |                                     vertical: 8, | ||||||
|  |                                     horizontal: 8, | ||||||
|                                   ), |                                   ), | ||||||
|                               child: TextField( |                                 ), | ||||||
|  |                                 style: theme.textTheme.titleMedium, | ||||||
|  |                                 onTapOutside: | ||||||
|  |                                     (_) => | ||||||
|  |                                         FocusManager.instance.primaryFocus | ||||||
|  |                                             ?.unfocus(), | ||||||
|  |                               ), | ||||||
|  |                               TextField( | ||||||
|  |                                 controller: state.descriptionController, | ||||||
|  |                                 decoration: InputDecoration( | ||||||
|  |                                   hintText: 'postDescription'.tr(), | ||||||
|  |                                   border: InputBorder.none, | ||||||
|  |                                   isCollapsed: true, | ||||||
|  |                                   contentPadding: const EdgeInsets.fromLTRB( | ||||||
|  |                                     8, | ||||||
|  |                                     4, | ||||||
|  |                                     8, | ||||||
|  |                                     12, | ||||||
|  |                                   ), | ||||||
|  |                                 ), | ||||||
|  |                                 style: theme.textTheme.bodyMedium, | ||||||
|  |                                 minLines: 1, | ||||||
|  |                                 maxLines: 3, | ||||||
|  |                                 onTapOutside: | ||||||
|  |                                     (_) => | ||||||
|  |                                         FocusManager.instance.primaryFocus | ||||||
|  |                                             ?.unfocus(), | ||||||
|  |                               ), | ||||||
|  |                               // Content field with borderless design | ||||||
|  |                               TextField( | ||||||
|                                 controller: state.contentController, |                                 controller: state.contentController, | ||||||
|                                 style: theme.textTheme.bodyMedium, |                                 style: theme.textTheme.bodyMedium, | ||||||
|                                 decoration: InputDecoration( |                                 decoration: InputDecoration( | ||||||
|                                   border: InputBorder.none, |                                   border: InputBorder.none, | ||||||
|                                   hintText: 'postContent'.tr(), |                                   hintText: 'postContent'.tr(), | ||||||
|                                   contentPadding: const EdgeInsets.all(8), |                                   isCollapsed: true, | ||||||
|  |                                   contentPadding: const EdgeInsets.symmetric( | ||||||
|  |                                     vertical: 8, | ||||||
|  |                                     horizontal: 8, | ||||||
|  |                                   ), | ||||||
|                                 ), |                                 ), | ||||||
|                                 maxLines: null, |                                 maxLines: null, | ||||||
|                                 onTapOutside: |                                 onTapOutside: | ||||||
| @@ -401,23 +433,23 @@ class PostComposeScreen extends HookConsumerWidget { | |||||||
|                                         FocusManager.instance.primaryFocus |                                         FocusManager.instance.primaryFocus | ||||||
|                                             ?.unfocus(), |                                             ?.unfocus(), | ||||||
|                               ), |                               ), | ||||||
|                             ), |  | ||||||
|  |  | ||||||
|                             const Gap(8), |                               const Gap(8), | ||||||
|  |  | ||||||
|                             // Attachments preview |                               // Attachments preview | ||||||
|                             if (state.attachments.value.isNotEmpty) |                               if (state.attachments.value.isNotEmpty) | ||||||
|                               LayoutBuilder( |                                 LayoutBuilder( | ||||||
|                                 builder: (context, constraints) { |                                   builder: (context, constraints) { | ||||||
|                                   final isWide = isWideScreen(context); |                                     final isWide = isWideScreen(context); | ||||||
|                                   return isWide |                                     return isWide | ||||||
|                                       ? buildWideAttachmentGrid() |                                         ? buildWideAttachmentGrid() | ||||||
|                                       : buildNarrowAttachmentList(); |                                         : buildNarrowAttachmentList(); | ||||||
|                                 }, |                                   }, | ||||||
|                               ) |                                 ) | ||||||
|                             else |                               else | ||||||
|                               const SizedBox.shrink(), |                                 const SizedBox.shrink(), | ||||||
|                           ], |                             ], | ||||||
|  |                           ), | ||||||
|                         ), |                         ), | ||||||
|                       ), |                       ), | ||||||
|                     ), |                     ), | ||||||
|   | |||||||
| @@ -138,17 +138,7 @@ class ArticleComposeScreen extends HookConsumerWidget { | |||||||
|       showModalBottomSheet( |       showModalBottomSheet( | ||||||
|         context: context, |         context: context, | ||||||
|         isScrollControlled: true, |         isScrollControlled: true, | ||||||
|         builder: |         builder: (context) => ComposeSettingsSheet(state: state), | ||||||
|             (context) => ComposeSettingsSheet( |  | ||||||
|               titleController: state.titleController, |  | ||||||
|               descriptionController: state.descriptionController, |  | ||||||
|               visibility: state.visibility, |  | ||||||
|               tagsController: state.tagsController, |  | ||||||
|               categoriesController: state.categoriesController, |  | ||||||
|               onVisibilityChanged: () { |  | ||||||
|                 // Trigger rebuild if needed |  | ||||||
|               }, |  | ||||||
|             ), |  | ||||||
|       ); |       ); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -242,10 +232,39 @@ class ArticleComposeScreen extends HookConsumerWidget { | |||||||
|           child: Column( |           child: Column( | ||||||
|             crossAxisAlignment: CrossAxisAlignment.start, |             crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|             children: [ |             children: [ | ||||||
|  |               TextField( | ||||||
|  |                 controller: state.titleController, | ||||||
|  |                 decoration: InputDecoration( | ||||||
|  |                   hintText: 'postTitle'.tr(), | ||||||
|  |                   border: InputBorder.none, | ||||||
|  |                   isCollapsed: true, | ||||||
|  |                   contentPadding: const EdgeInsets.symmetric( | ||||||
|  |                     vertical: 8, | ||||||
|  |                     horizontal: 8, | ||||||
|  |                   ), | ||||||
|  |                 ), | ||||||
|  |                 style: theme.textTheme.titleMedium, | ||||||
|  |                 onTapOutside: | ||||||
|  |                     (_) => FocusManager.instance.primaryFocus?.unfocus(), | ||||||
|  |               ), | ||||||
|  |               TextField( | ||||||
|  |                 controller: state.descriptionController, | ||||||
|  |                 decoration: InputDecoration( | ||||||
|  |                   hintText: 'postDescription'.tr(), | ||||||
|  |                   border: InputBorder.none, | ||||||
|  |                   isCollapsed: true, | ||||||
|  |                   contentPadding: const EdgeInsets.fromLTRB(8, 4, 8, 12), | ||||||
|  |                 ), | ||||||
|  |                 style: theme.textTheme.bodyMedium, | ||||||
|  |                 minLines: 1, | ||||||
|  |                 maxLines: 3, | ||||||
|  |                 onTapOutside: | ||||||
|  |                     (_) => FocusManager.instance.primaryFocus?.unfocus(), | ||||||
|  |               ), | ||||||
|               Expanded( |               Expanded( | ||||||
|                 child: RawKeyboardListener( |                 child: KeyboardListener( | ||||||
|                   focusNode: FocusNode(), |                   focusNode: FocusNode(), | ||||||
|                   onKey: |                   onKeyEvent: | ||||||
|                       (event) => _handleKeyPress( |                       (event) => _handleKeyPress( | ||||||
|                         event, |                         event, | ||||||
|                         state, |                         state, | ||||||
| @@ -454,7 +473,7 @@ class ArticleComposeScreen extends HookConsumerWidget { | |||||||
|                               flex: showPreview.value ? 1 : 2, |                               flex: showPreview.value ? 1 : 2, | ||||||
|                               child: buildEditorPane(), |                               child: buildEditorPane(), | ||||||
|                             ), |                             ), | ||||||
|                             const VerticalDivider(), |                             if (showPreview.value) const VerticalDivider(), | ||||||
|                             if (showPreview.value) |                             if (showPreview.value) | ||||||
|                               Expanded(child: buildPreviewPane()), |                               Expanded(child: buildPreviewPane()), | ||||||
|                           ], |                           ], | ||||||
| @@ -475,7 +494,7 @@ class ArticleComposeScreen extends HookConsumerWidget { | |||||||
|  |  | ||||||
|   // Helper method to handle keyboard shortcuts |   // Helper method to handle keyboard shortcuts | ||||||
|   void _handleKeyPress( |   void _handleKeyPress( | ||||||
|     RawKeyEvent event, |     KeyEvent event, | ||||||
|     ComposeState state, |     ComposeState state, | ||||||
|     WidgetRef ref, |     WidgetRef ref, | ||||||
|     BuildContext context, { |     BuildContext context, { | ||||||
| @@ -485,7 +504,9 @@ class ArticleComposeScreen extends HookConsumerWidget { | |||||||
|  |  | ||||||
|     final isPaste = event.logicalKey == LogicalKeyboardKey.keyV; |     final isPaste = event.logicalKey == LogicalKeyboardKey.keyV; | ||||||
|     final isSave = event.logicalKey == LogicalKeyboardKey.keyS; |     final isSave = event.logicalKey == LogicalKeyboardKey.keyS; | ||||||
|     final isModifierPressed = event.isMetaPressed || event.isControlPressed; |     final isModifierPressed = | ||||||
|  |         HardwareKeyboard.instance.isMetaPressed || | ||||||
|  |         HardwareKeyboard.instance.isControlPressed; | ||||||
|     final isSubmit = event.logicalKey == LogicalKeyboardKey.enter; |     final isSubmit = event.logicalKey == LogicalKeyboardKey.enter; | ||||||
|  |  | ||||||
|     if (isPaste && isModifierPressed) { |     if (isPaste && isModifierPressed) { | ||||||
|   | |||||||
							
								
								
									
										107
									
								
								lib/screens/posts/post_category_detail.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								lib/screens/posts/post_category_detail.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,107 @@ | |||||||
|  | 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/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/post/post_list.dart'; | ||||||
|  | import 'package:island/widgets/response.dart'; | ||||||
|  | import 'package:riverpod_annotation/riverpod_annotation.dart'; | ||||||
|  | import 'package:styled_widget/styled_widget.dart'; | ||||||
|  |  | ||||||
|  | part 'post_category_detail.g.dart'; | ||||||
|  |  | ||||||
|  | @riverpod | ||||||
|  | Future<SnPostCategory> postCategory(Ref ref, String slug) async { | ||||||
|  |   final apiClient = ref.watch(apiClientProvider); | ||||||
|  |   final resp = await apiClient.get('/sphere/posts/categories/$slug'); | ||||||
|  |   return SnPostCategory.fromJson(resp.data); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @riverpod | ||||||
|  | Future<SnPostTag> postTag(Ref ref, String slug) async { | ||||||
|  |   final apiClient = ref.watch(apiClientProvider); | ||||||
|  |   final resp = await apiClient.get('/sphere/posts/tags/$slug'); | ||||||
|  |   return SnPostTag.fromJson(resp.data); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class PostCategoryDetailScreen extends HookConsumerWidget { | ||||||
|  |   final String slug; | ||||||
|  |   final bool isCategory; | ||||||
|  |   const PostCategoryDetailScreen({ | ||||||
|  |     super.key, | ||||||
|  |     required this.slug, | ||||||
|  |     required this.isCategory, | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|  |     final postCategory = | ||||||
|  |         isCategory ? ref.watch(postCategoryProvider(slug)) : null; | ||||||
|  |     final postTag = isCategory ? null : ref.watch(postTagProvider(slug)); | ||||||
|  |  | ||||||
|  |     final postFilterTitle = | ||||||
|  |         isCategory | ||||||
|  |             ? postCategory?.value?.categoryDisplayTitle ?? 'loading' | ||||||
|  |             : postTag?.value?.name ?? postTag?.value?.slug ?? 'loading'; | ||||||
|  |  | ||||||
|  |     return AppScaffold( | ||||||
|  |       isNoBackground: false, | ||||||
|  |       appBar: AppBar(title: Text(postFilterTitle).tr()), | ||||||
|  |       body: Column( | ||||||
|  |         crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|  |         children: [ | ||||||
|  |           if (isCategory) | ||||||
|  |             postCategory!.when( | ||||||
|  |               data: | ||||||
|  |                   (category) => Column( | ||||||
|  |                     crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|  |                     children: [ | ||||||
|  |                       Text(category.categoryDisplayTitle).bold().fontSize(15), | ||||||
|  |                       Text('A category'), | ||||||
|  |                     ], | ||||||
|  |                   ).padding(horizontal: 24, vertical: 16), | ||||||
|  |               error: | ||||||
|  |                   (error, _) => ResponseErrorWidget( | ||||||
|  |                     error: error, | ||||||
|  |                     onRetry: () => ref.invalidate(postCategoryProvider(slug)), | ||||||
|  |                   ), | ||||||
|  |               loading: () => ResponseLoadingWidget(), | ||||||
|  |             ) | ||||||
|  |           else | ||||||
|  |             postTag!.when( | ||||||
|  |               data: | ||||||
|  |                   (tag) => Column( | ||||||
|  |                     crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|  |                     children: [ | ||||||
|  |                       Text(tag.name ?? '#${tag.slug}').bold().fontSize(15), | ||||||
|  |                       Text('A tag'), | ||||||
|  |                     ], | ||||||
|  |                   ).padding(horizontal: 24, vertical: 16), | ||||||
|  |               error: | ||||||
|  |                   (error, _) => ResponseErrorWidget( | ||||||
|  |                     error: error, | ||||||
|  |                     onRetry: () => ref.invalidate(postTagProvider(slug)), | ||||||
|  |                   ), | ||||||
|  |               loading: () => ResponseLoadingWidget(), | ||||||
|  |             ), | ||||||
|  |           const Divider(height: 1), | ||||||
|  |           Expanded( | ||||||
|  |             child: CustomScrollView( | ||||||
|  |               slivers: [ | ||||||
|  |                 const SliverGap(4), | ||||||
|  |                 SliverPostList( | ||||||
|  |                   categories: isCategory ? [slug] : null, | ||||||
|  |                   tags: isCategory ? null : [slug], | ||||||
|  |                 ), | ||||||
|  |                 SliverGap(MediaQuery.of(context).padding.bottom + 8), | ||||||
|  |               ], | ||||||
|  |             ), | ||||||
|  |           ), | ||||||
|  |         ], | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										270
									
								
								lib/screens/posts/post_category_detail.g.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										270
									
								
								lib/screens/posts/post_category_detail.g.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,270 @@ | |||||||
|  | // GENERATED CODE - DO NOT MODIFY BY HAND | ||||||
|  |  | ||||||
|  | part of 'post_category_detail.dart'; | ||||||
|  |  | ||||||
|  | // ************************************************************************** | ||||||
|  | // RiverpodGenerator | ||||||
|  | // ************************************************************************** | ||||||
|  |  | ||||||
|  | String _$postCategoryHash() => r'0df2de729ba96819ee37377314615abef0c99547'; | ||||||
|  |  | ||||||
|  | /// Copied from Dart SDK | ||||||
|  | class _SystemHash { | ||||||
|  |   _SystemHash._(); | ||||||
|  |  | ||||||
|  |   static int combine(int hash, int value) { | ||||||
|  |     // ignore: parameter_assignments | ||||||
|  |     hash = 0x1fffffff & (hash + value); | ||||||
|  |     // ignore: parameter_assignments | ||||||
|  |     hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); | ||||||
|  |     return hash ^ (hash >> 6); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   static int finish(int hash) { | ||||||
|  |     // ignore: parameter_assignments | ||||||
|  |     hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); | ||||||
|  |     // ignore: parameter_assignments | ||||||
|  |     hash = hash ^ (hash >> 11); | ||||||
|  |     return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// See also [postCategory]. | ||||||
|  | @ProviderFor(postCategory) | ||||||
|  | const postCategoryProvider = PostCategoryFamily(); | ||||||
|  |  | ||||||
|  | /// See also [postCategory]. | ||||||
|  | class PostCategoryFamily extends Family<AsyncValue<SnPostCategory>> { | ||||||
|  |   /// See also [postCategory]. | ||||||
|  |   const PostCategoryFamily(); | ||||||
|  |  | ||||||
|  |   /// See also [postCategory]. | ||||||
|  |   PostCategoryProvider call(String slug) { | ||||||
|  |     return PostCategoryProvider(slug); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   PostCategoryProvider getProviderOverride( | ||||||
|  |     covariant PostCategoryProvider provider, | ||||||
|  |   ) { | ||||||
|  |     return call(provider.slug); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   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'postCategoryProvider'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// See also [postCategory]. | ||||||
|  | class PostCategoryProvider extends AutoDisposeFutureProvider<SnPostCategory> { | ||||||
|  |   /// See also [postCategory]. | ||||||
|  |   PostCategoryProvider(String slug) | ||||||
|  |     : this._internal( | ||||||
|  |         (ref) => postCategory(ref as PostCategoryRef, slug), | ||||||
|  |         from: postCategoryProvider, | ||||||
|  |         name: r'postCategoryProvider', | ||||||
|  |         debugGetCreateSourceHash: | ||||||
|  |             const bool.fromEnvironment('dart.vm.product') | ||||||
|  |                 ? null | ||||||
|  |                 : _$postCategoryHash, | ||||||
|  |         dependencies: PostCategoryFamily._dependencies, | ||||||
|  |         allTransitiveDependencies: | ||||||
|  |             PostCategoryFamily._allTransitiveDependencies, | ||||||
|  |         slug: slug, | ||||||
|  |       ); | ||||||
|  |  | ||||||
|  |   PostCategoryProvider._internal( | ||||||
|  |     super._createNotifier, { | ||||||
|  |     required super.name, | ||||||
|  |     required super.dependencies, | ||||||
|  |     required super.allTransitiveDependencies, | ||||||
|  |     required super.debugGetCreateSourceHash, | ||||||
|  |     required super.from, | ||||||
|  |     required this.slug, | ||||||
|  |   }) : super.internal(); | ||||||
|  |  | ||||||
|  |   final String slug; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Override overrideWith( | ||||||
|  |     FutureOr<SnPostCategory> Function(PostCategoryRef provider) create, | ||||||
|  |   ) { | ||||||
|  |     return ProviderOverride( | ||||||
|  |       origin: this, | ||||||
|  |       override: PostCategoryProvider._internal( | ||||||
|  |         (ref) => create(ref as PostCategoryRef), | ||||||
|  |         from: from, | ||||||
|  |         name: null, | ||||||
|  |         dependencies: null, | ||||||
|  |         allTransitiveDependencies: null, | ||||||
|  |         debugGetCreateSourceHash: null, | ||||||
|  |         slug: slug, | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   AutoDisposeFutureProviderElement<SnPostCategory> createElement() { | ||||||
|  |     return _PostCategoryProviderElement(this); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   bool operator ==(Object other) { | ||||||
|  |     return other is PostCategoryProvider && other.slug == slug; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   int get hashCode { | ||||||
|  |     var hash = _SystemHash.combine(0, runtimeType.hashCode); | ||||||
|  |     hash = _SystemHash.combine(hash, slug.hashCode); | ||||||
|  |  | ||||||
|  |     return _SystemHash.finish(hash); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @Deprecated('Will be removed in 3.0. Use Ref instead') | ||||||
|  | // ignore: unused_element | ||||||
|  | mixin PostCategoryRef on AutoDisposeFutureProviderRef<SnPostCategory> { | ||||||
|  |   /// The parameter `slug` of this provider. | ||||||
|  |   String get slug; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class _PostCategoryProviderElement | ||||||
|  |     extends AutoDisposeFutureProviderElement<SnPostCategory> | ||||||
|  |     with PostCategoryRef { | ||||||
|  |   _PostCategoryProviderElement(super.provider); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   String get slug => (origin as PostCategoryProvider).slug; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | String _$postTagHash() => r'e050fdf9af81a843a9abd9cf979dd2672e0a2b93'; | ||||||
|  |  | ||||||
|  | /// See also [postTag]. | ||||||
|  | @ProviderFor(postTag) | ||||||
|  | const postTagProvider = PostTagFamily(); | ||||||
|  |  | ||||||
|  | /// See also [postTag]. | ||||||
|  | class PostTagFamily extends Family<AsyncValue<SnPostTag>> { | ||||||
|  |   /// See also [postTag]. | ||||||
|  |   const PostTagFamily(); | ||||||
|  |  | ||||||
|  |   /// See also [postTag]. | ||||||
|  |   PostTagProvider call(String slug) { | ||||||
|  |     return PostTagProvider(slug); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   PostTagProvider getProviderOverride(covariant PostTagProvider provider) { | ||||||
|  |     return call(provider.slug); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   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'postTagProvider'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// See also [postTag]. | ||||||
|  | class PostTagProvider extends AutoDisposeFutureProvider<SnPostTag> { | ||||||
|  |   /// See also [postTag]. | ||||||
|  |   PostTagProvider(String slug) | ||||||
|  |     : this._internal( | ||||||
|  |         (ref) => postTag(ref as PostTagRef, slug), | ||||||
|  |         from: postTagProvider, | ||||||
|  |         name: r'postTagProvider', | ||||||
|  |         debugGetCreateSourceHash: | ||||||
|  |             const bool.fromEnvironment('dart.vm.product') | ||||||
|  |                 ? null | ||||||
|  |                 : _$postTagHash, | ||||||
|  |         dependencies: PostTagFamily._dependencies, | ||||||
|  |         allTransitiveDependencies: PostTagFamily._allTransitiveDependencies, | ||||||
|  |         slug: slug, | ||||||
|  |       ); | ||||||
|  |  | ||||||
|  |   PostTagProvider._internal( | ||||||
|  |     super._createNotifier, { | ||||||
|  |     required super.name, | ||||||
|  |     required super.dependencies, | ||||||
|  |     required super.allTransitiveDependencies, | ||||||
|  |     required super.debugGetCreateSourceHash, | ||||||
|  |     required super.from, | ||||||
|  |     required this.slug, | ||||||
|  |   }) : super.internal(); | ||||||
|  |  | ||||||
|  |   final String slug; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Override overrideWith( | ||||||
|  |     FutureOr<SnPostTag> Function(PostTagRef provider) create, | ||||||
|  |   ) { | ||||||
|  |     return ProviderOverride( | ||||||
|  |       origin: this, | ||||||
|  |       override: PostTagProvider._internal( | ||||||
|  |         (ref) => create(ref as PostTagRef), | ||||||
|  |         from: from, | ||||||
|  |         name: null, | ||||||
|  |         dependencies: null, | ||||||
|  |         allTransitiveDependencies: null, | ||||||
|  |         debugGetCreateSourceHash: null, | ||||||
|  |         slug: slug, | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   AutoDisposeFutureProviderElement<SnPostTag> createElement() { | ||||||
|  |     return _PostTagProviderElement(this); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   bool operator ==(Object other) { | ||||||
|  |     return other is PostTagProvider && other.slug == slug; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   int get hashCode { | ||||||
|  |     var hash = _SystemHash.combine(0, runtimeType.hashCode); | ||||||
|  |     hash = _SystemHash.combine(hash, slug.hashCode); | ||||||
|  |  | ||||||
|  |     return _SystemHash.finish(hash); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @Deprecated('Will be removed in 3.0. Use Ref instead') | ||||||
|  | // ignore: unused_element | ||||||
|  | mixin PostTagRef on AutoDisposeFutureProviderRef<SnPostTag> { | ||||||
|  |   /// The parameter `slug` of this provider. | ||||||
|  |   String get slug; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class _PostTagProviderElement | ||||||
|  |     extends AutoDisposeFutureProviderElement<SnPostTag> | ||||||
|  |     with PostTagRef { | ||||||
|  |   _PostTagProviderElement(super.provider); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   String get slug => (origin as PostTagProvider).slug; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 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 | ||||||
| @@ -92,6 +92,7 @@ class PostDetailScreen extends HookConsumerWidget { | |||||||
|                   right: 0, |                   right: 0, | ||||||
|                   child: Material( |                   child: Material( | ||||||
|                     elevation: 2, |                     elevation: 2, | ||||||
|  |                     color: Theme.of(context).colorScheme.surfaceContainer, | ||||||
|                     child: postState |                     child: postState | ||||||
|                         .when( |                         .when( | ||||||
|                           data: |                           data: | ||||||
| @@ -107,8 +108,8 @@ class PostDetailScreen extends HookConsumerWidget { | |||||||
|                           error: (_, _) => const SizedBox.shrink(), |                           error: (_, _) => const SizedBox.shrink(), | ||||||
|                         ) |                         ) | ||||||
|                         .padding( |                         .padding( | ||||||
|                           bottom: MediaQuery.of(context).padding.bottom + 16, |                           bottom: MediaQuery.of(context).padding.bottom + 8, | ||||||
|                           top: 16, |                           top: 8, | ||||||
|                           horizontal: 16, |                           horizontal: 16, | ||||||
|                         ), |                         ), | ||||||
|                   ), |                   ), | ||||||
|   | |||||||
| @@ -87,13 +87,22 @@ class PublisherProfileScreen extends HookConsumerWidget { | |||||||
|       publisherAppbarForcegroundColorProvider(name), |       publisherAppbarForcegroundColorProvider(name), | ||||||
|     ); |     ); | ||||||
|  |  | ||||||
|  |     final categoryTabController = useTabController(initialLength: 3); | ||||||
|  |     final categoryTab = useState(0); | ||||||
|  |     categoryTabController.addListener(() { | ||||||
|  |       categoryTab.value = categoryTabController.index; | ||||||
|  |     }); | ||||||
|  |  | ||||||
|     final subscribing = useState(false); |     final subscribing = useState(false); | ||||||
|  |  | ||||||
|     Future<void> subscribe() async { |     Future<void> subscribe() async { | ||||||
|       final apiClient = ref.watch(apiClientProvider); |       final apiClient = ref.watch(apiClientProvider); | ||||||
|       subscribing.value = true; |       subscribing.value = true; | ||||||
|       try { |       try { | ||||||
|         await apiClient.post("/publishers/$name/subscribe", data: {'tier': 0}); |         await apiClient.post( | ||||||
|  |           "/sphere/publishers/$name/subscribe", | ||||||
|  |           data: {'tier': 0}, | ||||||
|  |         ); | ||||||
|         ref.invalidate(publisherSubscriptionStatusProvider(name)); |         ref.invalidate(publisherSubscriptionStatusProvider(name)); | ||||||
|         HapticFeedback.heavyImpact(); |         HapticFeedback.heavyImpact(); | ||||||
|       } catch (err) { |       } catch (err) { | ||||||
| @@ -107,7 +116,7 @@ class PublisherProfileScreen extends HookConsumerWidget { | |||||||
|       final apiClient = ref.watch(apiClientProvider); |       final apiClient = ref.watch(apiClientProvider); | ||||||
|       subscribing.value = true; |       subscribing.value = true; | ||||||
|       try { |       try { | ||||||
|         await apiClient.post("/publishers/$name/unsubscribe"); |         await apiClient.post("/sphere/publishers/$name/unsubscribe"); | ||||||
|         ref.invalidate(publisherSubscriptionStatusProvider(name)); |         ref.invalidate(publisherSubscriptionStatusProvider(name)); | ||||||
|         HapticFeedback.heavyImpact(); |         HapticFeedback.heavyImpact(); | ||||||
|       } catch (err) { |       } catch (err) { | ||||||
| @@ -268,6 +277,16 @@ class PublisherProfileScreen extends HookConsumerWidget { | |||||||
|       ).padding(horizontal: 20, vertical: 16), |       ).padding(horizontal: 20, vertical: 16), | ||||||
|     ); |     ); | ||||||
|  |  | ||||||
|  |     Widget publisherCategoryTabWidget() => Card( | ||||||
|  |       margin: EdgeInsets.symmetric(horizontal: 8, vertical: 4), | ||||||
|  |       child: TabBar( | ||||||
|  |         controller: categoryTabController, | ||||||
|  |         dividerColor: Colors.transparent, | ||||||
|  |         splashBorderRadius: const BorderRadius.all(Radius.circular(8)), | ||||||
|  |         tabs: [Tab(text: 'All'), Tab(text: 'Posts'), Tab(text: 'Articles')], | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |  | ||||||
|     return publisher.when( |     return publisher.when( | ||||||
|       data: |       data: | ||||||
|           (data) => AppScaffold( |           (data) => AppScaffold( | ||||||
| @@ -321,7 +340,18 @@ class PublisherProfileScreen extends HookConsumerWidget { | |||||||
|                           child: CustomScrollView( |                           child: CustomScrollView( | ||||||
|                             slivers: [ |                             slivers: [ | ||||||
|                               SliverGap(16), |                               SliverGap(16), | ||||||
|                               SliverPostList(pubName: name), |                               SliverToBoxAdapter( | ||||||
|  |                                 child: publisherCategoryTabWidget(), | ||||||
|  |                               ), | ||||||
|  |                               SliverPostList( | ||||||
|  |                                 key: ValueKey(categoryTab.value), | ||||||
|  |                                 pubName: name, | ||||||
|  |                                 type: switch (categoryTab.value) { | ||||||
|  |                                   1 => 0, | ||||||
|  |                                   2 => 1, | ||||||
|  |                                   _ => null, | ||||||
|  |                                 }, | ||||||
|  |                               ), | ||||||
|                               SliverGap( |                               SliverGap( | ||||||
|                                 MediaQuery.of(context).padding.bottom + 16, |                                 MediaQuery.of(context).padding.bottom + 16, | ||||||
|                               ), |                               ), | ||||||
| @@ -334,9 +364,9 @@ class PublisherProfileScreen extends HookConsumerWidget { | |||||||
|                             alignment: Alignment.topLeft, |                             alignment: Alignment.topLeft, | ||||||
|                             child: SingleChildScrollView( |                             child: SingleChildScrollView( | ||||||
|                               child: Column( |                               child: Column( | ||||||
|                                 crossAxisAlignment: CrossAxisAlignment.start, |                                 crossAxisAlignment: CrossAxisAlignment.stretch, | ||||||
|                                 children: [ |                                 children: [ | ||||||
|                                   publisherBasisWidget(data), |                                   publisherBasisWidget(data).padding(bottom: 8), | ||||||
|                                   publisherBadgesWidget(data), |                                   publisherBadgesWidget(data), | ||||||
|                                   publisherVerificationWidget(data), |                                   publisherVerificationWidget(data), | ||||||
|                                   publisherBioWidget(data), |                                   publisherBioWidget(data), | ||||||
| @@ -398,7 +428,16 @@ class PublisherProfileScreen extends HookConsumerWidget { | |||||||
|                           child: publisherVerificationWidget(data), |                           child: publisherVerificationWidget(data), | ||||||
|                         ), |                         ), | ||||||
|                         SliverToBoxAdapter(child: publisherBioWidget(data)), |                         SliverToBoxAdapter(child: publisherBioWidget(data)), | ||||||
|                         SliverPostList(pubName: name), |                         SliverToBoxAdapter(child: publisherCategoryTabWidget()), | ||||||
|  |                         SliverPostList( | ||||||
|  |                           key: ValueKey(categoryTab.value), | ||||||
|  |                           pubName: name, | ||||||
|  |                           type: switch (categoryTab.value) { | ||||||
|  |                             1 => 0, | ||||||
|  |                             2 => 1, | ||||||
|  |                             _ => null, | ||||||
|  |                           }, | ||||||
|  |                         ), | ||||||
|                         SliverGap(MediaQuery.of(context).padding.bottom + 16), |                         SliverGap(MediaQuery.of(context).padding.bottom + 16), | ||||||
|                       ], |                       ], | ||||||
|                     ), |                     ), | ||||||
|   | |||||||
| @@ -488,6 +488,7 @@ class _RealmMemberListSheet extends HookConsumerWidget { | |||||||
|     Future<void> invitePerson() async { |     Future<void> invitePerson() async { | ||||||
|       final result = await showModalBottomSheet( |       final result = await showModalBottomSheet( | ||||||
|         isScrollControlled: true, |         isScrollControlled: true, | ||||||
|  |         useRootNavigator: true, | ||||||
|         context: context, |         context: context, | ||||||
|         builder: (context) => const AccountPickerSheet(), |         builder: (context) => const AccountPickerSheet(), | ||||||
|       ); |       ); | ||||||
|   | |||||||
							
								
								
									
										103
									
								
								lib/screens/stickers/marketplace.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								lib/screens/stickers/marketplace.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,103 @@ | |||||||
|  | import 'package:easy_localization/easy_localization.dart'; | ||||||
|  | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:go_router/go_router.dart'; | ||||||
|  | import 'package:gap/gap.dart'; | ||||||
|  | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
|  | import 'package:island/models/sticker.dart'; | ||||||
|  | import 'package:island/pods/network.dart'; | ||||||
|  | import 'package:island/widgets/app_scaffold.dart'; | ||||||
|  | import 'package:material_symbols_icons/symbols.dart'; | ||||||
|  | import 'package:riverpod_annotation/riverpod_annotation.dart'; | ||||||
|  | import 'package:riverpod_paging_utils/riverpod_paging_utils.dart'; | ||||||
|  |  | ||||||
|  | part 'marketplace.g.dart'; | ||||||
|  |  | ||||||
|  | @riverpod | ||||||
|  | class MarketplaceStickerPacksNotifier extends _$MarketplaceStickerPacksNotifier | ||||||
|  |     with CursorPagingNotifierMixin<SnStickerPack> { | ||||||
|  |   @override | ||||||
|  |   Future<CursorPagingData<SnStickerPack>> build() { | ||||||
|  |     return fetch(cursor: null); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Future<CursorPagingData<SnStickerPack>> fetch({ | ||||||
|  |     required String? cursor, | ||||||
|  |   }) async { | ||||||
|  |     final client = ref.read(apiClientProvider); | ||||||
|  |     final offset = cursor == null ? 0 : int.parse(cursor); | ||||||
|  |  | ||||||
|  |     final response = await client.get( | ||||||
|  |       '/sphere/stickers', | ||||||
|  |       queryParameters: {'offset': offset, 'take': 20}, | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  |     final total = int.parse(response.headers.value('X-Total') ?? '0'); | ||||||
|  |     final List<dynamic> data = response.data; | ||||||
|  |     final stickers = data.map((e) => SnStickerPack.fromJson(e)).toList(); | ||||||
|  |  | ||||||
|  |     final hasMore = offset + stickers.length < total; | ||||||
|  |     final nextCursor = hasMore ? (offset + stickers.length).toString() : null; | ||||||
|  |  | ||||||
|  |     return CursorPagingData( | ||||||
|  |       items: stickers, | ||||||
|  |       hasMore: hasMore, | ||||||
|  |       nextCursor: nextCursor, | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// User-facing marketplace screen for browsing sticker packs. | ||||||
|  | /// This version does NOT rely on publisher name (no pubName). | ||||||
|  | class MarketplaceStickersScreen extends HookConsumerWidget { | ||||||
|  |   const MarketplaceStickersScreen({super.key}); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|  |     return AppScaffold( | ||||||
|  |       appBar: AppBar( | ||||||
|  |         title: const Text('stickers').tr(), | ||||||
|  |         actions: const [Gap(8)], | ||||||
|  |       ), | ||||||
|  |       body: const SliverMarketplaceStickerPacksList(), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class SliverMarketplaceStickerPacksList extends HookConsumerWidget { | ||||||
|  |   const SliverMarketplaceStickerPacksList({super.key}); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|  |     return PagingHelperView( | ||||||
|  |       provider: marketplaceStickerPacksNotifierProvider, | ||||||
|  |       futureRefreshable: marketplaceStickerPacksNotifierProvider.future, | ||||||
|  |       notifierRefreshable: marketplaceStickerPacksNotifierProvider.notifier, | ||||||
|  |       contentBuilder: | ||||||
|  |           (data, widgetCount, endItemView) => ListView.builder( | ||||||
|  |             padding: EdgeInsets.zero, | ||||||
|  |             itemCount: widgetCount, | ||||||
|  |             itemBuilder: (context, index) { | ||||||
|  |               if (index == widgetCount - 1) { | ||||||
|  |                 return endItemView; | ||||||
|  |               } | ||||||
|  |  | ||||||
|  |               final pack = data.items[index]; | ||||||
|  |               return ListTile( | ||||||
|  |                 title: Text(pack.name), | ||||||
|  |                 subtitle: Text(pack.description), | ||||||
|  |                 trailing: const Icon(Symbols.chevron_right), | ||||||
|  |                 onTap: () { | ||||||
|  |                   // Navigate to user-facing sticker pack detail page. | ||||||
|  |                   // Adjust the route name/parameters if your app uses different ones. | ||||||
|  |                   context.pushNamed( | ||||||
|  |                     'stickerPackDetail', | ||||||
|  |                     pathParameters: {'packId': pack.id}, | ||||||
|  |                   ); | ||||||
|  |                 }, | ||||||
|  |               ); | ||||||
|  |             }, | ||||||
|  |           ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										32
									
								
								lib/screens/stickers/marketplace.g.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								lib/screens/stickers/marketplace.g.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | |||||||
|  | // GENERATED CODE - DO NOT MODIFY BY HAND | ||||||
|  |  | ||||||
|  | part of 'marketplace.dart'; | ||||||
|  |  | ||||||
|  | // ************************************************************************** | ||||||
|  | // RiverpodGenerator | ||||||
|  | // ************************************************************************** | ||||||
|  |  | ||||||
|  | String _$marketplaceStickerPacksNotifierHash() => | ||||||
|  |     r'b62ae8b7f5c4f8bb3be8c17fc005ea26da355187'; | ||||||
|  |  | ||||||
|  | /// See also [MarketplaceStickerPacksNotifier]. | ||||||
|  | @ProviderFor(MarketplaceStickerPacksNotifier) | ||||||
|  | final marketplaceStickerPacksNotifierProvider = | ||||||
|  |     AutoDisposeAsyncNotifierProvider< | ||||||
|  |       MarketplaceStickerPacksNotifier, | ||||||
|  |       CursorPagingData<SnStickerPack> | ||||||
|  |     >.internal( | ||||||
|  |       MarketplaceStickerPacksNotifier.new, | ||||||
|  |       name: r'marketplaceStickerPacksNotifierProvider', | ||||||
|  |       debugGetCreateSourceHash: | ||||||
|  |           const bool.fromEnvironment('dart.vm.product') | ||||||
|  |               ? null | ||||||
|  |               : _$marketplaceStickerPacksNotifierHash, | ||||||
|  |       dependencies: null, | ||||||
|  |       allTransitiveDependencies: null, | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  | typedef _$MarketplaceStickerPacksNotifier = | ||||||
|  |     AutoDisposeAsyncNotifier<CursorPagingData<SnStickerPack>>; | ||||||
|  | // 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 | ||||||
							
								
								
									
										230
									
								
								lib/screens/stickers/pack_detail.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										230
									
								
								lib/screens/stickers/pack_detail.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,230 @@ | |||||||
|  | import 'package:easy_localization/easy_localization.dart'; | ||||||
|  | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:flutter/services.dart'; | ||||||
|  | import 'package:gap/gap.dart'; | ||||||
|  | import 'package:google_fonts/google_fonts.dart'; | ||||||
|  | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
|  | import 'package:island/models/sticker.dart'; | ||||||
|  | import 'package:island/pods/network.dart'; | ||||||
|  | import 'package:island/screens/creators/stickers/stickers.dart'; | ||||||
|  | import 'package:island/widgets/alert.dart'; | ||||||
|  | import 'package:island/widgets/app_scaffold.dart'; | ||||||
|  | import 'package:island/widgets/content/cloud_files.dart'; | ||||||
|  | import 'package:material_symbols_icons/symbols.dart'; | ||||||
|  | import 'package:riverpod_annotation/riverpod_annotation.dart'; | ||||||
|  | import 'package:styled_widget/styled_widget.dart'; | ||||||
|  |  | ||||||
|  | part 'pack_detail.g.dart'; // generated by riverpod_annotation build_runner | ||||||
|  |  | ||||||
|  | /// Marketplace version of sticker pack detail page (no publisher dependency). | ||||||
|  | /// Shows all stickers in the pack and provides a button to add the sticker. | ||||||
|  | /// API interactions are intentionally left blank per request. | ||||||
|  | @riverpod | ||||||
|  | Future<List<SnSticker>> marketplaceStickerPackContent( | ||||||
|  |   Ref ref, { | ||||||
|  |   required String packId, | ||||||
|  | }) async { | ||||||
|  |   final apiClient = ref.watch(apiClientProvider); | ||||||
|  |   final resp = await apiClient.get('/sphere/stickers/$packId/content'); | ||||||
|  |   return (resp.data as List).map((e) => SnSticker.fromJson(e)).toList(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @riverpod | ||||||
|  | Future<bool> marketplaceStickerPackOwnership( | ||||||
|  |   Ref ref, { | ||||||
|  |   required String packId, | ||||||
|  | }) async { | ||||||
|  |   final api = ref.watch(apiClientProvider); | ||||||
|  |   try { | ||||||
|  |     await api.get('/sphere/stickers/$packId/own'); | ||||||
|  |     // If not 404, consider owned | ||||||
|  |     return true; | ||||||
|  |   } on Object catch (e) { | ||||||
|  |     // Dio error handling agnostic: treat 404 as not-owned, rethrow others | ||||||
|  |     final msg = e.toString(); | ||||||
|  |     if (msg.contains('404')) return false; | ||||||
|  |     rethrow; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class MarketplaceStickerPackDetailScreen extends HookConsumerWidget { | ||||||
|  |   final String id; | ||||||
|  |   const MarketplaceStickerPackDetailScreen({super.key, required this.id}); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|  |     // Pack metadata provider exists globally in creators file; reuse it. | ||||||
|  |     final pack = ref.watch(stickerPackProvider(id)); | ||||||
|  |     final packContent = ref.watch( | ||||||
|  |       marketplaceStickerPackContentProvider(packId: id), | ||||||
|  |     ); | ||||||
|  |     final owned = ref.watch( | ||||||
|  |       marketplaceStickerPackOwnershipProvider(packId: id), | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  |     // Add entire pack to user's collection | ||||||
|  |     Future<void> addPackToMyCollection() async { | ||||||
|  |       final apiClient = ref.watch(apiClientProvider); | ||||||
|  |       await apiClient.post('/sphere/stickers/$id/own'); | ||||||
|  |       HapticFeedback.selectionClick(); | ||||||
|  |       ref.invalidate(marketplaceStickerPackOwnershipProvider(packId: id)); | ||||||
|  |       if (!context.mounted) return; | ||||||
|  |       showSnackBar('stickerPackAdded'.tr()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // Remove ownership of the pack | ||||||
|  |     Future<void> removePackFromMyCollection() async { | ||||||
|  |       final apiClient = ref.watch(apiClientProvider); | ||||||
|  |       await apiClient.delete('/sphere/stickers/$id/own'); | ||||||
|  |       HapticFeedback.selectionClick(); | ||||||
|  |       ref.invalidate(marketplaceStickerPackOwnershipProvider(packId: id)); | ||||||
|  |       if (!context.mounted) return; | ||||||
|  |       showSnackBar('stickerPackRemoved'.tr()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return AppScaffold( | ||||||
|  |       appBar: AppBar(title: Text(pack.value?.name ?? 'loading'.tr())), | ||||||
|  |       body: pack.when( | ||||||
|  |         data: (p) { | ||||||
|  |           return Column( | ||||||
|  |             crossAxisAlignment: CrossAxisAlignment.stretch, | ||||||
|  |             children: [ | ||||||
|  |               // Pack meta | ||||||
|  |               Column( | ||||||
|  |                 crossAxisAlignment: CrossAxisAlignment.stretch, | ||||||
|  |                 children: [ | ||||||
|  |                   Text(p?.description ?? ''), | ||||||
|  |                   Row( | ||||||
|  |                     spacing: 4, | ||||||
|  |                     children: [ | ||||||
|  |                       const Icon(Symbols.folder, size: 16), | ||||||
|  |                       Text( | ||||||
|  |                         '${packContent.value?.length ?? 0}/24', | ||||||
|  |                         style: GoogleFonts.robotoMono(), | ||||||
|  |                       ), | ||||||
|  |                     ], | ||||||
|  |                   ).opacity(0.85), | ||||||
|  |                   Row( | ||||||
|  |                     spacing: 4, | ||||||
|  |                     children: [ | ||||||
|  |                       const Icon(Symbols.sell, size: 16), | ||||||
|  |                       Text(p?.prefix ?? '', style: GoogleFonts.robotoMono()), | ||||||
|  |                     ], | ||||||
|  |                   ).opacity(0.85), | ||||||
|  |                   Row( | ||||||
|  |                     spacing: 4, | ||||||
|  |                     children: [ | ||||||
|  |                       const Icon(Symbols.tag, size: 16), | ||||||
|  |                       SelectableText( | ||||||
|  |                         p?.id ?? id, | ||||||
|  |                         style: GoogleFonts.robotoMono(), | ||||||
|  |                       ), | ||||||
|  |                     ], | ||||||
|  |                   ).opacity(0.85), | ||||||
|  |                 ], | ||||||
|  |               ).padding(horizontal: 24, vertical: 24), | ||||||
|  |               const Divider(height: 1), | ||||||
|  |               // Stickers grid | ||||||
|  |               Expanded( | ||||||
|  |                 child: packContent.when( | ||||||
|  |                   data: | ||||||
|  |                       (stickers) => RefreshIndicator( | ||||||
|  |                         onRefresh: | ||||||
|  |                             () => ref.refresh( | ||||||
|  |                               marketplaceStickerPackContentProvider( | ||||||
|  |                                 packId: id, | ||||||
|  |                               ).future, | ||||||
|  |                             ), | ||||||
|  |                         child: GridView.builder( | ||||||
|  |                           padding: const EdgeInsets.symmetric( | ||||||
|  |                             horizontal: 24, | ||||||
|  |                             vertical: 20, | ||||||
|  |                           ), | ||||||
|  |                           gridDelegate: | ||||||
|  |                               const SliverGridDelegateWithMaxCrossAxisExtent( | ||||||
|  |                                 maxCrossAxisExtent: 96, | ||||||
|  |                                 mainAxisSpacing: 12, | ||||||
|  |                                 crossAxisSpacing: 12, | ||||||
|  |                               ), | ||||||
|  |                           itemCount: stickers.length, | ||||||
|  |                           itemBuilder: (context, index) { | ||||||
|  |                             final sticker = stickers[index]; | ||||||
|  |                             return Tooltip( | ||||||
|  |                               message: ':${p?.prefix ?? ''}${sticker.slug}:', | ||||||
|  |                               child: ClipRRect( | ||||||
|  |                                 borderRadius: const BorderRadius.all( | ||||||
|  |                                   Radius.circular(8), | ||||||
|  |                                 ), | ||||||
|  |                                 child: Container( | ||||||
|  |                                   decoration: BoxDecoration( | ||||||
|  |                                     color: | ||||||
|  |                                         Theme.of( | ||||||
|  |                                           context, | ||||||
|  |                                         ).colorScheme.surfaceContainer, | ||||||
|  |                                     borderRadius: const BorderRadius.all( | ||||||
|  |                                       Radius.circular(8), | ||||||
|  |                                     ), | ||||||
|  |                                   ), | ||||||
|  |                                   child: AspectRatio( | ||||||
|  |                                     aspectRatio: 1, | ||||||
|  |                                     child: CloudImageWidget( | ||||||
|  |                                       fileId: sticker.imageId, | ||||||
|  |                                       fit: BoxFit.contain, | ||||||
|  |                                     ), | ||||||
|  |                                   ), | ||||||
|  |                                 ), | ||||||
|  |                               ), | ||||||
|  |                             ); | ||||||
|  |                           }, | ||||||
|  |                         ), | ||||||
|  |                       ), | ||||||
|  |                   error: | ||||||
|  |                       (err, _) => | ||||||
|  |                           Text( | ||||||
|  |                             'Error: $err', | ||||||
|  |                           ).textAlignment(TextAlign.center).center(), | ||||||
|  |                   loading: () => const CircularProgressIndicator().center(), | ||||||
|  |                 ), | ||||||
|  |               ), | ||||||
|  |               Padding( | ||||||
|  |                 padding: EdgeInsets.symmetric(horizontal: 24, vertical: 8), | ||||||
|  |                 child: owned.when( | ||||||
|  |                   data: | ||||||
|  |                       (isOwned) => FilledButton.icon( | ||||||
|  |                         onPressed: | ||||||
|  |                             isOwned | ||||||
|  |                                 ? removePackFromMyCollection | ||||||
|  |                                 : addPackToMyCollection, | ||||||
|  |                         icon: Icon( | ||||||
|  |                           isOwned ? Symbols.remove_circle : Symbols.add_circle, | ||||||
|  |                         ), | ||||||
|  |                         label: Text( | ||||||
|  |                           isOwned ? 'removePack'.tr() : 'addPack'.tr(), | ||||||
|  |                         ), | ||||||
|  |                       ), | ||||||
|  |                   loading: | ||||||
|  |                       () => const SizedBox( | ||||||
|  |                         height: 32, | ||||||
|  |                         width: 32, | ||||||
|  |                         child: CircularProgressIndicator(strokeWidth: 2), | ||||||
|  |                       ), | ||||||
|  |                   error: | ||||||
|  |                       (_, _) => OutlinedButton.icon( | ||||||
|  |                         onPressed: addPackToMyCollection, | ||||||
|  |                         icon: const Icon(Symbols.add_circle), | ||||||
|  |                         label: Text('addPack').tr(), | ||||||
|  |                       ), | ||||||
|  |                 ), | ||||||
|  |               ), | ||||||
|  |               Gap(MediaQuery.of(context).padding.bottom), | ||||||
|  |             ], | ||||||
|  |           ); | ||||||
|  |         }, | ||||||
|  |         error: | ||||||
|  |             (err, _) => | ||||||
|  |                 Text('Error: $err').textAlignment(TextAlign.center).center(), | ||||||
|  |         loading: () => const CircularProgressIndicator().center(), | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										317
									
								
								lib/screens/stickers/pack_detail.g.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										317
									
								
								lib/screens/stickers/pack_detail.g.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,317 @@ | |||||||
|  | // GENERATED CODE - DO NOT MODIFY BY HAND | ||||||
|  |  | ||||||
|  | part of 'pack_detail.dart'; | ||||||
|  |  | ||||||
|  | // ************************************************************************** | ||||||
|  | // RiverpodGenerator | ||||||
|  | // ************************************************************************** | ||||||
|  |  | ||||||
|  | String _$marketplaceStickerPackContentHash() => | ||||||
|  |     r'886f8305c978dbea6e5d990a7d555048ac704a5d'; | ||||||
|  |  | ||||||
|  | /// Copied from Dart SDK | ||||||
|  | class _SystemHash { | ||||||
|  |   _SystemHash._(); | ||||||
|  |  | ||||||
|  |   static int combine(int hash, int value) { | ||||||
|  |     // ignore: parameter_assignments | ||||||
|  |     hash = 0x1fffffff & (hash + value); | ||||||
|  |     // ignore: parameter_assignments | ||||||
|  |     hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); | ||||||
|  |     return hash ^ (hash >> 6); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   static int finish(int hash) { | ||||||
|  |     // ignore: parameter_assignments | ||||||
|  |     hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); | ||||||
|  |     // ignore: parameter_assignments | ||||||
|  |     hash = hash ^ (hash >> 11); | ||||||
|  |     return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// Marketplace version of sticker pack detail page (no publisher dependency). | ||||||
|  | /// Shows all stickers in the pack and provides a button to add the sticker. | ||||||
|  | /// API interactions are intentionally left blank per request. | ||||||
|  | /// | ||||||
|  | /// Copied from [marketplaceStickerPackContent]. | ||||||
|  | @ProviderFor(marketplaceStickerPackContent) | ||||||
|  | const marketplaceStickerPackContentProvider = | ||||||
|  |     MarketplaceStickerPackContentFamily(); | ||||||
|  |  | ||||||
|  | /// Marketplace version of sticker pack detail page (no publisher dependency). | ||||||
|  | /// Shows all stickers in the pack and provides a button to add the sticker. | ||||||
|  | /// API interactions are intentionally left blank per request. | ||||||
|  | /// | ||||||
|  | /// Copied from [marketplaceStickerPackContent]. | ||||||
|  | class MarketplaceStickerPackContentFamily | ||||||
|  |     extends Family<AsyncValue<List<SnSticker>>> { | ||||||
|  |   /// Marketplace version of sticker pack detail page (no publisher dependency). | ||||||
|  |   /// Shows all stickers in the pack and provides a button to add the sticker. | ||||||
|  |   /// API interactions are intentionally left blank per request. | ||||||
|  |   /// | ||||||
|  |   /// Copied from [marketplaceStickerPackContent]. | ||||||
|  |   const MarketplaceStickerPackContentFamily(); | ||||||
|  |  | ||||||
|  |   /// Marketplace version of sticker pack detail page (no publisher dependency). | ||||||
|  |   /// Shows all stickers in the pack and provides a button to add the sticker. | ||||||
|  |   /// API interactions are intentionally left blank per request. | ||||||
|  |   /// | ||||||
|  |   /// Copied from [marketplaceStickerPackContent]. | ||||||
|  |   MarketplaceStickerPackContentProvider call({required String packId}) { | ||||||
|  |     return MarketplaceStickerPackContentProvider(packId: packId); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   MarketplaceStickerPackContentProvider getProviderOverride( | ||||||
|  |     covariant MarketplaceStickerPackContentProvider provider, | ||||||
|  |   ) { | ||||||
|  |     return call(packId: provider.packId); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   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'marketplaceStickerPackContentProvider'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// Marketplace version of sticker pack detail page (no publisher dependency). | ||||||
|  | /// Shows all stickers in the pack and provides a button to add the sticker. | ||||||
|  | /// API interactions are intentionally left blank per request. | ||||||
|  | /// | ||||||
|  | /// Copied from [marketplaceStickerPackContent]. | ||||||
|  | class MarketplaceStickerPackContentProvider | ||||||
|  |     extends AutoDisposeFutureProvider<List<SnSticker>> { | ||||||
|  |   /// Marketplace version of sticker pack detail page (no publisher dependency). | ||||||
|  |   /// Shows all stickers in the pack and provides a button to add the sticker. | ||||||
|  |   /// API interactions are intentionally left blank per request. | ||||||
|  |   /// | ||||||
|  |   /// Copied from [marketplaceStickerPackContent]. | ||||||
|  |   MarketplaceStickerPackContentProvider({required String packId}) | ||||||
|  |     : this._internal( | ||||||
|  |         (ref) => marketplaceStickerPackContent( | ||||||
|  |           ref as MarketplaceStickerPackContentRef, | ||||||
|  |           packId: packId, | ||||||
|  |         ), | ||||||
|  |         from: marketplaceStickerPackContentProvider, | ||||||
|  |         name: r'marketplaceStickerPackContentProvider', | ||||||
|  |         debugGetCreateSourceHash: | ||||||
|  |             const bool.fromEnvironment('dart.vm.product') | ||||||
|  |                 ? null | ||||||
|  |                 : _$marketplaceStickerPackContentHash, | ||||||
|  |         dependencies: MarketplaceStickerPackContentFamily._dependencies, | ||||||
|  |         allTransitiveDependencies: | ||||||
|  |             MarketplaceStickerPackContentFamily._allTransitiveDependencies, | ||||||
|  |         packId: packId, | ||||||
|  |       ); | ||||||
|  |  | ||||||
|  |   MarketplaceStickerPackContentProvider._internal( | ||||||
|  |     super._createNotifier, { | ||||||
|  |     required super.name, | ||||||
|  |     required super.dependencies, | ||||||
|  |     required super.allTransitiveDependencies, | ||||||
|  |     required super.debugGetCreateSourceHash, | ||||||
|  |     required super.from, | ||||||
|  |     required this.packId, | ||||||
|  |   }) : super.internal(); | ||||||
|  |  | ||||||
|  |   final String packId; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Override overrideWith( | ||||||
|  |     FutureOr<List<SnSticker>> Function( | ||||||
|  |       MarketplaceStickerPackContentRef provider, | ||||||
|  |     ) | ||||||
|  |     create, | ||||||
|  |   ) { | ||||||
|  |     return ProviderOverride( | ||||||
|  |       origin: this, | ||||||
|  |       override: MarketplaceStickerPackContentProvider._internal( | ||||||
|  |         (ref) => create(ref as MarketplaceStickerPackContentRef), | ||||||
|  |         from: from, | ||||||
|  |         name: null, | ||||||
|  |         dependencies: null, | ||||||
|  |         allTransitiveDependencies: null, | ||||||
|  |         debugGetCreateSourceHash: null, | ||||||
|  |         packId: packId, | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   AutoDisposeFutureProviderElement<List<SnSticker>> createElement() { | ||||||
|  |     return _MarketplaceStickerPackContentProviderElement(this); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   bool operator ==(Object other) { | ||||||
|  |     return other is MarketplaceStickerPackContentProvider && | ||||||
|  |         other.packId == packId; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   int get hashCode { | ||||||
|  |     var hash = _SystemHash.combine(0, runtimeType.hashCode); | ||||||
|  |     hash = _SystemHash.combine(hash, packId.hashCode); | ||||||
|  |  | ||||||
|  |     return _SystemHash.finish(hash); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @Deprecated('Will be removed in 3.0. Use Ref instead') | ||||||
|  | // ignore: unused_element | ||||||
|  | mixin MarketplaceStickerPackContentRef | ||||||
|  |     on AutoDisposeFutureProviderRef<List<SnSticker>> { | ||||||
|  |   /// The parameter `packId` of this provider. | ||||||
|  |   String get packId; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class _MarketplaceStickerPackContentProviderElement | ||||||
|  |     extends AutoDisposeFutureProviderElement<List<SnSticker>> | ||||||
|  |     with MarketplaceStickerPackContentRef { | ||||||
|  |   _MarketplaceStickerPackContentProviderElement(super.provider); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   String get packId => (origin as MarketplaceStickerPackContentProvider).packId; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | String _$marketplaceStickerPackOwnershipHash() => | ||||||
|  |     r'e5dd301c309fac958729d13d984ce7a77edbe7e6'; | ||||||
|  |  | ||||||
|  | /// See also [marketplaceStickerPackOwnership]. | ||||||
|  | @ProviderFor(marketplaceStickerPackOwnership) | ||||||
|  | const marketplaceStickerPackOwnershipProvider = | ||||||
|  |     MarketplaceStickerPackOwnershipFamily(); | ||||||
|  |  | ||||||
|  | /// See also [marketplaceStickerPackOwnership]. | ||||||
|  | class MarketplaceStickerPackOwnershipFamily extends Family<AsyncValue<bool>> { | ||||||
|  |   /// See also [marketplaceStickerPackOwnership]. | ||||||
|  |   const MarketplaceStickerPackOwnershipFamily(); | ||||||
|  |  | ||||||
|  |   /// See also [marketplaceStickerPackOwnership]. | ||||||
|  |   MarketplaceStickerPackOwnershipProvider call({required String packId}) { | ||||||
|  |     return MarketplaceStickerPackOwnershipProvider(packId: packId); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   MarketplaceStickerPackOwnershipProvider getProviderOverride( | ||||||
|  |     covariant MarketplaceStickerPackOwnershipProvider provider, | ||||||
|  |   ) { | ||||||
|  |     return call(packId: provider.packId); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   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'marketplaceStickerPackOwnershipProvider'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// See also [marketplaceStickerPackOwnership]. | ||||||
|  | class MarketplaceStickerPackOwnershipProvider | ||||||
|  |     extends AutoDisposeFutureProvider<bool> { | ||||||
|  |   /// See also [marketplaceStickerPackOwnership]. | ||||||
|  |   MarketplaceStickerPackOwnershipProvider({required String packId}) | ||||||
|  |     : this._internal( | ||||||
|  |         (ref) => marketplaceStickerPackOwnership( | ||||||
|  |           ref as MarketplaceStickerPackOwnershipRef, | ||||||
|  |           packId: packId, | ||||||
|  |         ), | ||||||
|  |         from: marketplaceStickerPackOwnershipProvider, | ||||||
|  |         name: r'marketplaceStickerPackOwnershipProvider', | ||||||
|  |         debugGetCreateSourceHash: | ||||||
|  |             const bool.fromEnvironment('dart.vm.product') | ||||||
|  |                 ? null | ||||||
|  |                 : _$marketplaceStickerPackOwnershipHash, | ||||||
|  |         dependencies: MarketplaceStickerPackOwnershipFamily._dependencies, | ||||||
|  |         allTransitiveDependencies: | ||||||
|  |             MarketplaceStickerPackOwnershipFamily._allTransitiveDependencies, | ||||||
|  |         packId: packId, | ||||||
|  |       ); | ||||||
|  |  | ||||||
|  |   MarketplaceStickerPackOwnershipProvider._internal( | ||||||
|  |     super._createNotifier, { | ||||||
|  |     required super.name, | ||||||
|  |     required super.dependencies, | ||||||
|  |     required super.allTransitiveDependencies, | ||||||
|  |     required super.debugGetCreateSourceHash, | ||||||
|  |     required super.from, | ||||||
|  |     required this.packId, | ||||||
|  |   }) : super.internal(); | ||||||
|  |  | ||||||
|  |   final String packId; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Override overrideWith( | ||||||
|  |     FutureOr<bool> Function(MarketplaceStickerPackOwnershipRef provider) create, | ||||||
|  |   ) { | ||||||
|  |     return ProviderOverride( | ||||||
|  |       origin: this, | ||||||
|  |       override: MarketplaceStickerPackOwnershipProvider._internal( | ||||||
|  |         (ref) => create(ref as MarketplaceStickerPackOwnershipRef), | ||||||
|  |         from: from, | ||||||
|  |         name: null, | ||||||
|  |         dependencies: null, | ||||||
|  |         allTransitiveDependencies: null, | ||||||
|  |         debugGetCreateSourceHash: null, | ||||||
|  |         packId: packId, | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   AutoDisposeFutureProviderElement<bool> createElement() { | ||||||
|  |     return _MarketplaceStickerPackOwnershipProviderElement(this); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   bool operator ==(Object other) { | ||||||
|  |     return other is MarketplaceStickerPackOwnershipProvider && | ||||||
|  |         other.packId == packId; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   int get hashCode { | ||||||
|  |     var hash = _SystemHash.combine(0, runtimeType.hashCode); | ||||||
|  |     hash = _SystemHash.combine(hash, packId.hashCode); | ||||||
|  |  | ||||||
|  |     return _SystemHash.finish(hash); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @Deprecated('Will be removed in 3.0. Use Ref instead') | ||||||
|  | // ignore: unused_element | ||||||
|  | mixin MarketplaceStickerPackOwnershipRef on AutoDisposeFutureProviderRef<bool> { | ||||||
|  |   /// The parameter `packId` of this provider. | ||||||
|  |   String get packId; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class _MarketplaceStickerPackOwnershipProviderElement | ||||||
|  |     extends AutoDisposeFutureProviderElement<bool> | ||||||
|  |     with MarketplaceStickerPackOwnershipRef { | ||||||
|  |   _MarketplaceStickerPackOwnershipProviderElement(super.provider); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   String get packId => | ||||||
|  |       (origin as MarketplaceStickerPackOwnershipProvider).packId; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 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 | ||||||
| @@ -67,6 +67,9 @@ Future<void> subscribePushNotification( | |||||||
|   Dio apiClient, { |   Dio apiClient, { | ||||||
|   bool detailedErrors = false, |   bool detailedErrors = false, | ||||||
| }) async { | }) async { | ||||||
|  |   if (Platform.isLinux){ | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|   await FirebaseMessaging.instance.requestPermission( |   await FirebaseMessaging.instance.requestPermission( | ||||||
|     alert: true, |     alert: true, | ||||||
|     badge: true, |     badge: true, | ||||||
|   | |||||||
							
								
								
									
										395
									
								
								lib/services/update_service.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										395
									
								
								lib/services/update_service.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,395 @@ | |||||||
|  | import 'dart:async'; | ||||||
|  | import 'dart:developer'; | ||||||
|  | import 'dart:io'; | ||||||
|  |  | ||||||
|  | import 'package:dio/dio.dart'; | ||||||
|  | import 'package:flutter/foundation.dart'; | ||||||
|  | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:flutter_app_update/azhon_app_update.dart'; | ||||||
|  | import 'package:flutter_app_update/update_model.dart'; | ||||||
|  | import 'package:island/widgets/content/markdown.dart'; | ||||||
|  | import 'package:material_symbols_icons/symbols.dart'; | ||||||
|  | import 'package:package_info_plus/package_info_plus.dart'; | ||||||
|  | import 'package:collection/collection.dart'; // Added for firstWhereOrNull | ||||||
|  | import 'package:styled_widget/styled_widget.dart'; | ||||||
|  | import 'package:url_launcher/url_launcher.dart'; | ||||||
|  | import 'package:island/widgets/content/sheet.dart'; | ||||||
|  |  | ||||||
|  | /// Data model for a GitHub release we care about | ||||||
|  | class GithubReleaseInfo { | ||||||
|  |   final String tagName; | ||||||
|  |   final String name; | ||||||
|  |   final String body; | ||||||
|  |   final String htmlUrl; | ||||||
|  |   final DateTime createdAt; | ||||||
|  |   final List<GithubReleaseAsset> assets; | ||||||
|  |  | ||||||
|  |   const GithubReleaseInfo({ | ||||||
|  |     required this.tagName, | ||||||
|  |     required this.name, | ||||||
|  |     required this.body, | ||||||
|  |     required this.htmlUrl, | ||||||
|  |     required this.createdAt, | ||||||
|  |     this.assets = const [], | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// Data model for a GitHub release asset | ||||||
|  | class GithubReleaseAsset { | ||||||
|  |   final String name; | ||||||
|  |   final String browserDownloadUrl; | ||||||
|  |  | ||||||
|  |   const GithubReleaseAsset({ | ||||||
|  |     required this.name, | ||||||
|  |     required this.browserDownloadUrl, | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   factory GithubReleaseAsset.fromJson(Map<String, dynamic> json) { | ||||||
|  |     return GithubReleaseAsset( | ||||||
|  |       name: json['name'] as String, | ||||||
|  |       browserDownloadUrl: json['browser_download_url'] as String, | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// Parses version and build number from "x.y.z+build" | ||||||
|  | class _ParsedVersion implements Comparable<_ParsedVersion> { | ||||||
|  |   final int major; | ||||||
|  |   final int minor; | ||||||
|  |   final int patch; | ||||||
|  |   final int build; | ||||||
|  |  | ||||||
|  |   const _ParsedVersion(this.major, this.minor, this.patch, this.build); | ||||||
|  |  | ||||||
|  |   static _ParsedVersion? tryParse(String input) { | ||||||
|  |     // Expect format like 0.0.0+00 (build after '+'). Allow missing build as 0. | ||||||
|  |     final partsPlus = input.split('+'); | ||||||
|  |     final core = partsPlus[0].trim(); | ||||||
|  |     final buildStr = partsPlus.length > 1 ? partsPlus[1].trim() : '0'; | ||||||
|  |     final coreParts = core.split('.'); | ||||||
|  |     if (coreParts.length != 3) return null; | ||||||
|  |  | ||||||
|  |     final major = int.tryParse(coreParts[0]) ?? 0; | ||||||
|  |     final minor = int.tryParse(coreParts[1]) ?? 0; | ||||||
|  |     final patch = int.tryParse(coreParts[2]) ?? 0; | ||||||
|  |     final build = int.tryParse(buildStr) ?? 0; | ||||||
|  |  | ||||||
|  |     return _ParsedVersion(major, minor, patch, build); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   int compareTo(_ParsedVersion other) { | ||||||
|  |     if (major != other.major) return major.compareTo(other.major); | ||||||
|  |     if (minor != other.minor) return minor.compareTo(other.minor); | ||||||
|  |     if (patch != other.patch) return patch.compareTo(other.patch); | ||||||
|  |     return build.compareTo(other.build); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   String toString() => '$major.$minor.$patch+$build'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class UpdateService { | ||||||
|  |   UpdateService({Dio? dio, this.useProxy = false}) | ||||||
|  |     : _dio = | ||||||
|  |           dio ?? | ||||||
|  |           Dio( | ||||||
|  |             BaseOptions( | ||||||
|  |               headers: { | ||||||
|  |                 // Identify the app to GitHub; avoids some rate-limits and adds clarity | ||||||
|  |                 'Accept': 'application/vnd.github+json', | ||||||
|  |                 'User-Agent': 'solian-update-checker', | ||||||
|  |               }, | ||||||
|  |               connectTimeout: const Duration(seconds: 10), | ||||||
|  |               receiveTimeout: const Duration(seconds: 15), | ||||||
|  |             ), | ||||||
|  |           ); | ||||||
|  |  | ||||||
|  |   final Dio _dio; | ||||||
|  |   final bool useProxy; | ||||||
|  |  | ||||||
|  |   static const _proxyBaseUrl = 'https://ghfast.top/'; | ||||||
|  |  | ||||||
|  |   static const _releasesLatestApi = | ||||||
|  |       'https://api.github.com/repos/solsynth/solian/releases/latest'; | ||||||
|  |  | ||||||
|  |   /// Checks GitHub for the latest release and compares against the current app version. | ||||||
|  |   /// If update is available, shows a bottom sheet with changelog and an action to open release page. | ||||||
|  |   Future<void> checkForUpdates(BuildContext context) async { | ||||||
|  |     log('[Update] Checking for updates...'); | ||||||
|  |     try { | ||||||
|  |       final release = await fetchLatestRelease(); | ||||||
|  |       if (release == null) { | ||||||
|  |         log('[Update] No latest release found or could not fetch.'); | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |       log('[Update] Fetched latest release: ${release.tagName}'); | ||||||
|  |  | ||||||
|  |       final info = await PackageInfo.fromPlatform(); | ||||||
|  |       final localVersionStr = '${info.version}+${info.buildNumber}'; | ||||||
|  |       log('[Update] Local app version: $localVersionStr'); | ||||||
|  |  | ||||||
|  |       final latest = _ParsedVersion.tryParse(release.tagName); | ||||||
|  |       final local = _ParsedVersion.tryParse(localVersionStr); | ||||||
|  |  | ||||||
|  |       if (latest == null || local == null) { | ||||||
|  |         log( | ||||||
|  |           '[Update] Failed to parse versions. Latest: ${release.tagName}, Local: $localVersionStr', | ||||||
|  |         ); | ||||||
|  |         // If parsing fails, do nothing silently | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |       log('[Update] Parsed versions. Latest: $latest, Local: $local'); | ||||||
|  |  | ||||||
|  |       final needsUpdate = latest.compareTo(local) > 0; | ||||||
|  |       if (!needsUpdate) { | ||||||
|  |         log('[Update] App is up to date. No update needed.'); | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |       log('[Update] Update available! Latest: $latest, Local: $local'); | ||||||
|  |  | ||||||
|  |       if (!context.mounted) { | ||||||
|  |         log('[Update] Context not mounted, cannot show update sheet.'); | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       // Delay to ensure UI is ready (if called at startup) | ||||||
|  |       await Future.delayed(const Duration(milliseconds: 100)); | ||||||
|  |  | ||||||
|  |       if (context.mounted) { | ||||||
|  |         await showUpdateSheet(context, release); | ||||||
|  |         log('[Update] Update sheet shown.'); | ||||||
|  |       } | ||||||
|  |     } catch (e) { | ||||||
|  |       log('[Update] Error checking for updates: $e'); | ||||||
|  |       // Ignore errors (network, api, etc.) | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   /// Manually show the update sheet with a provided release. | ||||||
|  |   /// Useful for About page or testing. | ||||||
|  |   Future<void> showUpdateSheet( | ||||||
|  |     BuildContext context, | ||||||
|  |     GithubReleaseInfo release, | ||||||
|  |   ) async { | ||||||
|  |     if (!context.mounted) return; | ||||||
|  |     await showModalBottomSheet( | ||||||
|  |       context: context, | ||||||
|  |       isScrollControlled: true, | ||||||
|  |       useRootNavigator: true, | ||||||
|  |       builder: (ctx) { | ||||||
|  |         String? androidUpdateUrl; | ||||||
|  |         if (Platform.isAndroid) { | ||||||
|  |           androidUpdateUrl = _getAndroidUpdateUrl(release.assets); | ||||||
|  |         } | ||||||
|  |         return _UpdateSheet( | ||||||
|  |           release: release, | ||||||
|  |           onOpen: () async { | ||||||
|  |             final uri = Uri.parse(release.htmlUrl); | ||||||
|  |             if (await canLaunchUrl(uri)) { | ||||||
|  |               await launchUrl(uri, mode: LaunchMode.externalApplication); | ||||||
|  |             } | ||||||
|  |           }, | ||||||
|  |           androidUpdateUrl: androidUpdateUrl, | ||||||
|  |           useProxy: useProxy, // Pass the useProxy flag | ||||||
|  |         ); | ||||||
|  |       }, | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   String? _getAndroidUpdateUrl(List<GithubReleaseAsset> assets) { | ||||||
|  |     final arm64 = assets.firstWhereOrNull( | ||||||
|  |       (asset) => asset.name == 'app-arm64-v8a-release.apk', | ||||||
|  |     ); | ||||||
|  |     final armeabi = assets.firstWhereOrNull( | ||||||
|  |       (asset) => asset.name == 'app-armeabi-v7a-release.apk', | ||||||
|  |     ); | ||||||
|  |     final x86_64 = assets.firstWhereOrNull( | ||||||
|  |       (asset) => asset.name == 'app-x86_64-release.apk', | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  |     // Prioritize arm64, then armeabi, then x86_64 | ||||||
|  |     if (arm64 != null) { | ||||||
|  |       return arm64.browserDownloadUrl; | ||||||
|  |     } else if (armeabi != null) { | ||||||
|  |       return armeabi.browserDownloadUrl; | ||||||
|  |     } else if (x86_64 != null) { | ||||||
|  |       return x86_64.browserDownloadUrl; | ||||||
|  |     } | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   /// Fetch the latest release info from GitHub. | ||||||
|  |   /// Public so other screens (e.g., About) can manually trigger update checks. | ||||||
|  |   Future<GithubReleaseInfo?> fetchLatestRelease() async { | ||||||
|  |     final apiEndpoint = | ||||||
|  |         useProxy | ||||||
|  |             ? '$_proxyBaseUrl${Uri.encodeComponent(_releasesLatestApi)}' | ||||||
|  |             : _releasesLatestApi; | ||||||
|  |  | ||||||
|  |     log( | ||||||
|  |       '[Update] Fetching latest release from GitHub API: $apiEndpoint (Proxy: $useProxy)', | ||||||
|  |     ); | ||||||
|  |     final resp = await _dio.get(apiEndpoint); | ||||||
|  |     if (resp.statusCode != 200) { | ||||||
|  |       log( | ||||||
|  |         '[Update] Failed to fetch latest release. Status code: ${resp.statusCode}', | ||||||
|  |       ); | ||||||
|  |       return null; | ||||||
|  |     } | ||||||
|  |     final data = resp.data as Map<String, dynamic>; | ||||||
|  |     log('[Update] Successfully fetched release data.'); | ||||||
|  |  | ||||||
|  |     final tagName = (data['tag_name'] ?? '').toString(); | ||||||
|  |     final name = (data['name'] ?? tagName).toString(); | ||||||
|  |     final body = (data['body'] ?? '').toString(); | ||||||
|  |     final htmlUrl = (data['html_url'] ?? '').toString(); | ||||||
|  |     final createdAtStr = (data['created_at'] ?? '').toString(); | ||||||
|  |     final createdAt = DateTime.tryParse(createdAtStr) ?? DateTime.now(); | ||||||
|  |     final assetsData = | ||||||
|  |         (data['assets'] as List<dynamic>?) | ||||||
|  |             ?.map((e) => GithubReleaseAsset.fromJson(e as Map<String, dynamic>)) | ||||||
|  |             .toList() ?? | ||||||
|  |         []; | ||||||
|  |  | ||||||
|  |     if (tagName.isEmpty || htmlUrl.isEmpty) { | ||||||
|  |       log( | ||||||
|  |         '[Update] Missing tag_name or html_url in release data. TagName: "$tagName", HtmlUrl: "$htmlUrl"', | ||||||
|  |       ); | ||||||
|  |       return null; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     log('[Update] Returning GithubReleaseInfo for tag: $tagName'); | ||||||
|  |     return GithubReleaseInfo( | ||||||
|  |       tagName: tagName, | ||||||
|  |       name: name, | ||||||
|  |       body: body, | ||||||
|  |       htmlUrl: htmlUrl, | ||||||
|  |       createdAt: createdAt, | ||||||
|  |       assets: assetsData, | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class _UpdateSheet extends StatefulWidget { | ||||||
|  |   const _UpdateSheet({ | ||||||
|  |     required this.release, | ||||||
|  |     required this.onOpen, | ||||||
|  |     this.androidUpdateUrl, | ||||||
|  |     this.useProxy = false, | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   final String? androidUpdateUrl; | ||||||
|  |   final bool useProxy; | ||||||
|  |   final GithubReleaseInfo release; | ||||||
|  |   final VoidCallback onOpen; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   State<_UpdateSheet> createState() => _UpdateSheetState(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class _UpdateSheetState extends State<_UpdateSheet> { | ||||||
|  |   late bool _useProxy; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   void initState() { | ||||||
|  |     super.initState(); | ||||||
|  |     _useProxy = widget.useProxy; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   Future<void> _installUpdate(String url) async { | ||||||
|  |     final downloadUrl = | ||||||
|  |         _useProxy ? 'https://ghfast.top/${Uri.encodeComponent(url)}' : url; | ||||||
|  |  | ||||||
|  |     UpdateModel model = UpdateModel( | ||||||
|  |       downloadUrl, | ||||||
|  |       "solian-update-${widget.release.tagName}.apk", | ||||||
|  |       "launcher_icon", | ||||||
|  |       'https://apps.apple.com/us/app/solian/id6499032345', | ||||||
|  |     ); | ||||||
|  |     AzhonAppUpdate.update(model); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context) { | ||||||
|  |     final theme = Theme.of(context); | ||||||
|  |     return SheetScaffold( | ||||||
|  |       titleText: 'Update available', | ||||||
|  |       child: Padding( | ||||||
|  |         padding: EdgeInsets.only( | ||||||
|  |           bottom: 16 + MediaQuery.of(context).padding.bottom, | ||||||
|  |         ), | ||||||
|  |         child: Column( | ||||||
|  |           crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|  |           children: [ | ||||||
|  |             Column( | ||||||
|  |               crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|  |               children: [ | ||||||
|  |                 Text( | ||||||
|  |                   widget.release.name, | ||||||
|  |                   style: theme.textTheme.titleMedium, | ||||||
|  |                 ).bold(), | ||||||
|  |                 Text(widget.release.tagName).fontSize(12), | ||||||
|  |               ], | ||||||
|  |             ).padding(vertical: 16, horizontal: 16), | ||||||
|  |             const Divider(height: 1), | ||||||
|  |             Expanded( | ||||||
|  |               child: SingleChildScrollView( | ||||||
|  |                 padding: const EdgeInsets.symmetric( | ||||||
|  |                   horizontal: 16, | ||||||
|  |                   vertical: 16, | ||||||
|  |                 ), | ||||||
|  |                 child: MarkdownTextContent( | ||||||
|  |                   content: | ||||||
|  |                       widget.release.body.isEmpty | ||||||
|  |                           ? 'No changelog provided.' | ||||||
|  |                           : widget.release.body, | ||||||
|  |                 ), | ||||||
|  |               ), | ||||||
|  |             ), | ||||||
|  |             if (!kIsWeb && Platform.isAndroid) | ||||||
|  |               SwitchListTile( | ||||||
|  |                 title: const Text('Use GitHub Proxy for Download'), | ||||||
|  |                 value: _useProxy, | ||||||
|  |                 onChanged: (value) { | ||||||
|  |                   setState(() { | ||||||
|  |                     _useProxy = value; | ||||||
|  |                   }); | ||||||
|  |                 }, | ||||||
|  |               ).padding(horizontal: 8), | ||||||
|  |             Column( | ||||||
|  |               children: [ | ||||||
|  |                 Row( | ||||||
|  |                   spacing: 8, | ||||||
|  |                   children: [ | ||||||
|  |                     if (!kIsWeb && | ||||||
|  |                         Platform.isAndroid && | ||||||
|  |                         widget.androidUpdateUrl != null) | ||||||
|  |                       Expanded( | ||||||
|  |                         child: FilledButton.icon( | ||||||
|  |                           onPressed: () { | ||||||
|  |                             log(widget.androidUpdateUrl!); | ||||||
|  |                             _installUpdate(widget.androidUpdateUrl!); | ||||||
|  |                           }, | ||||||
|  |                           icon: const Icon(Symbols.update), | ||||||
|  |                           label: const Text('Install update'), | ||||||
|  |                         ), | ||||||
|  |                       ), | ||||||
|  |                     Expanded( | ||||||
|  |                       child: FilledButton.icon( | ||||||
|  |                         onPressed: widget.onOpen, | ||||||
|  |                         icon: const Icon(Icons.open_in_new), | ||||||
|  |                         label: const Text('Open release page'), | ||||||
|  |                       ), | ||||||
|  |                     ), | ||||||
|  |                   ], | ||||||
|  |                 ), | ||||||
|  |               ], | ||||||
|  |             ).padding(horizontal: 16), | ||||||
|  |           ], | ||||||
|  |         ), | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										30
									
								
								lib/utils/mapping.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								lib/utils/mapping.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | |||||||
|  | String _upperCamelToLowerSnake(String input) { | ||||||
|  |   final regex = RegExp(r'(?<=[a-z0-9])([A-Z])'); | ||||||
|  |   return input | ||||||
|  |       .replaceAllMapped(regex, (match) => '_${match.group(0)}') | ||||||
|  |       .toLowerCase(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | Map<String, dynamic> convertMapKeysToSnakeCase(Map<String, dynamic> input) { | ||||||
|  |   final result = <String, dynamic>{}; | ||||||
|  |  | ||||||
|  |   input.forEach((key, value) { | ||||||
|  |     final newKey = _upperCamelToLowerSnake(key); | ||||||
|  |  | ||||||
|  |     if (value is Map<String, dynamic>) { | ||||||
|  |       result[newKey] = convertMapKeysToSnakeCase(value); | ||||||
|  |     } else if (value is List) { | ||||||
|  |       result[newKey] = | ||||||
|  |           value.map((item) { | ||||||
|  |             if (item is Map<String, dynamic>) { | ||||||
|  |               return convertMapKeysToSnakeCase(item); | ||||||
|  |             } | ||||||
|  |             return item; | ||||||
|  |           }).toList(); | ||||||
|  |     } else { | ||||||
|  |       result[newKey] = value; | ||||||
|  |     } | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   return result; | ||||||
|  | } | ||||||
| @@ -1,5 +1,6 @@ | |||||||
| import 'dart:async'; | import 'dart:async'; | ||||||
|  |  | ||||||
|  | import 'package:easy_localization/easy_localization.dart'; | ||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
| import 'package:flutter_hooks/flutter_hooks.dart'; | import 'package:flutter_hooks/flutter_hooks.dart'; | ||||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
| @@ -44,9 +45,8 @@ class AccountPickerSheet extends HookConsumerWidget { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     return Container( |     return Container( | ||||||
|       constraints: BoxConstraints( |       padding: MediaQuery.of(context).viewInsets, | ||||||
|         maxHeight: MediaQuery.of(context).size.height * 0.4, |       height: MediaQuery.of(context).size.height * 0.6, | ||||||
|       ), |  | ||||||
|       child: Column( |       child: Column( | ||||||
|         children: [ |         children: [ | ||||||
|           Padding( |           Padding( | ||||||
| @@ -54,8 +54,8 @@ class AccountPickerSheet extends HookConsumerWidget { | |||||||
|             child: TextField( |             child: TextField( | ||||||
|               controller: searchController, |               controller: searchController, | ||||||
|               onChanged: onSearchChanged, |               onChanged: onSearchChanged, | ||||||
|               decoration: const InputDecoration( |               decoration: InputDecoration( | ||||||
|                 hintText: 'Search accounts...', |                 hintText: 'searchAccounts'.tr(), | ||||||
|                 contentPadding: EdgeInsets.symmetric( |                 contentPadding: EdgeInsets.symmetric( | ||||||
|                   horizontal: 18, |                   horizontal: 18, | ||||||
|                   vertical: 16, |                   vertical: 16, | ||||||
|   | |||||||
| @@ -130,9 +130,22 @@ class AccountStatusWidget extends HookConsumerWidget { | |||||||
|               size: 16, |               size: 16, | ||||||
|             ).padding(right: 4), |             ).padding(right: 4), | ||||||
|           if (status.value?.isCustomized ?? false) |           if (status.value?.isCustomized ?? false) | ||||||
|             Text(status.value?.label ?? 'unknown'.tr()) |             Flexible( | ||||||
|  |               child: Text( | ||||||
|  |                 status.value?.label ?? 'unknown'.tr(), | ||||||
|  |                 maxLines: 1, | ||||||
|  |                 overflow: TextOverflow.ellipsis, | ||||||
|  |               ), | ||||||
|  |             ) | ||||||
|           else |           else | ||||||
|             Text((status.value?.label ?? 'offline').toLowerCase()).tr(), |             Flexible( | ||||||
|  |               child: | ||||||
|  |                   Text( | ||||||
|  |                     (status.value?.label ?? 'offline').toLowerCase(), | ||||||
|  |                     maxLines: 1, | ||||||
|  |                     overflow: TextOverflow.ellipsis, | ||||||
|  |                   ).tr(), | ||||||
|  |             ), | ||||||
|           if (!(status.value?.isOnline ?? false) && |           if (!(status.value?.isOnline ?? false) && | ||||||
|               account.value?.profile.lastSeenAt != null) |               account.value?.profile.lastSeenAt != null) | ||||||
|             Flexible( |             Flexible( | ||||||
|   | |||||||
| @@ -55,7 +55,7 @@ class AccountStatusCreationSheet extends HookConsumerWidget { | |||||||
|             'attitude': attitude.value, |             'attitude': attitude.value, | ||||||
|             'is_invisible': isInvisible.value, |             'is_invisible': isInvisible.value, | ||||||
|             'is_not_disturb': isNotDisturb.value, |             'is_not_disturb': isNotDisturb.value, | ||||||
|             'cleared_at': clearedAt.value?.toIso8601String(), |             'cleared_at': clearedAt.value?.toUtc().toIso8601String(), | ||||||
|             if (labelController.text.isNotEmpty) 'label': labelController.text, |             if (labelController.text.isNotEmpty) 'label': labelController.text, | ||||||
|           }, |           }, | ||||||
|           options: Options(method: initialStatus == null ? 'POST' : 'PATCH'), |           options: Options(method: initialStatus == null ? 'POST' : 'PATCH'), | ||||||
|   | |||||||
| @@ -69,7 +69,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()), | ||||||
|                   ], |                   ], | ||||||
|   | |||||||
| @@ -331,7 +331,7 @@ class _WebSocketIndicator extends HookConsumerWidget { | |||||||
|     final user = ref.watch(userInfoProvider); |     final user = ref.watch(userInfoProvider); | ||||||
|     final websocketState = ref.watch(websocketStateProvider); |     final websocketState = ref.watch(websocketStateProvider); | ||||||
|     final indicatorHeight = |     final indicatorHeight = | ||||||
|         MediaQuery.of(context).padding.top + (isDesktop ? 27.5 : 60); |         MediaQuery.of(context).padding.top + (isDesktop ? 27.5 : 25); | ||||||
|  |  | ||||||
|     Color indicatorColor; |     Color indicatorColor; | ||||||
|     String indicatorText; |     String indicatorText; | ||||||
| @@ -343,7 +343,7 @@ class _WebSocketIndicator extends HookConsumerWidget { | |||||||
|       indicatorColor = Colors.teal; |       indicatorColor = Colors.teal; | ||||||
|       indicatorText = 'connectionReconnecting'; |       indicatorText = 'connectionReconnecting'; | ||||||
|     } else { |     } else { | ||||||
|       indicatorColor = Colors.orange; |       indicatorColor = Colors.red; | ||||||
|       indicatorText = 'connectionDisconnected'; |       indicatorText = 'connectionDisconnected'; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -2,8 +2,11 @@ import 'dart:async'; | |||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
| import 'package:flutter_hooks/flutter_hooks.dart'; | import 'package:flutter_hooks/flutter_hooks.dart'; | ||||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
|  | import 'package:island/pods/websocket.dart'; | ||||||
| import 'package:island/services/notify.dart'; | import 'package:island/services/notify.dart'; | ||||||
| import 'package:island/services/sharing_intent.dart'; | import 'package:island/services/sharing_intent.dart'; | ||||||
|  | import 'package:island/services/update_service.dart'; | ||||||
|  | import 'package:island/widgets/content/network_status_sheet.dart'; | ||||||
| import 'package:island/widgets/tour/tour.dart'; | import 'package:island/widgets/tour/tour.dart'; | ||||||
|  |  | ||||||
| class AppWrapper extends HookConsumerWidget { | class AppWrapper extends HookConsumerWidget { | ||||||
| @@ -19,12 +22,34 @@ class AppWrapper extends HookConsumerWidget { | |||||||
|       }); |       }); | ||||||
|       final sharingService = SharingIntentService(); |       final sharingService = SharingIntentService(); | ||||||
|       sharingService.initialize(context); |       sharingService.initialize(context); | ||||||
|  |       UpdateService().checkForUpdates(context); | ||||||
|       return () { |       return () { | ||||||
|         sharingService.dispose(); |         sharingService.dispose(); | ||||||
|         ntySubs?.cancel(); |         ntySubs?.cancel(); | ||||||
|       }; |       }; | ||||||
|     }, const []); |     }, const []); | ||||||
|  |  | ||||||
|  |     final wsNotifier = ref.watch(websocketStateProvider.notifier); | ||||||
|  |     final websocketState = ref.watch(websocketStateProvider); | ||||||
|  |  | ||||||
|  |     final networkStateShowing = useState(false); | ||||||
|  |  | ||||||
|  |     if (websocketState == WebSocketState.duplicateDevice()) { | ||||||
|  |       if (!networkStateShowing.value) { | ||||||
|  |         WidgetsBinding.instance.addPostFrameCallback((_) { | ||||||
|  |           networkStateShowing.value = true; | ||||||
|  |           showModalBottomSheet( | ||||||
|  |             context: context, | ||||||
|  |             isScrollControlled: true, | ||||||
|  |             isDismissible: false, | ||||||
|  |             builder: | ||||||
|  |                 (context) => | ||||||
|  |                     NetworkStatusSheet(onReconnect: () => wsNotifier.connect()), | ||||||
|  |           ).then((_) => networkStateShowing.value = false); | ||||||
|  |         }); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|     return TourTriggerWidget(child: child); |     return TourTriggerWidget(child: child); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -14,6 +14,7 @@ import 'package:island/models/embed.dart'; | |||||||
| import 'package:island/pods/call.dart'; | import 'package:island/pods/call.dart'; | ||||||
| import 'package:island/pods/translate.dart'; | import 'package:island/pods/translate.dart'; | ||||||
| import 'package:island/screens/chat/room.dart'; | import 'package:island/screens/chat/room.dart'; | ||||||
|  | import 'package:island/utils/mapping.dart'; | ||||||
| import 'package:island/widgets/account/account_name.dart'; | import 'package:island/widgets/account/account_name.dart'; | ||||||
| import 'package:island/widgets/account/account_pfc.dart'; | import 'package:island/widgets/account/account_pfc.dart'; | ||||||
| import 'package:island/widgets/app_scaffold.dart'; | import 'package:island/widgets/app_scaffold.dart'; | ||||||
| @@ -292,12 +293,11 @@ class MessageItem extends HookConsumerWidget { | |||||||
|                             ), |                             ), | ||||||
|                           if (remoteMessage.meta['embeds'] != null) |                           if (remoteMessage.meta['embeds'] != null) | ||||||
|                             ...((remoteMessage.meta['embeds'] as List<dynamic>) |                             ...((remoteMessage.meta['embeds'] as List<dynamic>) | ||||||
|                                 .where((embed) => embed['Type'] == 'link') |  | ||||||
|                                 .map( |                                 .map( | ||||||
|                                   (embed) => SnEmbedLink.fromJson( |                                   (embed) => convertMapKeysToSnakeCase(embed), | ||||||
|                                     embed as Map<String, dynamic>, |  | ||||||
|                                   ), |  | ||||||
|                                 ) |                                 ) | ||||||
|  |                                 .where((embed) => embed['type'] == 'link') | ||||||
|  |                                 .map((embed) => SnScrappedLink.fromJson(embed)) | ||||||
|                                 .map( |                                 .map( | ||||||
|                                   (link) => LayoutBuilder( |                                   (link) => LayoutBuilder( | ||||||
|                                     builder: (context, constraints) { |                                     builder: (context, constraints) { | ||||||
|   | |||||||
| @@ -36,7 +36,8 @@ Future<SnCheckInResult?> checkInResultToday(Ref ref) async { | |||||||
|  |  | ||||||
| class CheckInWidget extends HookConsumerWidget { | class CheckInWidget extends HookConsumerWidget { | ||||||
|   final EdgeInsets? margin; |   final EdgeInsets? margin; | ||||||
|   const CheckInWidget({super.key, this.margin}); |   final VoidCallback? onChecked; | ||||||
|  |   const CheckInWidget({super.key, this.margin, this.onChecked}); | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context, WidgetRef ref) { |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
| @@ -52,6 +53,7 @@ class CheckInWidget extends HookConsumerWidget { | |||||||
|         ref.invalidate(checkInResultTodayProvider); |         ref.invalidate(checkInResultTodayProvider); | ||||||
|         final userNotifier = ref.read(userInfoProvider.notifier); |         final userNotifier = ref.read(userInfoProvider.notifier); | ||||||
|         userNotifier.fetchUser(); |         userNotifier.fetchUser(); | ||||||
|  |         onChecked?.call(); | ||||||
|       } catch (err) { |       } catch (err) { | ||||||
|         if (err is DioException) { |         if (err is DioException) { | ||||||
|           if (err.response?.statusCode == 423 && context.mounted) { |           if (err.response?.statusCode == 423 && context.mounted) { | ||||||
|   | |||||||
| @@ -272,8 +272,96 @@ class AttachmentPreview extends HookConsumerWidget { | |||||||
|       borderRadius: BorderRadius.circular(8), |       borderRadius: BorderRadius.circular(8), | ||||||
|       child: Container( |       child: Container( | ||||||
|         color: Theme.of(context).colorScheme.surfaceContainer, |         color: Theme.of(context).colorScheme.surfaceContainer, | ||||||
|         child: Column( |         child: Stack( | ||||||
|           children: [ |           children: [ | ||||||
|  |             AspectRatio( | ||||||
|  |               aspectRatio: ratio, | ||||||
|  |               child: Stack( | ||||||
|  |                 fit: StackFit.expand, | ||||||
|  |                 children: [ | ||||||
|  |                   Builder( | ||||||
|  |                     key: ValueKey(item.hashCode), | ||||||
|  |                     builder: (context) { | ||||||
|  |                       if (item.isOnCloud) { | ||||||
|  |                         return CloudFileWidget(item: item.data); | ||||||
|  |                       } else if (item.data is XFile) { | ||||||
|  |                         final file = item.data as XFile; | ||||||
|  |                         if (file.path.isEmpty) { | ||||||
|  |                           return FutureBuilder<Uint8List>( | ||||||
|  |                             future: file.readAsBytes(), | ||||||
|  |                             builder: (context, snapshot) { | ||||||
|  |                               if (snapshot.hasData) { | ||||||
|  |                                 return Image.memory(snapshot.data!); | ||||||
|  |                               } | ||||||
|  |                               return const Center( | ||||||
|  |                                 child: CircularProgressIndicator(), | ||||||
|  |                               ); | ||||||
|  |                             }, | ||||||
|  |                           ); | ||||||
|  |                         } | ||||||
|  |  | ||||||
|  |                         switch (item.type) { | ||||||
|  |                           case UniversalFileType.image: | ||||||
|  |                             return kIsWeb | ||||||
|  |                                 ? Image.network(file.path) | ||||||
|  |                                 : Image.file(File(file.path)); | ||||||
|  |                           default: | ||||||
|  |                             return Column( | ||||||
|  |                               children: [ | ||||||
|  |                                 const Icon(Symbols.document_scanner), | ||||||
|  |                                 Text(file.name), | ||||||
|  |                               ], | ||||||
|  |                             ); | ||||||
|  |                         } | ||||||
|  |                       } else if (item is List<int> || item is Uint8List) { | ||||||
|  |                         switch (item.type) { | ||||||
|  |                           case UniversalFileType.image: | ||||||
|  |                             return Image.memory(item.data); | ||||||
|  |                           default: | ||||||
|  |                             return Column( | ||||||
|  |                               children: [const Icon(Symbols.document_scanner)], | ||||||
|  |                             ); | ||||||
|  |                         } | ||||||
|  |                       } | ||||||
|  |                       return Placeholder(); | ||||||
|  |                     }, | ||||||
|  |                   ), | ||||||
|  |                   if (progress != null) | ||||||
|  |                     Positioned.fill( | ||||||
|  |                       child: Container( | ||||||
|  |                         color: Colors.black.withOpacity(0.3), | ||||||
|  |                         padding: EdgeInsets.symmetric( | ||||||
|  |                           horizontal: 40, | ||||||
|  |                           vertical: 16, | ||||||
|  |                         ), | ||||||
|  |                         child: Column( | ||||||
|  |                           mainAxisAlignment: MainAxisAlignment.center, | ||||||
|  |                           crossAxisAlignment: CrossAxisAlignment.center, | ||||||
|  |                           children: [ | ||||||
|  |                             if (progress != null) | ||||||
|  |                               Text( | ||||||
|  |                                 '${progress!.toStringAsFixed(2)}%', | ||||||
|  |                                 style: TextStyle(color: Colors.white), | ||||||
|  |                               ) | ||||||
|  |                             else | ||||||
|  |                               Text( | ||||||
|  |                                 'uploading'.tr(), | ||||||
|  |                                 style: TextStyle(color: Colors.white), | ||||||
|  |                               ), | ||||||
|  |                             Gap(6), | ||||||
|  |                             Center( | ||||||
|  |                               child: LinearProgressIndicator( | ||||||
|  |                                 value: | ||||||
|  |                                     progress != null ? progress! / 100.0 : null, | ||||||
|  |                               ), | ||||||
|  |                             ), | ||||||
|  |                           ], | ||||||
|  |                         ), | ||||||
|  |                       ), | ||||||
|  |                     ), | ||||||
|  |                 ], | ||||||
|  |               ), | ||||||
|  |             ), | ||||||
|             Row( |             Row( | ||||||
|               mainAxisAlignment: MainAxisAlignment.spaceBetween, |               mainAxisAlignment: MainAxisAlignment.spaceBetween, | ||||||
|               children: [ |               children: [ | ||||||
| @@ -397,94 +485,6 @@ class AttachmentPreview extends HookConsumerWidget { | |||||||
|                   ), |                   ), | ||||||
|               ], |               ], | ||||||
|             ).padding(horizontal: 12, vertical: 8), |             ).padding(horizontal: 12, vertical: 8), | ||||||
|             AspectRatio( |  | ||||||
|               aspectRatio: ratio, |  | ||||||
|               child: Stack( |  | ||||||
|                 fit: StackFit.expand, |  | ||||||
|                 children: [ |  | ||||||
|                   Builder( |  | ||||||
|                     key: ValueKey(item.hashCode), |  | ||||||
|                     builder: (context) { |  | ||||||
|                       if (item.isOnCloud) { |  | ||||||
|                         return CloudFileWidget(item: item.data); |  | ||||||
|                       } else if (item.data is XFile) { |  | ||||||
|                         final file = item.data as XFile; |  | ||||||
|                         if (file.path.isEmpty) { |  | ||||||
|                           return FutureBuilder<Uint8List>( |  | ||||||
|                             future: file.readAsBytes(), |  | ||||||
|                             builder: (context, snapshot) { |  | ||||||
|                               if (snapshot.hasData) { |  | ||||||
|                                 return Image.memory(snapshot.data!); |  | ||||||
|                               } |  | ||||||
|                               return const Center( |  | ||||||
|                                 child: CircularProgressIndicator(), |  | ||||||
|                               ); |  | ||||||
|                             }, |  | ||||||
|                           ); |  | ||||||
|                         } |  | ||||||
|  |  | ||||||
|                         switch (item.type) { |  | ||||||
|                           case UniversalFileType.image: |  | ||||||
|                             return kIsWeb |  | ||||||
|                                 ? Image.network(file.path) |  | ||||||
|                                 : Image.file(File(file.path)); |  | ||||||
|                           default: |  | ||||||
|                             return Column( |  | ||||||
|                               children: [ |  | ||||||
|                                 const Icon(Symbols.document_scanner), |  | ||||||
|                                 Text(file.name), |  | ||||||
|                               ], |  | ||||||
|                             ); |  | ||||||
|                         } |  | ||||||
|                       } else if (item is List<int> || item is Uint8List) { |  | ||||||
|                         switch (item.type) { |  | ||||||
|                           case UniversalFileType.image: |  | ||||||
|                             return Image.memory(item.data); |  | ||||||
|                           default: |  | ||||||
|                             return Column( |  | ||||||
|                               children: [const Icon(Symbols.document_scanner)], |  | ||||||
|                             ); |  | ||||||
|                         } |  | ||||||
|                       } |  | ||||||
|                       return Placeholder(); |  | ||||||
|                     }, |  | ||||||
|                   ), |  | ||||||
|                   if (progress != null) |  | ||||||
|                     Positioned.fill( |  | ||||||
|                       child: Container( |  | ||||||
|                         color: Colors.black.withOpacity(0.3), |  | ||||||
|                         padding: EdgeInsets.symmetric( |  | ||||||
|                           horizontal: 40, |  | ||||||
|                           vertical: 16, |  | ||||||
|                         ), |  | ||||||
|                         child: Column( |  | ||||||
|                           mainAxisAlignment: MainAxisAlignment.center, |  | ||||||
|                           crossAxisAlignment: CrossAxisAlignment.center, |  | ||||||
|                           children: [ |  | ||||||
|                             if (progress != null) |  | ||||||
|                               Text( |  | ||||||
|                                 '${progress!.toStringAsFixed(2)}%', |  | ||||||
|                                 style: TextStyle(color: Colors.white), |  | ||||||
|                               ) |  | ||||||
|                             else |  | ||||||
|                               Text( |  | ||||||
|                                 'uploading'.tr(), |  | ||||||
|                                 style: TextStyle(color: Colors.white), |  | ||||||
|                               ), |  | ||||||
|                             Gap(6), |  | ||||||
|                             Center( |  | ||||||
|                               child: LinearProgressIndicator( |  | ||||||
|                                 value: |  | ||||||
|                                     progress != null ? progress! / 100.0 : null, |  | ||||||
|                               ), |  | ||||||
|                             ), |  | ||||||
|                           ], |  | ||||||
|                         ), |  | ||||||
|                       ), |  | ||||||
|                     ), |  | ||||||
|                 ], |  | ||||||
|               ), |  | ||||||
|             ), |  | ||||||
|           ], |           ], | ||||||
|         ), |         ), | ||||||
|       ), |       ), | ||||||
|   | |||||||
| @@ -31,6 +31,7 @@ class CloudFileList extends HookConsumerWidget { | |||||||
|   final bool disableZoomIn; |   final bool disableZoomIn; | ||||||
|   final bool disableConstraint; |   final bool disableConstraint; | ||||||
|   final EdgeInsets? padding; |   final EdgeInsets? padding; | ||||||
|  |   final bool isColumn; | ||||||
|   const CloudFileList({ |   const CloudFileList({ | ||||||
|     super.key, |     super.key, | ||||||
|     required this.files, |     required this.files, | ||||||
| @@ -40,6 +41,7 @@ class CloudFileList extends HookConsumerWidget { | |||||||
|     this.disableZoomIn = false, |     this.disableZoomIn = false, | ||||||
|     this.disableConstraint = false, |     this.disableConstraint = false, | ||||||
|     this.padding, |     this.padding, | ||||||
|  |     this.isColumn = false, | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
|   double calculateAspectRatio() { |   double calculateAspectRatio() { | ||||||
| @@ -63,6 +65,74 @@ class CloudFileList extends HookConsumerWidget { | |||||||
|     ); |     ); | ||||||
|  |  | ||||||
|     if (files.isEmpty) return const SizedBox.shrink(); |     if (files.isEmpty) return const SizedBox.shrink(); | ||||||
|  |  | ||||||
|  |     if (isColumn) { | ||||||
|  |       final children = <Widget>[]; | ||||||
|  |       const maxFiles = 2; | ||||||
|  |       final filesToShow = files.take(maxFiles).toList(); | ||||||
|  |  | ||||||
|  |       for (var i = 0; i < filesToShow.length; i++) { | ||||||
|  |         final file = filesToShow[i]; | ||||||
|  |         final isImage = file.mimeType?.startsWith('image') ?? false; | ||||||
|  |         final isAudio = file.mimeType?.startsWith('audio') ?? false; | ||||||
|  |         final widgetItem = ClipRRect( | ||||||
|  |           borderRadius: const BorderRadius.all(Radius.circular(8)), | ||||||
|  |           child: _CloudFileListEntry( | ||||||
|  |             file: file, | ||||||
|  |             heroTag: heroTags[i], | ||||||
|  |             isImage: isImage, | ||||||
|  |             disableZoomIn: disableZoomIn, | ||||||
|  |             onTap: () { | ||||||
|  |               if (!isImage) { | ||||||
|  |                 return; | ||||||
|  |               } | ||||||
|  |               if (!disableZoomIn) { | ||||||
|  |                 context.pushTransparentRoute( | ||||||
|  |                   CloudFileZoomIn(item: file, heroTag: heroTags[i]), | ||||||
|  |                   rootNavigator: true, | ||||||
|  |                 ); | ||||||
|  |               } | ||||||
|  |             }, | ||||||
|  |           ), | ||||||
|  |         ); | ||||||
|  |  | ||||||
|  |         Widget item; | ||||||
|  |         if (isAudio) { | ||||||
|  |           item = SizedBox(height: 120, child: widgetItem); | ||||||
|  |         } else { | ||||||
|  |           item = AspectRatio( | ||||||
|  |             aspectRatio: file.fileMeta?['ratio'] as double? ?? 1.0, | ||||||
|  |             child: widgetItem, | ||||||
|  |           ); | ||||||
|  |         } | ||||||
|  |         children.add(item); | ||||||
|  |         if (i < filesToShow.length - 1) { | ||||||
|  |           children.add(const Gap(8)); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       if (files.length > maxFiles) { | ||||||
|  |         children.add(const Gap(8)); | ||||||
|  |         children.add( | ||||||
|  |           Text( | ||||||
|  |             'filesListAdditional'.plural(files.length - filesToShow.length), | ||||||
|  |             textAlign: TextAlign.center, | ||||||
|  |             style: Theme.of(context).textTheme.bodyMedium?.copyWith( | ||||||
|  |               color: Theme.of(context).colorScheme.onSurface.withOpacity(0.7), | ||||||
|  |             ), | ||||||
|  |           ), | ||||||
|  |         ); | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       return Padding( | ||||||
|  |         padding: padding ?? EdgeInsets.zero, | ||||||
|  |         child: Column( | ||||||
|  |           mainAxisSize: MainAxisSize.min, | ||||||
|  |           crossAxisAlignment: CrossAxisAlignment.stretch, | ||||||
|  |           children: children, | ||||||
|  |         ), | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|     if (files.length == 1) { |     if (files.length == 1) { | ||||||
|       final isImage = files.first.mimeType?.startsWith('image') ?? false; |       final isImage = files.first.mimeType?.startsWith('image') ?? false; | ||||||
|       final isAudio = files.first.mimeType?.startsWith('audio') ?? false; |       final isAudio = files.first.mimeType?.startsWith('audio') ?? false; | ||||||
|   | |||||||
| @@ -1,5 +1,4 @@ | |||||||
| import 'dart:math' as math; | import 'dart:math' as math; | ||||||
| import 'dart:ui'; |  | ||||||
|  |  | ||||||
| import 'package:cached_network_image/cached_network_image.dart'; | import 'package:cached_network_image/cached_network_image.dart'; | ||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
| @@ -103,6 +102,7 @@ class CloudVideoWidget extends HookConsumerWidget { | |||||||
|                 Symbols.play_arrow, |                 Symbols.play_arrow, | ||||||
|                 fill: 1, |                 fill: 1, | ||||||
|                 size: 32, |                 size: 32, | ||||||
|  |                 color: Colors.white, | ||||||
|                 shadows: [ |                 shadows: [ | ||||||
|                   BoxShadow( |                   BoxShadow( | ||||||
|                     color: Colors.black54, |                     color: Colors.black54, | ||||||
| @@ -114,6 +114,26 @@ class CloudVideoWidget extends HookConsumerWidget { | |||||||
|               ), |               ), | ||||||
|             ), |             ), | ||||||
|           ), |           ), | ||||||
|  |           Positioned( | ||||||
|  |             bottom: 0, | ||||||
|  |             left: 0, | ||||||
|  |             right: 0, | ||||||
|  |             child: IgnorePointer( | ||||||
|  |               child: Container( | ||||||
|  |                 height: 100, | ||||||
|  |                 decoration: BoxDecoration( | ||||||
|  |                   gradient: LinearGradient( | ||||||
|  |                     begin: Alignment.bottomCenter, | ||||||
|  |                     end: Alignment.topCenter, | ||||||
|  |                     colors: [ | ||||||
|  |                       Theme.of(context).colorScheme.surface.withOpacity(0.85), | ||||||
|  |                       Colors.transparent, | ||||||
|  |                     ], | ||||||
|  |                   ), | ||||||
|  |                 ), | ||||||
|  |               ), | ||||||
|  |             ), | ||||||
|  |           ), | ||||||
|           Positioned( |           Positioned( | ||||||
|             bottom: 0, |             bottom: 0, | ||||||
|             left: 0, |             left: 0, | ||||||
| @@ -122,7 +142,7 @@ class CloudVideoWidget extends HookConsumerWidget { | |||||||
|               mainAxisAlignment: MainAxisAlignment.end, |               mainAxisAlignment: MainAxisAlignment.end, | ||||||
|               crossAxisAlignment: CrossAxisAlignment.stretch, |               crossAxisAlignment: CrossAxisAlignment.stretch, | ||||||
|               children: [ |               children: [ | ||||||
|                 Row( |                 Wrap( | ||||||
|                   spacing: 8, |                   spacing: 8, | ||||||
|                   children: [ |                   children: [ | ||||||
|                     if (item.fileMeta?['duration'] != null) |                     if (item.fileMeta?['duration'] != null) | ||||||
| @@ -133,6 +153,7 @@ class CloudVideoWidget extends HookConsumerWidget { | |||||||
|                                   .toInt(), |                                   .toInt(), | ||||||
|                         ).formatDuration(), |                         ).formatDuration(), | ||||||
|                         style: TextStyle( |                         style: TextStyle( | ||||||
|  |                           color: Colors.white, | ||||||
|                           shadows: [ |                           shadows: [ | ||||||
|                             BoxShadow( |                             BoxShadow( | ||||||
|                               color: Colors.black54, |                               color: Colors.black54, | ||||||
| @@ -147,6 +168,7 @@ class CloudVideoWidget extends HookConsumerWidget { | |||||||
|                       Text( |                       Text( | ||||||
|                         '${int.parse(item.fileMeta?['bit_rate'] as String) ~/ 1000} Kbps', |                         '${int.parse(item.fileMeta?['bit_rate'] as String) ~/ 1000} Kbps', | ||||||
|                         style: TextStyle( |                         style: TextStyle( | ||||||
|  |                           color: Colors.white, | ||||||
|                           shadows: [ |                           shadows: [ | ||||||
|                             BoxShadow( |                             BoxShadow( | ||||||
|                               color: Colors.black54, |                               color: Colors.black54, | ||||||
| @@ -161,7 +183,10 @@ class CloudVideoWidget extends HookConsumerWidget { | |||||||
|                 ), |                 ), | ||||||
|                 Text( |                 Text( | ||||||
|                   item.name, |                   item.name, | ||||||
|  |                   maxLines: 1, | ||||||
|  |                   overflow: TextOverflow.ellipsis, | ||||||
|                   style: TextStyle( |                   style: TextStyle( | ||||||
|  |                     color: Colors.white, | ||||||
|                     fontWeight: FontWeight.bold, |                     fontWeight: FontWeight.bold, | ||||||
|                     shadows: [ |                     shadows: [ | ||||||
|                       BoxShadow( |                       BoxShadow( | ||||||
| @@ -174,8 +199,8 @@ class CloudVideoWidget extends HookConsumerWidget { | |||||||
|                   ), |                   ), | ||||||
|                 ), |                 ), | ||||||
|               ], |               ], | ||||||
|             ), |             ).padding(horizontal: 16, bottom: 12), | ||||||
|           ).padding(horizontal: 16, bottom: 12), |           ), | ||||||
|         ], |         ], | ||||||
|       ), |       ), | ||||||
|       onTap: () { |       onTap: () { | ||||||
|   | |||||||
| @@ -6,7 +6,7 @@ import 'package:material_symbols_icons/symbols.dart'; | |||||||
| import 'package:url_launcher/url_launcher.dart'; | import 'package:url_launcher/url_launcher.dart'; | ||||||
|  |  | ||||||
| class EmbedLinkWidget extends StatelessWidget { | class EmbedLinkWidget extends StatelessWidget { | ||||||
|   final SnEmbedLink link; |   final SnScrappedLink link; | ||||||
|   final double? maxWidth; |   final double? maxWidth; | ||||||
|   final EdgeInsetsGeometry? margin; |   final EdgeInsetsGeometry? margin; | ||||||
|  |  | ||||||
| @@ -116,7 +116,8 @@ class EmbedLinkWidget extends StatelessWidget { | |||||||
|                     ], |                     ], | ||||||
|  |  | ||||||
|                     // Description |                     // Description | ||||||
|                     if (link.description != null && link.description!.isNotEmpty) ...[ |                     if (link.description != null && | ||||||
|  |                         link.description!.isNotEmpty) ...[ | ||||||
|                       Text( |                       Text( | ||||||
|                         link.description!, |                         link.description!, | ||||||
|                         style: theme.textTheme.bodyMedium?.copyWith( |                         style: theme.textTheme.bodyMedium?.copyWith( | ||||||
|   | |||||||
| @@ -6,6 +6,7 @@ import 'package:flutter/services.dart'; | |||||||
| import 'package:flutter_highlight/themes/a11y-dark.dart'; | import 'package:flutter_highlight/themes/a11y-dark.dart'; | ||||||
| import 'package:flutter_highlight/themes/a11y-light.dart'; | import 'package:flutter_highlight/themes/a11y-light.dart'; | ||||||
| import 'package:flutter_hooks/flutter_hooks.dart'; | import 'package:flutter_hooks/flutter_hooks.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/file.dart'; | import 'package:island/models/file.dart'; | ||||||
| import 'package:island/pods/config.dart'; | import 'package:island/pods/config.dart'; | ||||||
| @@ -71,7 +72,22 @@ class MarkdownTextContent extends HookConsumerWidget { | |||||||
|             textStyle: textStyle ?? Theme.of(context).textTheme.bodyMedium!, |             textStyle: textStyle ?? Theme.of(context).textTheme.bodyMedium!, | ||||||
|           ), |           ), | ||||||
|           HrConfig(height: 1, color: Theme.of(context).dividerColor), |           HrConfig(height: 1, color: Theme.of(context).dividerColor), | ||||||
|           PreConfig(theme: isDark ? a11yDarkTheme : a11yLightTheme), |           PreConfig( | ||||||
|  |             theme: isDark ? a11yDarkTheme : a11yLightTheme, | ||||||
|  |             textStyle: GoogleFonts.robotoMono(fontSize: 14), | ||||||
|  |             styleNotMatched: GoogleFonts.robotoMono(fontSize: 14), | ||||||
|  |             decoration: BoxDecoration( | ||||||
|  |               color: Theme.of(context).colorScheme.surfaceContainerHighest, | ||||||
|  |               borderRadius: BorderRadius.all(Radius.circular(8.0)), | ||||||
|  |             ), | ||||||
|  |           ), | ||||||
|  |           TableConfig( | ||||||
|  |             wrapper: | ||||||
|  |                 (child) => SingleChildScrollView( | ||||||
|  |                   scrollDirection: Axis.horizontal, | ||||||
|  |                   child: child, | ||||||
|  |                 ), | ||||||
|  |           ), | ||||||
|           LinkConfig( |           LinkConfig( | ||||||
|             style: |             style: | ||||||
|                 linkStyle ?? |                 linkStyle ?? | ||||||
| @@ -145,6 +161,8 @@ class MarkdownTextContent extends HookConsumerWidget { | |||||||
|                     ); |                     ); | ||||||
|                   case 'stickers': |                   case 'stickers': | ||||||
|                     final size = doesEnlargeSticker ? 96.0 : 24.0; |                     final size = doesEnlargeSticker ? 96.0 : 24.0; | ||||||
|  |                     final stickerUri = | ||||||
|  |                         '$baseUrl/sphere/stickers/lookup/${uri.pathSegments[0]}/open'; | ||||||
|                     return ClipRRect( |                     return ClipRRect( | ||||||
|                       borderRadius: const BorderRadius.all(Radius.circular(8)), |                       borderRadius: const BorderRadius.all(Radius.circular(8)), | ||||||
|                       child: Container( |                       child: Container( | ||||||
| @@ -155,11 +173,10 @@ class MarkdownTextContent extends HookConsumerWidget { | |||||||
|                           ), |                           ), | ||||||
|                         ), |                         ), | ||||||
|                         child: UniversalImage( |                         child: UniversalImage( | ||||||
|                           uri: |                           uri: stickerUri, | ||||||
|                               '$baseUrl/sphere/stickers/lookup/${uri.pathSegments[0]}/open', |  | ||||||
|                           width: size, |                           width: size, | ||||||
|                           height: size, |                           height: size, | ||||||
|                           fit: BoxFit.cover, |                           fit: BoxFit.contain, | ||||||
|                           noCacheOptimization: true, |                           noCacheOptimization: true, | ||||||
|                         ), |                         ), | ||||||
|                       ), |                       ), | ||||||
|   | |||||||
							
								
								
									
										81
									
								
								lib/widgets/content/network_status_sheet.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								lib/widgets/content/network_status_sheet.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,81 @@ | |||||||
|  | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
|  | import 'package:island/pods/websocket.dart'; | ||||||
|  | import 'package:material_symbols_icons/symbols.dart'; | ||||||
|  | import 'package:island/widgets/content/sheet.dart'; | ||||||
|  |  | ||||||
|  | class NetworkStatusSheet extends HookConsumerWidget { | ||||||
|  |   final VoidCallback onReconnect; | ||||||
|  |  | ||||||
|  |   const NetworkStatusSheet({super.key, required this.onReconnect}); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|  |     final ws = ref.watch(websocketProvider); | ||||||
|  |     final wsState = ref.watch(websocketStateProvider); | ||||||
|  |  | ||||||
|  |     return SheetScaffold( | ||||||
|  |       titleText: | ||||||
|  |           wsState == WebSocketState.connected() | ||||||
|  |               ? 'Connection Status' | ||||||
|  |               : 'Connection Issue', | ||||||
|  |       child: Padding( | ||||||
|  |         padding: const EdgeInsets.all(20), | ||||||
|  |         child: Column( | ||||||
|  |           crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|  |           children: [ | ||||||
|  |             wsState.when( | ||||||
|  |               connected: | ||||||
|  |                   () => Text( | ||||||
|  |                     'Connected to server', | ||||||
|  |                     style: Theme.of(context).textTheme.bodyLarge, | ||||||
|  |                   ), | ||||||
|  |               connecting: | ||||||
|  |                   () => Text( | ||||||
|  |                     'Connecting to server...', | ||||||
|  |                     style: Theme.of(context).textTheme.bodyLarge, | ||||||
|  |                   ), | ||||||
|  |               disconnected: | ||||||
|  |                   () => Text( | ||||||
|  |                     'Disconnected from server', | ||||||
|  |                     style: Theme.of(context).textTheme.bodyLarge, | ||||||
|  |                   ), | ||||||
|  |               serverDown: | ||||||
|  |                   () => Text( | ||||||
|  |                     'The server is not available right now... Please try again later...', | ||||||
|  |                     style: Theme.of(context).textTheme.bodyLarge, | ||||||
|  |                   ), | ||||||
|  |               duplicateDevice: | ||||||
|  |                   () => Text( | ||||||
|  |                     'Another device has connected with the same account.', | ||||||
|  |                     style: Theme.of(context).textTheme.bodyLarge, | ||||||
|  |                   ), | ||||||
|  |               error: | ||||||
|  |                   (message) => Text( | ||||||
|  |                     'Connection error: $message', | ||||||
|  |                     style: Theme.of(context).textTheme.bodyLarge, | ||||||
|  |                   ), | ||||||
|  |             ), | ||||||
|  |             const SizedBox(height: 16), | ||||||
|  |             if (ws.heartbeatDelay != null) | ||||||
|  |               Text( | ||||||
|  |                 'Last heartbeat: ${ws.heartbeatDelay!.inMilliseconds}ms', | ||||||
|  |                 style: Theme.of(context).textTheme.bodyMedium, | ||||||
|  |               ), | ||||||
|  |             const SizedBox(height: 24), | ||||||
|  |             Center( | ||||||
|  |               child: FilledButton.icon( | ||||||
|  |                 icon: const Icon(Symbols.wifi), | ||||||
|  |                 label: const Text('Reconnect'), | ||||||
|  |                 onPressed: () { | ||||||
|  |                   onReconnect(); | ||||||
|  |                   Navigator.pop(context); | ||||||
|  |                 }, | ||||||
|  |               ), | ||||||
|  |             ), | ||||||
|  |           ], | ||||||
|  |         ), | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										76
									
								
								lib/widgets/debug_sheet.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								lib/widgets/debug_sheet.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,76 @@ | |||||||
|  | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:flutter/services.dart'; | ||||||
|  | import 'package:flutter_cache_manager/flutter_cache_manager.dart'; | ||||||
|  | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
|  | import 'package:island/pods/message.dart'; | ||||||
|  | import 'package:island/pods/network.dart'; | ||||||
|  | import 'package:island/pods/websocket.dart'; | ||||||
|  | import 'package:island/widgets/content/network_status_sheet.dart'; | ||||||
|  | import 'package:island/widgets/content/sheet.dart'; | ||||||
|  | import 'package:material_symbols_icons/symbols.dart'; | ||||||
|  |  | ||||||
|  | class DebugSheet extends HookConsumerWidget { | ||||||
|  |   const DebugSheet({super.key}); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|  |     final wsNotifier = ref.watch(websocketStateProvider.notifier); | ||||||
|  |  | ||||||
|  |     return SheetScaffold( | ||||||
|  |       titleText: 'Debug', | ||||||
|  |       child: Column( | ||||||
|  |         children: [ | ||||||
|  |           ListTile( | ||||||
|  |             minTileHeight: 48, | ||||||
|  |             leading: const Icon(Symbols.wifi), | ||||||
|  |             trailing: const Icon(Symbols.chevron_right), | ||||||
|  |             title: Text('Connection Status'), | ||||||
|  |             contentPadding: EdgeInsets.symmetric(horizontal: 24), | ||||||
|  |             onTap: () { | ||||||
|  |               showModalBottomSheet( | ||||||
|  |                 context: context, | ||||||
|  |                 isScrollControlled: true, | ||||||
|  |                 builder: | ||||||
|  |                     (context) => NetworkStatusSheet( | ||||||
|  |                       onReconnect: () => wsNotifier.connect(), | ||||||
|  |                     ), | ||||||
|  |               ); | ||||||
|  |             }, | ||||||
|  |           ), | ||||||
|  |           const Divider(height: 1), | ||||||
|  |           ListTile( | ||||||
|  |             minTileHeight: 48, | ||||||
|  |             leading: const Icon(Symbols.copy_all), | ||||||
|  |             trailing: const Icon(Symbols.chevron_right), | ||||||
|  |             contentPadding: EdgeInsets.symmetric(horizontal: 24), | ||||||
|  |             title: Text('Copy access token'), | ||||||
|  |             onTap: () async { | ||||||
|  |               final tk = ref.watch(tokenProvider); | ||||||
|  |               Clipboard.setData(ClipboardData(text: tk!.token)); | ||||||
|  |             }, | ||||||
|  |           ), | ||||||
|  |           ListTile( | ||||||
|  |             minTileHeight: 48, | ||||||
|  |             leading: const Icon(Symbols.delete), | ||||||
|  |             trailing: const Icon(Symbols.chevron_right), | ||||||
|  |             contentPadding: EdgeInsets.symmetric(horizontal: 24), | ||||||
|  |             title: Text('Reset database'), | ||||||
|  |             onTap: () async { | ||||||
|  |               resetDatabase(ref); | ||||||
|  |             }, | ||||||
|  |           ), | ||||||
|  |           ListTile( | ||||||
|  |             minTileHeight: 48, | ||||||
|  |             leading: const Icon(Symbols.clear), | ||||||
|  |             trailing: const Icon(Symbols.chevron_right), | ||||||
|  |             contentPadding: EdgeInsets.symmetric(horizontal: 24), | ||||||
|  |             title: Text('Clear cache'), | ||||||
|  |             onTap: () async { | ||||||
|  |               DefaultCacheManager().emptyCache(); | ||||||
|  |             }, | ||||||
|  |           ), | ||||||
|  |         ], | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -248,7 +248,7 @@ class _PaymentContentState extends ConsumerState<_PaymentContent> { | |||||||
|     try { |     try { | ||||||
|       final client = ref.read(apiClientProvider); |       final client = ref.read(apiClientProvider); | ||||||
|       final response = await client.post( |       final response = await client.post( | ||||||
|         '/orders/${widget.order.id}/pay', |         '/id/orders/${widget.order.id}/pay', | ||||||
|         data: {'pin_code': pin}, |         data: {'pin_code': pin}, | ||||||
|       ); |       ); | ||||||
|  |  | ||||||
|   | |||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user