Compare commits
	
		
			92 Commits
		
	
	
		
			3.0.0+93
			...
			91c5a2e1b6
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 91c5a2e1b6 | |||
| cb991d1574 | |||
| 75097ab6fc | |||
| 8d855867c1 | |||
| 89fd80bcb8 | |||
| ab4f4faafe | |||
| 52111c4b95 | |||
| 15a5848785 | |||
| e29a2fc054 | |||
| 7f4e489f51 | |||
| eb4d2c2e2f | |||
| 9b67d58ee4 | |||
| 4dbee27718 | |||
| 4b9c9aec92 | |||
| 00b3dc7be6 | |||
| 7f26196e85 | |||
| 3e5669780f | |||
| 484ded03b1 | |||
| b3786827ef | |||
| 217a0c0a54 | |||
| 5c0f7225e6 | |||
| a7cb7170b8 | |||
| 2fac5e5383 | |||
| 00b9c4b957 | |||
| 6fbf3d9fc4 | |||
| 36fb06b81c | |||
| 129c215a02 | |||
| 0f125f45f0 | |||
| 30416f7ca0 | |||
| 6e74cf3a93 | |||
| 0d6424e155 | |||
| 0424eb0c2a | |||
| be01bac5db | |||
| 7bb4e68054 | |||
| 61e61866d7 | |||
| 280c261ea1 | |||
| 49bc6b9a98 | |||
| 461f32545a | |||
| 36b9026e9e | |||
| 78f258dcea | |||
| 044fb983d6 | |||
| 2a7876e22f | |||
| 0bbfa6ddde | |||
| 1f46f89056 | |||
| 7740cf7830 | |||
| d165e488ad | |||
| 37f5c61905 | |||
| 723e17ff47 | |||
| 20e6cc4283 | |||
| b2a118bbd0 | |||
| 3dd7c8a5b2 | |||
| d832729278 | |||
| dff8532229 | |||
| bf77bfce64 | |||
|  | accb99bb24 | ||
|  | e67429e513 | ||
|  | a87f5d61e6 | ||
| b84fafb53c | |||
| 9e9d8b6fab | |||
| 088cb4d5a2 | |||
| 33e84805d7 | |||
| 9aca6eb674 | |||
| e431a54a89 | |||
| c00987dfdc | |||
| 4187ceb248 | |||
| 152e076d44 | |||
| 7f36c86c55 | |||
| 311420e1f7 | |||
| afe1c700eb | |||
| b0c1981c9a | |||
| d943275ed5 | |||
| 010a49251c | |||
| bdc13978c3 | |||
| 5d8c73e468 | |||
| 360572d7d1 | |||
| 9c1a983466 | |||
| bfa97dcd11 | |||
| aaa505e83e | |||
| 552bdfa58f | |||
| ebde4eeed5 | |||
| 688f035f85 | |||
| fd587270da | |||
| 6b4189a93b | |||
| fed8171b36 | |||
| f39a066f71 | |||
| edf4ff1c5b | |||
| 9abc61a310 | |||
| 205d52df46 | |||
| a2053aa772 | |||
| 81103ba8b6 | |||
| 76faa4172f | |||
| 9866c22746 | 
							
								
								
									
										83
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,83 @@ | ||||
| name: Build Release | ||||
|  | ||||
| on: | ||||
|   push: | ||||
|     tags: | ||||
|       - '*' | ||||
|   workflow_dispatch: | ||||
|  | ||||
| jobs: | ||||
|   build-web: | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - name: Clone repository | ||||
|         uses: actions/checkout@v4 | ||||
|       - name: Set up Flutter | ||||
|         uses: subosito/flutter-action@v2 | ||||
|         with: | ||||
|           channel: stable | ||||
|           cache: true | ||||
|       - run: flutter pub get | ||||
|       - run: flutter build web --release | ||||
|       - name: Archive production artifacts | ||||
|         uses: actions/upload-artifact@v4 | ||||
|         with: | ||||
|           name: build-output-web | ||||
|           path: build/web | ||||
|   build-exe: | ||||
|     runs-on: windows-latest | ||||
|     steps: | ||||
|       - name: Clone repository | ||||
|         uses: actions/checkout@v4 | ||||
|       - name: Set up Flutter | ||||
|         uses: subosito/flutter-action@v2 | ||||
|         with: | ||||
|           channel: stable | ||||
|           cache: true | ||||
|       - run: flutter pub get | ||||
|       - run: flutter build windows | ||||
|       - name: Archive production artifacts | ||||
|         uses: actions/upload-artifact@v4 | ||||
|         with: | ||||
|           name: build-output-windows | ||||
|           path: build/windows/x64/runner/Release | ||||
|   build-linux: | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - name: Clone repository | ||||
|         uses: actions/checkout@v4 | ||||
|       - name: Set up Flutter | ||||
|         uses: subosito/flutter-action@v2 | ||||
|         with: | ||||
|           channel: stable | ||||
|       - run: | | ||||
|           sudo apt-get update -y | ||||
|           sudo apt-get install -y ninja-build libgtk-3-dev | ||||
|           sudo apt-get install -y libmpv-dev mpv | ||||
|           sudo apt-get install -y libayatana-appindicator3-dev | ||||
|           sudo apt-get install -y keybinder-3.0 | ||||
|           sudo apt-get install -y libnotify-dev | ||||
|           sudo apt-get install -y libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev | ||||
|           sudo apt-get install -y gstreamer-1.0 | ||||
|       - run: flutter pub get | ||||
|       - run: flutter build linux | ||||
|       - name: Archive production artifacts | ||||
|         uses: actions/upload-artifact@v4 | ||||
|         with: | ||||
|           name: build-output-linux | ||||
|           path: build/linux/x64/release/bundle | ||||
|       - name: Build AppImage | ||||
|         run: | | ||||
|           rm -r Solian.AppDir | true | ||||
|           mkdir Solian.AppDir | ||||
|           cp -r build/linux/x64/release/bundle/* Solian.AppDir | ||||
|           cp -r buildtools/appimage_config/* Solian.AppDir | ||||
|           cp assets/icons/icon-padded.png Solian.AppDir | ||||
|           sudo chmod +x buildtools/appimagetool-x86_64.AppImage | ||||
|           sudo chmod +x Solian.AppDir/AppRun | ||||
|           ./buildtools/appimagetool-x86_64.AppImage Solian.AppDir | ||||
|       - name: Archive production artifacts | ||||
|         uses: actions/upload-artifact@v4 | ||||
|         with: | ||||
|           name: build-output-linux-appimage | ||||
|           path: './*.AppImage*' | ||||
							
								
								
									
										1
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| {} | ||||
| @@ -11,16 +11,14 @@ plugins { | ||||
| android { | ||||
|     namespace = "dev.solsynth.solian" | ||||
|     compileSdk = flutter.compileSdkVersion | ||||
|     ndkVersion = "27.0.12077973" | ||||
|     ndkVersion = "29.0.13113456" | ||||
|  | ||||
|     compileOptions { | ||||
|         sourceCompatibility = JavaVersion.VERSION_17 | ||||
|         targetCompatibility = JavaVersion.VERSION_17 | ||||
|     } | ||||
|  | ||||
|     kotlinOptions { | ||||
|         jvmTarget = JavaVersion.VERSION_17.toString() | ||||
|     } | ||||
|     kotlinOptions { jvmTarget = JavaVersion.VERSION_17.toString() } | ||||
|  | ||||
|     defaultConfig { | ||||
|         applicationId = "dev.solsynth.solian" | ||||
| @@ -32,11 +30,20 @@ android { | ||||
|         versionName = flutter.versionName | ||||
|     } | ||||
|  | ||||
|     signingConfigs { | ||||
|         release { | ||||
|             keyAlias = keystoreProperties['keyAlias'] | ||||
|             keyPassword = keystoreProperties['keyPassword'] | ||||
|             storeFile = keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null | ||||
|             storePassword = keystoreProperties['storePassword'] | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     buildTypes { | ||||
|         release { | ||||
|             // TODO: Add your own signing config for the release build. | ||||
|             // Signing with the debug keys for now, so `flutter run --release` works. | ||||
|             signingConfig = signingConfigs.getByName("debug") | ||||
|             signingConfig = signingConfigs.getByName("release") | ||||
|             minifyEnabled = true | ||||
|             shrinkResources = true | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -34,19 +34,38 @@ | ||||
|                  while the Flutter UI initializes. After that, this theme continues | ||||
|                  to determine the Window background behind the Flutter UI. --> | ||||
|             <meta-data | ||||
|               android:name="io.flutter.embedding.android.NormalTheme" | ||||
|               android:resource="@style/NormalTheme" | ||||
|               /> | ||||
|                 android:name="io.flutter.embedding.android.NormalTheme" | ||||
|                 android:resource="@style/NormalTheme" | ||||
|             /> | ||||
|             <intent-filter> | ||||
|                 <action android:name="android.intent.action.MAIN"/> | ||||
|                 <category android:name="android.intent.category.LAUNCHER"/> | ||||
|                 <action android:name="android.intent.action.MAIN" /> | ||||
|                 <category android:name="android.intent.category.LAUNCHER" /> | ||||
|             </intent-filter> | ||||
|         </activity> | ||||
|         <provider | ||||
|             android:name="com.superlist.super_native_extensions.DataProvider" | ||||
|             android:authorities="dev.solsynth.solian.SuperClipboardDataProvider" | ||||
|  | ||||
|         <!-- Sign in with Apple --> | ||||
|         <activity | ||||
|             android:name="com.aboutyou.dart_packages.sign_in_with_apple.SignInWithAppleCallback" | ||||
|             android:exported="true" | ||||
|             android:grantUriPermissions="true" > | ||||
|         > | ||||
|             <intent-filter> | ||||
|                 <action android:name="android.intent.action.VIEW" /> | ||||
|                 <category android:name="android.intent.category.DEFAULT" /> | ||||
|                 <category android:name="android.intent.category.BROWSABLE" /> | ||||
|  | ||||
|                 <data android:scheme="signinwithapple" /> | ||||
|                 <data android:path="callback" /> | ||||
|             </intent-filter> | ||||
|         </activity> | ||||
|  | ||||
|         <provider | ||||
|             android:name="androidx.core.content.FileProvider" | ||||
|             android:authorities="dev.solsynth.solian.provider" | ||||
|             android:exported="false" | ||||
|             android:grantUriPermissions="true"> | ||||
|             <meta-data | ||||
|                 android:name="android.support.FILE_PROVIDER_PATHS" | ||||
|                 android:resource="@xml/provider_paths" /> | ||||
|         </provider> | ||||
|         <!-- Don't delete the meta-data below. | ||||
|              This is used by the Flutter tool to generate GeneratedPluginRegistrant.java --> | ||||
| @@ -61,8 +80,8 @@ | ||||
|          In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. --> | ||||
|     <queries> | ||||
|         <intent> | ||||
|             <action android:name="android.intent.action.PROCESS_TEXT"/> | ||||
|             <data android:mimeType="text/plain"/> | ||||
|             <action android:name="android.intent.action.PROCESS_TEXT" /> | ||||
|             <data android:mimeType="text/plain" /> | ||||
|         </intent> | ||||
|     </queries> | ||||
| </manifest> | ||||
| </manifest> | ||||
| @@ -1,5 +0,0 @@ | ||||
| package dev.solsynth.solian | ||||
|  | ||||
| import io.flutter.embedding.android.FlutterActivity | ||||
|  | ||||
| class MainActivity : FlutterActivity() | ||||
| @@ -0,0 +1,14 @@ | ||||
| package dev.solsynth.solian | ||||
|  | ||||
| import io.flutter.embedding.android.FlutterActivity | ||||
| import io.flutter.embedding.engine.FlutterEngine | ||||
| import io.flutter.plugins.sharedpreferences.LegacySharedPreferencesPlugin | ||||
|  | ||||
| class MainActivity : FlutterActivity() | ||||
| { | ||||
|     override fun configureFlutterEngine(flutterEngine: FlutterEngine) { | ||||
|         super.configureFlutterEngine(flutterEngine) | ||||
|         // https://github.com/flutter/flutter/issues/153075#issuecomment-2693189362 | ||||
|         flutterEngine.plugins.add(LegacySharedPreferencesPlugin()) | ||||
|     } | ||||
| } | ||||
							
								
								
									
										6
									
								
								android/app/src/main/res/xml/provider_paths.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								android/app/src/main/res/xml/provider_paths.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <paths xmlns:android="http://schemas.android.com/apk/res/android"> | ||||
|     <external-path | ||||
|         name="external_files" | ||||
|         path="." /> | ||||
| </paths> | ||||
							
								
								
									
										663
									
								
								android/build/reports/problems/problems-report.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										663
									
								
								android/build/reports/problems/problems-report.html
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME | ||||
| distributionPath=wrapper/dists | ||||
| zipStoreBase=GRADLE_USER_HOME | ||||
| zipStorePath=wrapper/dists | ||||
| distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip | ||||
| distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-all.zip | ||||
|   | ||||
| @@ -18,7 +18,7 @@ pluginManagement { | ||||
|  | ||||
| plugins { | ||||
|     id("dev.flutter.flutter-plugin-loader") version "1.0.0" | ||||
|     id("com.android.application") version "8.7.0" apply false | ||||
|     id("com.android.application") version "8.10.1" apply false | ||||
|     // START: FlutterFire Configuration | ||||
|     id("com.google.gms.google-services") version("4.3.15") apply false | ||||
|     // END: FlutterFire Configuration | ||||
|   | ||||
| @@ -10,6 +10,8 @@ | ||||
|   "loginEnterPassword": "Enter the code", | ||||
|   "loginSuccess": "Logged in as {}", | ||||
|   "loginGreeting": "Welcome back!", | ||||
|   "loginOr": "Or login with\nthird parties", | ||||
|   "loginInProgress": "Logging you in...", | ||||
|   "username": "Username", | ||||
|   "usernameCannotChangeHint": "Username cannot be updated after created.", | ||||
|   "usernameLookupHint": "We also take your email address.", | ||||
| @@ -27,7 +29,7 @@ | ||||
|   "fieldCannotBeEmpty": "This field cannot be empty.", | ||||
|   "fieldEmailAddressMustBeValid": "The email address must be valid.", | ||||
|   "logout": "Logout", | ||||
|   "updateYourProfile": "Edit Profile", | ||||
|   "updateYourProfile": "Profile Settings", | ||||
|   "accountBasicInfo": "Basic Info", | ||||
|   "accountProfile": "Your Profile", | ||||
|   "saveChanges": "Save Changes", | ||||
| @@ -98,12 +100,28 @@ | ||||
|   "permissionModerator": "Moderator", | ||||
|   "permissionMember": "Member", | ||||
|   "reply": "Reply", | ||||
|   "repliesCount": { | ||||
|     "zero": "No reply", | ||||
|     "one": "{} reply", | ||||
|     "other": "{} replies" | ||||
|   }, | ||||
|   "forward": "Forward", | ||||
|   "repliedTo": "Replied to", | ||||
|   "forwarded": "Forwarded", | ||||
|   "hasAttachments": { | ||||
|     "one": "{} attachment", | ||||
|     "other": "{} attachments" | ||||
|   }, | ||||
|   "postHasAttachments": { | ||||
|     "one": "{} attachment", | ||||
|     "other": "{} attachments" | ||||
|   }, | ||||
|   "edited": "Edited", | ||||
|   "addVideo": "Add video", | ||||
|   "addPhoto": "Add photo", | ||||
|   "addFile": "Add file", | ||||
|   "createDirectMessage": "New direct message", | ||||
|   "createDirectMessage": "Send new DM", | ||||
|   "gotoDirectMessage": "Go to DM", | ||||
|   "react": "React", | ||||
|   "reactions": { | ||||
|     "zero": "Reactions", | ||||
| @@ -116,6 +134,24 @@ | ||||
|   "connectionConnected": "Connected", | ||||
|   "connectionDisconnected": "Disconnected", | ||||
|   "connectionReconnecting": "Reconnecting", | ||||
|   "accountConnections": "Account Connections", | ||||
|   "accountConnectionsDescription": "Manage your external account connections", | ||||
|   "accountConnectionAdd": "Add Connection", | ||||
|   "accountConnectionDelete": "Delete Connection", | ||||
|   "accountConnectionDeleteHint": "Are you sure you want to delete this connection? This action cannot be undone.", | ||||
|   "accountConnectionsEmpty": "No connections found. Add a connection to get started.", | ||||
|   "accountConnectionProvider": "Provider", | ||||
|   "accountConnectionProviderHint": "Enter provider name", | ||||
|   "accountConnectionIdentifier": "Identifier", | ||||
|   "accountConnectionIdentifierHint": "Enter your identifier for this provider", | ||||
|   "accountConnectionDescription": "Add a connection to link your account with external services.", | ||||
|   "accountConnectionAddSuccess": "Connection added successfully.", | ||||
|   "accountConnectionAddError": "Unable to setup connection.", | ||||
|   "accountConnectionProviderApple": "Apple", | ||||
|   "accountConnectionProviderMicrosoft": "Microsoft", | ||||
|   "accountConnectionProviderGoogle": "Google", | ||||
|   "accountConnectionProviderGithub": "GitHub", | ||||
|   "accountConnectionProviderDiscord": "Discord", | ||||
|   "checkIn": "Check In", | ||||
|   "checkInNone": "Not checked-in yet", | ||||
|   "checkInNoneHint": "Get your fortune tips and daily rewards by checking in.", | ||||
| @@ -124,14 +160,11 @@ | ||||
|   "checkInResultLevel2": "A Normal Day", | ||||
|   "checkInResultLevel3": "Good Luck", | ||||
|   "checkInResultLevel4": "Best Luck", | ||||
|   "checkInResultLevelShort0": "Wrost", | ||||
|   "checkInResultLevelShort1": "Bad", | ||||
|   "checkInResultLevelShort2": "Normal", | ||||
|   "checkInResultLevelShort3": "Good", | ||||
|   "checkInResultLevelShort4": "Best", | ||||
|   "checkInActivityTitle": "{} checked in on {} and got a {}", | ||||
|   "eventCalander": "Event Calander", | ||||
|   "eventCalanderEmpty": "No events on that day.", | ||||
|   "fortuneGraph": "Fortune Trend", | ||||
|   "noFortuneData": "No fortune data available for this month.", | ||||
|   "creatorHub": "Creator Hub", | ||||
|   "creatorHubDescription": "Manage posts, analytics, and more.", | ||||
|   "developerPortal": "Developer Portal", | ||||
| @@ -155,7 +188,7 @@ | ||||
|   "status": "Status", | ||||
|   "statusActivityTitle": "{} is {} {}", | ||||
|   "statusActivityEndedTitle": "{} is {} {} until {}", | ||||
|   "appSettings": "App settings", | ||||
|   "appSettings": "App Settings", | ||||
|   "accountSettings": "Account Settings", | ||||
|   "settings": "Settings", | ||||
|   "language": "Language", | ||||
| @@ -223,6 +256,7 @@ | ||||
|   "creatorHubUnselectedHint": "Pick / create a publisher to get started.", | ||||
|   "relationships": "Relationships", | ||||
|   "addFriend": "Send a Friend Request", | ||||
|   "addFriendShort": "Add as Friend", | ||||
|   "addFriendHint": "Add a friend to your relationship list.", | ||||
|   "pendingRequest": "Pending", | ||||
|   "waitingRequest": "Waiting", | ||||
| @@ -267,16 +301,163 @@ | ||||
|   "posts": "Posts", | ||||
|   "settingsBackgroundImage": "Background Image", | ||||
|   "settingsBackgroundImageClear": "Clear Background Image", | ||||
|   "settingsBackgroundGenerateColor": "Generate color scheme from Bacground Image", | ||||
|   "messageNone": "No content to display", | ||||
|   "unreadMessages": { | ||||
|     "one": "{} unread message", | ||||
|     "other": "{} unread messages" | ||||
|   }, | ||||
|   "chatBreakNone": "None", | ||||
|   "settingsRealmCompactView": "Compact Realm View", | ||||
|   "settingsMixedFeed": "Mixed Feed", | ||||
|   "settingsAutoTranslate": "Auto Translate", | ||||
|   "settingsHideBottomNav": "Hide Bottom Navigation", | ||||
|   "settingsSoundEffects": "Sound Effects", | ||||
|   "settingsAprilFoolFeatures": "April Fool Features", | ||||
|   "settingsEnterToSend": "Enter to Send" | ||||
|   "settingsEnterToSend": "Enter to Send", | ||||
|   "settingsTransparentAppBar": "Transparent App Bar", | ||||
|   "settingsCustomFonts": "Custom Fonts", | ||||
|   "settingsCustomFontsHint": "Custom fonts will be used for all text in the app. Make sure it is installed on your device.", | ||||
|   "settingsColorScheme": "Color Scheme", | ||||
|   "postTitle": "Title", | ||||
|   "postDescription": "Description", | ||||
|   "call": "Call", | ||||
|   "done": "Done", | ||||
|   "loginResetPasswordSent": "Password reset link sent, please check your email inbox.", | ||||
|   "accountDeletion": "Delete Account", | ||||
|   "accountDeletionHint": "Are you sure to delete your account? If you confirmed, we will send an confirmation email to your primary email address, you can continue the deletion process by follow the insturctions in the email.", | ||||
|   "accountDeletionSent": "Account deletion confirmation email sent, please check your email inbox.", | ||||
|   "accountSecurityTitle": "Security", | ||||
|   "accountDangerZoneTitle": "Danger Zone", | ||||
|   "accountPassword": "Password", | ||||
|   "accountPasswordDescription": "Change your account password", | ||||
|   "accountPasswordChange": "Change Password", | ||||
|   "accountPasswordChangeSent": "Password reset link sent, please check your email inbox.", | ||||
|   "accountPasswordChangeDescription": "We will send an email to your primary email address to reset your password.", | ||||
|   "accountAuthFactor": "Auth factors", | ||||
|   "accountAuthFactorDescription": "Multi-factor authentication to ensure safety and convience", | ||||
|   "accountDeletionDescription": "Permanently delete your account and all your data", | ||||
|   "accountSettingsHelp": "Account Settings Help", | ||||
|   "accountSettingsHelpContent": "This page allows you to manage your account security, privacy, and other settings. If you need assistance, please contact support.", | ||||
|   "unauthorized": "Unauthorized", | ||||
|   "unauthorizedHint": "You're not signed in or session expired, please sign in again.", | ||||
|   "publisherBelongsTo": "Belongs to {}", | ||||
|   "postContent": "Content", | ||||
|   "postSettings": "Settings", | ||||
|   "postPublisherUnselected": "Publisher Unspecified", | ||||
|   "postVisibility": "Visibility", | ||||
|   "postVisibilityPublic": "Public", | ||||
|   "postVisibilityFriends": "Friends Only", | ||||
|   "postVisibilityUnlisted": "Unlisted", | ||||
|   "postVisibilityPrivate": "Private", | ||||
|   "copyMessage": "Copy Message", | ||||
|   "authFactor": "Authentication Factor", | ||||
|   "authFactorDelete": "Delete the Factor", | ||||
|   "authFactorDeleteHint": "Are you sure you want to delete this authentication factor? This action cannot be undone.", | ||||
|   "authFactorDisable": "Disable the Factor", | ||||
|   "authFactorDisableHint": "Are you sure you want to disable this authentication factor? You can enable it again later.", | ||||
|   "authFactorEnable": "Enable the Factor", | ||||
|   "authFactorEnableHint": "Please enter the code that generated by the factor to enable it.", | ||||
|   "authFactorNew": "Create Auth Factor", | ||||
|   "authFactorSecret": "Secret", | ||||
|   "authFactorSecretHint": "Create an secret for this factor.", | ||||
|   "authFactorQrCodeScan": "Scan this QR code with your authenticator app to set up TOTP authentication", | ||||
|   "authFactorNoQrCode": "No QR code available for this authentication factor", | ||||
|   "cancel": "Cancel", | ||||
|   "confirm": "Confirm", | ||||
|   "authFactorAdditional": "One more step", | ||||
|   "authFactorHint": "Contact method hint", | ||||
|   "authFactorHintHelper": "You need provide a part of your contact method and we will send the verification code to that contact method if it matched our records", | ||||
|   "authSessions": "Active Sessions", | ||||
|   "authSessionsDescription": "See devices you currently logged in.", | ||||
|   "authSessionsCount": { | ||||
|     "one": "{} session", | ||||
|     "other": "{} sessions" | ||||
|   }, | ||||
|   "authDeviceCurrent": "Current device", | ||||
|   "lastActiveAt": "Last active at {}", | ||||
|   "authDeviceLogout": "Logout", | ||||
|   "authDeviceLogoutHint": "Are you sure you want to logout this device? This will also disable the push notification to this device.", | ||||
|   "authDeviceEditLabel": "Edit Label", | ||||
|   "authDeviceLabelTitle": "Edit Device Label", | ||||
|   "authDeviceLabelHint": "Enter a name for this device", | ||||
|   "authDeviceSwipeEditHint": "Swipe left to edit label", | ||||
|   "authDeviceSwipeLogoutHint": "Swipe right to logout device", | ||||
|   "typingHint": { | ||||
|     "one": "{} is typing...", | ||||
|     "other": "{} are typing..." | ||||
|   }, | ||||
|   "settingsAppearance": "Appearance", | ||||
|   "settingsServer": "Server", | ||||
|   "settingsBehavior": "Behavior", | ||||
|   "settingsDesktop": "Desktop", | ||||
|   "settingsKeyboardShortcuts": "Keyboard Shortcuts", | ||||
|   "settingsEnterToSendDesktopHint": "Press Enter to send messages, use Shift+Enter for new line.", | ||||
|   "settingsHelp": "Settings Help", | ||||
|   "settingsHelpContent": "This page allows you to manage your app settings, appearance, and behavior. If you need assistance, please contact support.", | ||||
|   "settingsKeyboardShortcutSearch": "Search", | ||||
|   "settingsKeyboardShortcutSettings": "Settings", | ||||
|   "settingsKeyboardShortcutNewMessage": "New Message", | ||||
|   "settingsKeyboardShortcutCloseDialog": "Close Dialog", | ||||
|   "close": "Close", | ||||
|   "contactMethod": "Contact Method", | ||||
|   "contactMethodType": "Contact Type", | ||||
|   "contactMethodTypeEmail": "Email", | ||||
|   "contactMethodTypePhone": "Phone", | ||||
|   "contactMethodTypeAddress": "Address", | ||||
|   "contactMethodEmailHint": "Enter your email address", | ||||
|   "contactMethodPhoneHint": "Enter your phone number", | ||||
|   "contactMethodAddressHint": "Enter your physical address", | ||||
|   "contactMethodEmailDescription": "Your email will be used for account recovery and notifications", | ||||
|   "contactMethodPhoneDescription": "Your phone number will be used for account recovery and notifications", | ||||
|   "contactMethodAddressDescription": "Your physical address will be used for shipping and billing purposes.", | ||||
|   "contactMethodVerified": "Verified", | ||||
|   "contactMethodUnverified": "Unverified", | ||||
|   "contactMethodVerify": "Verify Contact", | ||||
|   "contactMethodDelete": "Delete Contact", | ||||
|   "contactMethodNew": "New Contact Method", | ||||
|   "contactMethodContentEmpty": "Contact content cannot be empty", | ||||
|   "contactMethodVerificationSent": "Verification code sent to your contact method", | ||||
|   "contactMethodVerificationNeeded": "The contact method is added, but not verified yet. You can verify it by tapping it and select verify.", | ||||
|   "accountContactMethod": "Contact Methods", | ||||
|   "accountContactMethodDescription": "Manage your contact methods for account recovery and notifications", | ||||
|   "authFactorVerificationNeeded": "The auth factor is added, but it is not enabled yet. You can enable it by tapping it and enter the verification code.", | ||||
|   "contactMethodPrimary": "Primary", | ||||
|   "contactMethodSetPrimary": "Set as Primary", | ||||
|   "contactMethodSetPrimaryHint": "Set this contact method as your primary contact method for account recovery and notifications", | ||||
|   "contactMethodDeleteHint": "Are you sure to delete this contact method? This action cannot be undone.", | ||||
|   "chatNotifyLevel": "Notify Level", | ||||
|   "chatNotifyLevelDescription": "Decide how many notifications you will receive.", | ||||
|   "chatNotifyLevelAll": "All", | ||||
|   "chatNotifyLevelMention": "Mentions", | ||||
|   "chatNotifyLevelNone": "None", | ||||
|   "chatNotifyLevelUpdated": "The notify level has been updated to {}.", | ||||
|   "chatBreak": "Take a Break", | ||||
|   "chatBreakDescription": "Set a time, before that time, your notification level will be metions only, to take a break of the current topic they're talking about.", | ||||
|   "chatBreakClear": "Clear the break time", | ||||
|   "chatBreakHour": "{} break", | ||||
|   "chatBreakDay": "{} day break", | ||||
|   "chatBreakSet": "Break set for {}", | ||||
|   "chatBreakCleared": "Chat break has been cleared.", | ||||
|   "chatBreakCustom": "Custom duration", | ||||
|   "chatBreakEnterMinutes": "Enter minutes", | ||||
|   "firstName": "First Name", | ||||
|   "middleName": "Middle Name", | ||||
|   "lastName": "Last Name", | ||||
|   "gender": "Gender", | ||||
|   "pronouns": "Pronouns", | ||||
|   "location": "Location", | ||||
|   "timeZone": "Time Zone", | ||||
|   "birthday": "Birthday", | ||||
|   "selectADate": "Select a date", | ||||
|   "checkInResultT0": "Worst", | ||||
|   "checkInResultT1": "Poor", | ||||
|   "checkInResultT2": "Mid", | ||||
|   "checkInResultT3": "Good", | ||||
|   "checkInResultT4": "Best", | ||||
|   "accountProfileView": "View Profile", | ||||
|   "unspecified": "Unspecified", | ||||
|   "added": "Added", | ||||
|   "preview": "Preview", | ||||
|   "togglePreview": "Toggle Preview" | ||||
| } | ||||
|   | ||||
| @@ -99,6 +99,14 @@ | ||||
|   "permissionMember": "成员", | ||||
|   "reply": "回复", | ||||
|   "forward": "转发", | ||||
|   "repliedTo": "回复了", | ||||
|   "forwarded": "转发了", | ||||
|   "hasAttachments": { | ||||
|     "other": "{}个附件" | ||||
|   }, | ||||
|   "postHasAttachments": { | ||||
|     "other": "{}个附件" | ||||
|   }, | ||||
|   "edited": "已编辑", | ||||
|   "addVideo": "添加视频", | ||||
|   "addPhoto": "添加照片", | ||||
| @@ -278,5 +286,31 @@ | ||||
|   "settingsHideBottomNav": "隐藏底部导航", | ||||
|   "settingsSoundEffects": "音效", | ||||
|   "settingsAprilFoolFeatures": "愚人节功能", | ||||
|   "settingsEnterToSend": "按下 Enter 发送" | ||||
|   "settingsEnterToSend": "按下 Enter 发送", | ||||
|   "postVisibility": "可见性", | ||||
|   "postVisibilityPublic": "公开", | ||||
|   "postVisibilityFriends": "仅好友可见", | ||||
|   "postVisibilityUnlisted": "不公开", | ||||
|   "postVisibilityPrivate": "私密", | ||||
|   "chatNotifyLevel": "通知级别", | ||||
|   "chatNotifyLevelDescription": "决定您将收到多少通知。", | ||||
|   "chatNotifyLevelAll": "全部", | ||||
|   "chatNotifyLevelMention": "提及", | ||||
|   "chatNotifyLevelNone": "无", | ||||
|   "chatNotifyLevelUpdated": "通知级别已更新为 {}。", | ||||
|   "chatBreak": "暂停聊天", | ||||
|   "chatBreakDescription": "设置一个时间,在该时间之前,您的通知级别将仅为提及,以暂时休息当前讨论的话题。", | ||||
|   "chatBreakClear": "清除暂停时间", | ||||
|   "chatBreakHour": "暂停 {} 分钟", | ||||
|   "chatBreakDay": "暂停 {} 天", | ||||
|   "chatBreakSet": "已设置暂停 {}", | ||||
|   "chatBreakCleared": "聊天暂停已清除。", | ||||
|   "chatBreakCustom": "自定义时长", | ||||
|   "chatBreakEnterMinutes": "输入分钟数", | ||||
|   "chatBreakNone": "无", | ||||
|   "checkInResultT0": "大凶", | ||||
|   "checkInResultT1": "凶", | ||||
|   "checkInResultT2": "中平", | ||||
|   "checkInResultT3": "吉", | ||||
|   "checkInResultT4": "大吉" | ||||
| } | ||||
| @@ -68,7 +68,7 @@ | ||||
|   "createRealmHint": "結識志同道合的朋友、建立社群等等。", | ||||
|   "editRealm": "編輯領域", | ||||
|   "deleteRealm": "刪除領域", | ||||
|   "deleteRealmHint": "確定要刪除此領域嗎?這也將刪除此領域下的所有頻道、發佈者和貼文。", | ||||
|   "deleteRealmHint": "確定要刪除此領域嗎?這也將刪除該領域下的所有頻道、發佈者和貼文。", | ||||
|   "explore": "探索", | ||||
|   "account": "帳號", | ||||
|   "name": "名稱", | ||||
| @@ -99,6 +99,14 @@ | ||||
|   "permissionMember": "成員", | ||||
|   "reply": "回覆", | ||||
|   "forward": "轉發", | ||||
|   "repliedTo": "回覆了", | ||||
|   "forwarded": "轉發了", | ||||
|   "hasAttachments": { | ||||
|     "other": "{}個附件" | ||||
|   }, | ||||
|   "postHasAttachments": { | ||||
|     "other": "{}個附件" | ||||
|   }, | ||||
|   "edited": "已編輯", | ||||
|   "addVideo": "新增影片", | ||||
|   "addPhoto": "新增照片", | ||||
| @@ -278,5 +286,26 @@ | ||||
|   "settingsHideBottomNav": "隱藏底部導航", | ||||
|   "settingsSoundEffects": "音效", | ||||
|   "settingsAprilFoolFeatures": "愚人節功能", | ||||
|   "settingsEnterToSend": "按下 Enter 傳送" | ||||
|   "settingsEnterToSend": "按下 Enter 傳送", | ||||
|   "postVisibility": "可見性", | ||||
|   "postVisibilityPublic": "公開", | ||||
|   "postVisibilityFriends": "僅好友可見", | ||||
|   "postVisibilityUnlisted": "不公開", | ||||
|   "postVisibilityPrivate": "私密", | ||||
|   "chatNotifyLevel": "通知等級", | ||||
|   "chatNotifyLevelDescription": "決定您將收到多少通知。", | ||||
|   "chatNotifyLevelAll": "全部", | ||||
|   "chatNotifyLevelMention": "提及", | ||||
|   "chatNotifyLevelNone": "無", | ||||
|   "chatNotifyLevelUpdated": "通知等級已更新為 {}。", | ||||
|   "chatBreak": "暫停聊天", | ||||
|   "chatBreakDescription": "設定一個時間,在該時間之前,您的通知等級將僅為提及,以暫時休息當前討論的話題。", | ||||
|   "chatBreakClear": "清除暫停時間", | ||||
|   "chatBreakHour": "暫停 {} 分鐘", | ||||
|   "chatBreakDay": "暫停 {} 天", | ||||
|   "chatBreakSet": "已設定暫停 {}", | ||||
|   "chatBreakCleared": "聊天暫停已清除。", | ||||
|   "chatBreakCustom": "自訂時長", | ||||
|   "chatBreakEnterMinutes": "輸入分鐘數", | ||||
|   "chatBreakNone": "無" | ||||
| } | ||||
							
								
								
									
										3
									
								
								assets/images/oidc/apple.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								assets/images/oidc/apple.svg
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| <svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="814" height="1000"> | ||||
|   <path d="M788.1 340.9c-5.8 4.5-108.2 62.2-108.2 190.5 0 148.4 130.3 200.9 134.2 202.2-.6 3.2-20.7 71.9-68.7 141.9-42.8 61.6-87.5 123.1-155.5 123.1s-85.5-39.5-164-39.5c-76.5 0-103.7 40.8-165.9 40.8s-105.6-57-155.5-127C46.7 790.7 0 663 0 541.8c0-194.4 126.4-297.5 250.8-297.5 66.1 0 121.2 43.4 162.7 43.4 39.5 0 101.1-46 176.3-46 28.5 0 130.9 2.6 198.3 99.2zm-234-181.5c31.1-36.9 53.1-88.1 53.1-139.3 0-7.1-.6-14.3-1.9-20.1-50.6 1.9-110.8 33.7-147.1 75.8-28.5 32.4-55.1 83.6-55.1 135.5 0 7.8 1.3 15.6 1.9 18.1 3.2.6 8.4 1.3 13.6 1.3 45.4 0 102.5-30.4 135.5-71.3z"/> | ||||
| </svg> | ||||
| After Width: | Height: | Size: 660 B | 
							
								
								
									
										1
									
								
								assets/images/oidc/discord.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								assets/images/oidc/discord.svg
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?><svg id="Discord-Logo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 126.644 96"><path id="Discord-Symbol-Black" d="M81.15,0c-1.2376,2.1973-2.3489,4.4704-3.3591,6.794-9.5975-1.4396-19.3718-1.4396-28.9945,0-.985-2.3236-2.1216-4.5967-3.3591-6.794-9.0166,1.5407-17.8059,4.2431-26.1405,8.0568C2.779,32.5304-1.6914,56.3725.5312,79.8863c9.6732,7.1476,20.5083,12.603,32.0505,16.0884,2.6014-3.4854,4.8998-7.1981,6.8698-11.0623-3.738-1.3891-7.3497-3.1318-10.8098-5.1523.9092-.6567,1.7932-1.3386,2.6519-1.9953,20.281,9.547,43.7696,9.547,64.0758,0,.8587.7072,1.7427,1.3891,2.6519,1.9953-3.4601,2.0457-7.0718,3.7632-10.835,5.1776,1.97,3.8642,4.2683,7.5769,6.8698,11.0623,11.5419-3.4854,22.3769-8.9156,32.0509-16.0631,2.626-27.2771-4.496-50.9172-18.817-71.8548C98.9811,4.2684,90.1918,1.5659,81.1752.0505l-.0252-.0505ZM42.2802,65.4144c-6.2383,0-11.4159-5.6575-11.4159-12.6535s4.9755-12.6788,11.3907-12.6788,11.5169,5.708,11.4159,12.6788c-.101,6.9708-5.026,12.6535-11.3907,12.6535ZM84.3576,65.4144c-6.2637,0-11.3907-5.6575-11.3907-12.6535s4.9755-12.6788,11.3907-12.6788,11.4917,5.708,11.3906,12.6788c-.101,6.9708-5.026,12.6535-11.3906,12.6535Z"/></svg> | ||||
| After Width: | Height: | Size: 1.1 KiB | 
							
								
								
									
										1
									
								
								assets/images/oidc/github.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								assets/images/oidc/github.svg
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| <svg width="98" height="96" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M48.854 0C21.839 0 0 22 0 49.217c0 21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362 0-1.141-.08-5.052-.08-9.127-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015 4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6-10.839-1.141-22.243-5.378-22.243-24.283 0-5.378 1.94-9.778 5.014-13.2-.485-1.222-2.184-6.275.486-13.038 0 0 4.125-1.304 13.426 5.052a46.97 46.97 0 0 1 12.214-1.63c4.125 0 8.33.571 12.213 1.63 9.302-6.356 13.427-5.052 13.427-5.052 2.67 6.763.97 11.816.485 13.038 3.155 3.422 5.015 7.822 5.015 13.2 0 18.905-11.404 23.06-22.324 24.283 1.78 1.548 3.316 4.481 3.316 9.126 0 6.6-.08 11.897-.08 13.526 0 1.304.89 2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0z" fill="#24292f"/></svg> | ||||
| After Width: | Height: | Size: 963 B | 
							
								
								
									
										104
									
								
								assets/images/oidc/google.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								assets/images/oidc/google.svg
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,104 @@ | ||||
| <svg version="1.1" viewBox="0 0 268.1522 273.8827" overflow="hidden" xml:space="preserve" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"> | ||||
|   <defs> | ||||
|     <linearGradient id="a"> | ||||
|       <stop offset="0" stop-color="#0fbc5c"/> | ||||
|       <stop offset="1" stop-color="#0cba65"/> | ||||
|     </linearGradient> | ||||
|     <linearGradient id="g"> | ||||
|       <stop offset=".2312727" stop-color="#0fbc5f"/> | ||||
|       <stop offset=".3115468" stop-color="#0fbc5f"/> | ||||
|       <stop offset=".3660131" stop-color="#0fbc5e"/> | ||||
|       <stop offset=".4575163" stop-color="#0fbc5d"/> | ||||
|       <stop offset=".540305" stop-color="#12bc58"/> | ||||
|       <stop offset=".6993464" stop-color="#28bf3c"/> | ||||
|       <stop offset=".7712418" stop-color="#38c02b"/> | ||||
|       <stop offset=".8605665" stop-color="#52c218"/> | ||||
|       <stop offset=".9150327" stop-color="#67c30f"/> | ||||
|       <stop offset="1" stop-color="#86c504"/> | ||||
|     </linearGradient> | ||||
|     <linearGradient id="h"> | ||||
|       <stop offset=".1416122" stop-color="#1abd4d"/> | ||||
|       <stop offset=".2475151" stop-color="#6ec30d"/> | ||||
|       <stop offset=".3115468" stop-color="#8ac502"/> | ||||
|       <stop offset=".3660131" stop-color="#a2c600"/> | ||||
|       <stop offset=".4456735" stop-color="#c8c903"/> | ||||
|       <stop offset=".540305" stop-color="#ebcb03"/> | ||||
|       <stop offset=".6156363" stop-color="#f7cd07"/> | ||||
|       <stop offset=".6993454" stop-color="#fdcd04"/> | ||||
|       <stop offset=".7712418" stop-color="#fdce05"/> | ||||
|       <stop offset=".8605661" stop-color="#ffce0a"/> | ||||
|     </linearGradient> | ||||
|     <linearGradient id="f"> | ||||
|       <stop offset=".3159041" stop-color="#ff4c3c"/> | ||||
|       <stop offset=".6038179" stop-color="#ff692c"/> | ||||
|       <stop offset=".7268366" stop-color="#ff7825"/> | ||||
|       <stop offset=".884534" stop-color="#ff8d1b"/> | ||||
|       <stop offset="1" stop-color="#ff9f13"/> | ||||
|     </linearGradient> | ||||
|     <linearGradient id="b"> | ||||
|       <stop offset=".2312727" stop-color="#ff4541"/> | ||||
|       <stop offset=".3115468" stop-color="#ff4540"/> | ||||
|       <stop offset=".4575163" stop-color="#ff4640"/> | ||||
|       <stop offset=".540305" stop-color="#ff473f"/> | ||||
|       <stop offset=".6993464" stop-color="#ff5138"/> | ||||
|       <stop offset=".7712418" stop-color="#ff5b33"/> | ||||
|       <stop offset=".8605665" stop-color="#ff6c29"/> | ||||
|       <stop offset="1" stop-color="#ff8c18"/> | ||||
|     </linearGradient> | ||||
|     <linearGradient id="d"> | ||||
|       <stop offset=".4084578" stop-color="#fb4e5a"/> | ||||
|       <stop offset="1" stop-color="#ff4540"/> | ||||
|     </linearGradient> | ||||
|     <linearGradient id="c"> | ||||
|       <stop offset=".1315461" stop-color="#0cba65"/> | ||||
|       <stop offset=".2097843" stop-color="#0bb86d"/> | ||||
|       <stop offset=".2972969" stop-color="#09b479"/> | ||||
|       <stop offset=".3962575" stop-color="#08ad93"/> | ||||
|       <stop offset=".4771242" stop-color="#0aa6a9"/> | ||||
|       <stop offset=".5684245" stop-color="#0d9cc6"/> | ||||
|       <stop offset=".667385" stop-color="#1893dd"/> | ||||
|       <stop offset=".7687273" stop-color="#258bf1"/> | ||||
|       <stop offset=".8585063" stop-color="#3086ff"/> | ||||
|     </linearGradient> | ||||
|     <linearGradient id="e"> | ||||
|       <stop offset=".3660131" stop-color="#ff4e3a"/> | ||||
|       <stop offset=".4575163" stop-color="#ff8a1b"/> | ||||
|       <stop offset=".540305" stop-color="#ffa312"/> | ||||
|       <stop offset=".6156363" stop-color="#ffb60c"/> | ||||
|       <stop offset=".7712418" stop-color="#ffcd0a"/> | ||||
|       <stop offset=".8605665" stop-color="#fecf0a"/> | ||||
|       <stop offset=".9150327" stop-color="#fecf08"/> | ||||
|       <stop offset="1" stop-color="#fdcd01"/> | ||||
|     </linearGradient> | ||||
|     <linearGradient xlink:href="#a" id="s" x1="219.6997" y1="329.5351" x2="254.4673" y2="329.5351" gradientUnits="userSpaceOnUse"/> | ||||
|     <radialGradient xlink:href="#b" id="m" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-1.936885,1.043001,1.455731,2.555422,290.5254,-400.6338)" cx="109.6267" cy="135.8619" fx="109.6267" fy="135.8619" r="71.46001"/> | ||||
|     <radialGradient xlink:href="#c" id="n" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-3.512595,-4.45809,-1.692547,1.260616,870.8006,191.554)" cx="45.25866" cy="279.2738" fx="45.25866" fy="279.2738" r="71.46001"/> | ||||
|     <radialGradient xlink:href="#d" id="l" cx="304.0166" cy="118.0089" fx="304.0166" fy="118.0089" r="47.85445" gradientTransform="matrix(2.064353,-4.926832e-6,-2.901531e-6,2.592041,-297.6788,-151.7469)" gradientUnits="userSpaceOnUse"/> | ||||
|     <radialGradient xlink:href="#e" id="o" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-0.2485783,2.083138,2.962486,0.3341668,-255.1463,-331.1636)" cx="181.001" cy="177.2013" fx="181.001" fy="177.2013" r="71.46001"/> | ||||
|     <radialGradient xlink:href="#f" id="p" cx="207.6733" cy="108.0972" fx="207.6733" fy="108.0972" r="41.1025" gradientTransform="matrix(-1.249206,1.343263,-3.896837,-3.425693,880.5011,194.9051)" gradientUnits="userSpaceOnUse"/> | ||||
|     <radialGradient xlink:href="#g" id="r" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-1.936885,-1.043001,1.455731,-2.555422,290.5254,838.6834)" cx="109.6267" cy="135.8619" fx="109.6267" fy="135.8619" r="71.46001"/> | ||||
|     <radialGradient xlink:href="#h" id="j" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-0.081402,-1.93722,2.926737,-0.1162508,-215.1345,632.8606)" cx="154.8697" cy="145.9691" fx="154.8697" fy="145.9691" r="71.46001"/> | ||||
|     <filter id="q" x="-.04842873" y="-.0582241" width="1.096857" height="1.116448" color-interpolation-filters="sRGB"> | ||||
|       <feGaussianBlur stdDeviation="1.700914"/> | ||||
|     </filter> | ||||
|     <filter id="k" x="-.01670084" y="-.01009856" width="1.033402" height="1.020197" color-interpolation-filters="sRGB"> | ||||
|       <feGaussianBlur stdDeviation=".2419367"/> | ||||
|     </filter> | ||||
|     <clipPath clipPathUnits="userSpaceOnUse" id="i"> | ||||
|       <path d="M371.3784 193.2406H237.0825v53.4375h77.167c-1.2405 7.5627-4.0259 15.0024-8.1049 21.7862-4.6734 7.7723-10.4511 13.6895-16.373 18.1957-17.7389 13.4983-38.42 16.2584-52.7828 16.2584-36.2824 0-67.2833-23.2865-79.2844-54.9287-.4843-1.1482-.8059-2.3344-1.1975-3.5068-2.652-8.0533-4.101-16.5825-4.101-25.4474 0-9.226 1.5691-18.0575 4.4301-26.3985 11.2851-32.8967 42.9849-57.4674 80.1789-57.4674 7.4811 0 14.6854.8843 21.5173 2.6481 15.6135 4.0309 26.6578 11.9698 33.4252 18.2494l40.834-39.7111c-24.839-22.616-57.2194-36.3201-95.8444-36.3201-30.8782-.00066-59.3863 9.55308-82.7477 25.6992-18.9454 13.0941-34.4833 30.6254-44.9695 50.9861-9.75366 18.8785-15.09441 39.7994-15.09441 62.2934 0 22.495 5.34891 43.6334 15.10261 62.3374v.126c10.3023 19.8567 25.3678 36.9537 43.6783 49.9878 15.9962 11.3866 44.6789 26.5516 84.0307 26.5516 22.6301 0 42.6867-4.0517 60.3748-11.6447 12.76-5.4775 24.0655-12.6217 34.3012-21.8036 13.5247-12.1323 24.1168-27.1388 31.3465-44.4041 7.2297-17.2654 11.097-36.7895 11.097-57.957 0-9.858-.9971-19.8694-2.6881-28.9684Z" fill="#000"/> | ||||
|     </clipPath> | ||||
|   </defs> | ||||
|   <g transform="matrix(0.957922,0,0,0.985255,-90.17436,-78.85577)"> | ||||
|     <g clip-path="url(#i)"> | ||||
|       <path d="M92.07563 219.9585c.14844 22.14 6.5014 44.983 16.11767 63.4234v.1269c6.9482 13.3919 16.4444 23.9704 27.2604 34.4518l65.326-23.67c-12.3593-6.2344-14.2452-10.0546-23.1048-17.0253-9.0537-9.0658-15.8015-19.4735-20.0038-31.677h-.1693l.1693-.1269c-2.7646-8.0587-3.0373-16.6129-3.1393-25.5029Z" fill="url(#j)" filter="url(#k)"/> | ||||
|       <path d="M237.0835 79.02491c-6.4568 22.52569-3.988 44.42139 0 57.16129 7.4561.0055 14.6388.8881 21.4494 2.6464 15.6135 4.0309 26.6566 11.97 33.424 18.2496l41.8794-40.7256c-24.8094-22.58904-54.6663-37.2961-96.7528-37.33169Z" fill="url(#l)" filter="url(#k)"/> | ||||
|       <path d="M236.9434 78.84678c-31.6709-.00068-60.9107 9.79833-84.8718 26.35902-8.8968 6.149-17.0612 13.2521-24.3311 21.1509-1.9045 17.7429 14.2569 39.5507 46.2615 39.3702 15.5284-17.9373 38.4946-29.5427 64.0561-29.5427.0233 0 .046.0019.0693.002l-1.0439-57.33536c-.0472-.00003-.0929-.00406-.1401-.00406Z" fill="url(#m)" filter="url(#k)"/> | ||||
|       <path d="m341.4751 226.3788-28.2685 19.2848c-1.2405 7.5627-4.0278 15.0023-8.1068 21.7861-4.6734 7.7723-10.4506 13.6898-16.3725 18.196-17.7022 13.4704-38.3286 16.2439-52.6877 16.2553-14.8415 25.1018-17.4435 37.6749 1.0439 57.9342 22.8762-.0167 43.157-4.1174 61.0458-11.7965 12.9312-5.551 24.3879-12.7913 34.7609-22.0964 13.7061-12.295 24.4421-27.5034 31.7688-45.0003 7.3267-17.497 11.2446-37.2822 11.2446-58.7336Z" fill="url(#n)" filter="url(#k)"/> | ||||
|       <path d="M234.9956 191.2104v57.4981h136.0062c1.1962-7.8745 5.1523-18.0644 5.1523-26.5001 0-9.858-.9963-21.899-2.6873-30.998Z" fill="#3086ff" filter="url(#k)"/> | ||||
|       <path d="M128.3894 124.3268c-8.393 9.1191-15.5632 19.326-21.2483 30.3646-9.75351 18.8785-15.09402 41.8295-15.09402 64.3235 0 .317.02642.6271.02855.9436 4.31953 8.2244 59.66647 6.6495 62.45617 0-.0035-.3103-.0387-.6128-.0387-.9238 0-9.226 1.5696-16.0262 4.4306-24.3672 3.5294-10.2885 9.0557-19.7628 16.1223-27.9257 1.6019-2.0309 5.8748-6.3969 7.1214-9.0157.4749-.9975-.8621-1.5574-.9369-1.9085-.0836-.3927-1.8762-.0769-2.2778-.3694-1.2751-.9288-3.8001-1.4138-5.3334-1.8449-3.2772-.9215-8.7085-2.9536-11.7252-5.0601-9.5357-6.6586-24.417-14.6122-33.5047-24.2164Z" fill="url(#o)" filter="url(#k)"/> | ||||
|       <path d="M162.0989 155.8569c22.1123 13.3013 28.4714-6.7139 43.173-12.9771L179.698 90.21568c-9.4075 3.92642-18.2957 8.80465-26.5426 14.50442-12.316 8.5122-23.192 18.8995-32.1763 30.7204Z" fill="url(#p)" filter="url(#q)"/> | ||||
|       <path d="M171.0987 290.222c-29.6829 10.6413-34.3299 11.023-37.0622 29.2903 5.2213 5.0597 10.8312 9.74 16.7926 13.9835 15.9962 11.3867 46.766 26.5517 86.1178 26.5517.0462 0 .0904-.004.1366-.004v-59.1574c-.0298.0001-.064.002-.0938.002-14.7359 0-26.5113-3.8435-38.5848-10.5273-2.9768-1.6479-8.3775 2.7772-11.1229.799-3.7865-2.7284-12.8991 2.3508-16.1833-.9378Z" fill="url(#r)" filter="url(#k)"/> | ||||
|       <path d="M219.6997 299.0227v59.9959c5.506.6402 11.2361 1.0289 17.2472 1.0289 6.0259 0 11.8556-.3073 17.5204-.8723v-59.7481c-6.3482 1.0777-12.3272 1.461-17.4776 1.461-5.9318 0-11.7005-.6858-17.29-1.8654Z" opacity=".5" fill="url(#s)" filter="url(#k)"/> | ||||
|     </g> | ||||
|   </g> | ||||
| </svg> | ||||
| After Width: | Height: | Size: 9.6 KiB | 
							
								
								
									
										1
									
								
								assets/images/oidc/microsoft.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								assets/images/oidc/microsoft.svg
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 21 21"><path fill="#f35325" d="M0 0h10v10H0z"/><path fill="#81bc06" d="M11 0h10v10H11z"/><path fill="#05a6f0" d="M0 11h10v10H0z"/><path fill="#ffba08" d="M11 11h10v10H11z"/></svg> | ||||
| After Width: | Height: | Size: 232 B | 
							
								
								
									
										4
									
								
								buildtools/appimage_config/AppRun
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										4
									
								
								buildtools/appimage_config/AppRun
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,4 @@ | ||||
| #!/bin/sh | ||||
|  | ||||
| cd "$(dirname "$0")" | ||||
| exec ./island | ||||
							
								
								
									
										8
									
								
								buildtools/appimage_config/Solian.desktop
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								buildtools/appimage_config/Solian.desktop
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| [Desktop Entry] | ||||
| Version=1.0 | ||||
| Type=Application | ||||
| Terminal=false | ||||
| Name=Solian | ||||
| Exec=island %u | ||||
| Icon=icon-padded | ||||
| Categories=Network; | ||||
							
								
								
									
										
											BIN
										
									
								
								buildtools/appimagetool-x86_64.AppImage
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								buildtools/appimagetool-x86_64.AppImage
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										14
									
								
								ios/Podfile
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								ios/Podfile
									
									
									
									
									
								
							| @@ -29,17 +29,29 @@ flutter_ios_podfile_setup | ||||
|  | ||||
| target 'Runner' do | ||||
|   use_frameworks! | ||||
|   use_modular_headers! | ||||
|  | ||||
|   pod 'Kingfisher', '~> 8.0' | ||||
|   pod 'Alamofire' | ||||
|  | ||||
|   flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) | ||||
|  | ||||
|   target 'RunnerTests' do | ||||
|     inherit! :search_paths | ||||
|   end | ||||
|  | ||||
|   target 'SolianNotificationService' do | ||||
|     inherit! :search_paths | ||||
|     pod 'Kingfisher', '~> 8.0' | ||||
|     pod 'Alamofire' | ||||
|   end | ||||
| end | ||||
|  | ||||
| post_install do |installer| | ||||
|   installer.pods_project.targets.each do |target| | ||||
|     flutter_additional_ios_build_settings(target) | ||||
|     target.build_configurations.each do |config| | ||||
|       # Workaround for https://github.com/flutter/flutter/issues/64502 | ||||
|       config.build_settings['ONLY_ACTIVE_ARCH'] = 'YES' # <= this line | ||||
|     end | ||||
|   end | ||||
| end | ||||
|   | ||||
							
								
								
									
										159
									
								
								ios/Podfile.lock
									
									
									
									
									
								
							
							
						
						
									
										159
									
								
								ios/Podfile.lock
									
									
									
									
									
								
							| @@ -1,4 +1,7 @@ | ||||
| PODS: | ||||
|   - Alamofire (5.10.2) | ||||
|   - connectivity_plus (0.0.1): | ||||
|     - Flutter | ||||
|   - croppy (0.0.1): | ||||
|     - Flutter | ||||
|   - device_info_plus (0.0.1): | ||||
| @@ -37,37 +40,37 @@ PODS: | ||||
|   - file_picker (0.0.1): | ||||
|     - DKImagePickerController/PhotoGallery | ||||
|     - Flutter | ||||
|   - Firebase/CoreOnly (11.10.0): | ||||
|     - FirebaseCore (~> 11.10.0) | ||||
|   - Firebase/Messaging (11.10.0): | ||||
|   - Firebase/CoreOnly (11.13.0): | ||||
|     - FirebaseCore (~> 11.13.0) | ||||
|   - Firebase/Messaging (11.13.0): | ||||
|     - Firebase/CoreOnly | ||||
|     - FirebaseMessaging (~> 11.10.0) | ||||
|   - firebase_core (3.13.1): | ||||
|     - Firebase/CoreOnly (= 11.10.0) | ||||
|     - FirebaseMessaging (~> 11.13.0) | ||||
|   - firebase_core (3.14.0): | ||||
|     - Firebase/CoreOnly (= 11.13.0) | ||||
|     - Flutter | ||||
|   - firebase_messaging (15.2.6): | ||||
|     - Firebase/Messaging (= 11.10.0) | ||||
|   - firebase_messaging (15.2.7): | ||||
|     - Firebase/Messaging (= 11.13.0) | ||||
|     - firebase_core | ||||
|     - Flutter | ||||
|   - FirebaseCore (11.10.0): | ||||
|     - FirebaseCoreInternal (~> 11.10.0) | ||||
|     - GoogleUtilities/Environment (~> 8.0) | ||||
|     - GoogleUtilities/Logger (~> 8.0) | ||||
|   - FirebaseCoreInternal (11.10.0): | ||||
|     - "GoogleUtilities/NSData+zlib (~> 8.0)" | ||||
|   - FirebaseInstallations (11.10.0): | ||||
|     - FirebaseCore (~> 11.10.0) | ||||
|     - GoogleUtilities/Environment (~> 8.0) | ||||
|     - GoogleUtilities/UserDefaults (~> 8.0) | ||||
|   - FirebaseCore (11.13.0): | ||||
|     - FirebaseCoreInternal (~> 11.13.0) | ||||
|     - GoogleUtilities/Environment (~> 8.1) | ||||
|     - GoogleUtilities/Logger (~> 8.1) | ||||
|   - FirebaseCoreInternal (11.13.0): | ||||
|     - "GoogleUtilities/NSData+zlib (~> 8.1)" | ||||
|   - FirebaseInstallations (11.13.0): | ||||
|     - FirebaseCore (~> 11.13.0) | ||||
|     - GoogleUtilities/Environment (~> 8.1) | ||||
|     - GoogleUtilities/UserDefaults (~> 8.1) | ||||
|     - PromisesObjC (~> 2.4) | ||||
|   - FirebaseMessaging (11.10.0): | ||||
|     - FirebaseCore (~> 11.10.0) | ||||
|   - FirebaseMessaging (11.13.0): | ||||
|     - FirebaseCore (~> 11.13.0) | ||||
|     - FirebaseInstallations (~> 11.0) | ||||
|     - GoogleDataTransport (~> 10.0) | ||||
|     - GoogleUtilities/AppDelegateSwizzler (~> 8.0) | ||||
|     - GoogleUtilities/Environment (~> 8.0) | ||||
|     - GoogleUtilities/Reachability (~> 8.0) | ||||
|     - GoogleUtilities/UserDefaults (~> 8.0) | ||||
|     - GoogleUtilities/AppDelegateSwizzler (~> 8.1) | ||||
|     - GoogleUtilities/Environment (~> 8.1) | ||||
|     - GoogleUtilities/Reachability (~> 8.1) | ||||
|     - GoogleUtilities/UserDefaults (~> 8.1) | ||||
|     - nanopb (~> 3.30910.0) | ||||
|   - Flutter (1.0.0) | ||||
|   - flutter_inappwebview_ios (0.0.1): | ||||
| @@ -81,9 +84,17 @@ PODS: | ||||
|     - Flutter | ||||
|   - flutter_platform_alert (0.0.1): | ||||
|     - Flutter | ||||
|   - flutter_timezone (0.0.1): | ||||
|     - Flutter | ||||
|   - flutter_udid (0.0.1): | ||||
|     - Flutter | ||||
|     - SAMKeychain | ||||
|   - flutter_webrtc (0.14.0): | ||||
|     - Flutter | ||||
|     - WebRTC-SDK (= 125.6422.07) | ||||
|   - gal (1.0.0): | ||||
|     - Flutter | ||||
|     - FlutterMacOS | ||||
|   - GoogleDataTransport (10.1.0): | ||||
|     - nanopb (~> 3.30910.0) | ||||
|     - PromisesObjC (~> 2.4) | ||||
| @@ -116,6 +127,10 @@ PODS: | ||||
|   - irondash_engine_context (0.0.1): | ||||
|     - Flutter | ||||
|   - Kingfisher (8.3.2) | ||||
|   - livekit_client (2.4.8): | ||||
|     - Flutter | ||||
|     - flutter_webrtc | ||||
|     - WebRTC-SDK (= 125.6422.07) | ||||
|   - media_kit_libs_ios_video (1.0.4): | ||||
|     - Flutter | ||||
|   - media_kit_video (0.0.1): | ||||
| @@ -125,40 +140,48 @@ PODS: | ||||
|     - nanopb/encode (= 3.30910.0) | ||||
|   - nanopb/decode (3.30910.0) | ||||
|   - nanopb/encode (3.30910.0) | ||||
|   - native_exif (0.0.1): | ||||
|     - Flutter | ||||
|   - OrderedSet (6.0.3) | ||||
|   - package_info_plus (0.4.5): | ||||
|     - Flutter | ||||
|   - pasteboard (0.0.1): | ||||
|     - Flutter | ||||
|   - path_provider_foundation (0.0.1): | ||||
|     - Flutter | ||||
|     - FlutterMacOS | ||||
|   - PromisesObjC (2.4.0) | ||||
|   - record_ios (1.0.0): | ||||
|     - Flutter | ||||
|   - SAMKeychain (1.5.3) | ||||
|   - SDWebImage (5.21.0): | ||||
|     - SDWebImage/Core (= 5.21.0) | ||||
|   - SDWebImage/Core (5.21.0) | ||||
|   - SDWebImage (5.21.1): | ||||
|     - SDWebImage/Core (= 5.21.1) | ||||
|   - SDWebImage/Core (5.21.1) | ||||
|   - shared_preferences_foundation (0.0.1): | ||||
|     - Flutter | ||||
|     - FlutterMacOS | ||||
|   - sign_in_with_apple (0.0.1): | ||||
|     - Flutter | ||||
|   - sqflite_darwin (0.0.4): | ||||
|     - Flutter | ||||
|     - FlutterMacOS | ||||
|   - sqlite3 (3.49.2): | ||||
|     - sqlite3/common (= 3.49.2) | ||||
|   - sqlite3/common (3.49.2) | ||||
|   - sqlite3/dbstatvtab (3.49.2): | ||||
|   - sqlite3 (3.50.1): | ||||
|     - sqlite3/common (= 3.50.1) | ||||
|   - sqlite3/common (3.50.1) | ||||
|   - sqlite3/dbstatvtab (3.50.1): | ||||
|     - sqlite3/common | ||||
|   - sqlite3/fts5 (3.49.2): | ||||
|   - sqlite3/fts5 (3.50.1): | ||||
|     - sqlite3/common | ||||
|   - sqlite3/math (3.49.2): | ||||
|   - sqlite3/math (3.50.1): | ||||
|     - sqlite3/common | ||||
|   - sqlite3/perf-threadsafe (3.49.2): | ||||
|   - sqlite3/perf-threadsafe (3.50.1): | ||||
|     - sqlite3/common | ||||
|   - sqlite3/rtree (3.49.2): | ||||
|   - sqlite3/rtree (3.50.1): | ||||
|     - sqlite3/common | ||||
|   - sqlite3_flutter_libs (0.0.1): | ||||
|     - Flutter | ||||
|     - FlutterMacOS | ||||
|     - sqlite3 (~> 3.49.2) | ||||
|     - sqlite3 (~> 3.50.1) | ||||
|     - sqlite3/dbstatvtab | ||||
|     - sqlite3/fts5 | ||||
|     - sqlite3/math | ||||
| @@ -173,8 +196,11 @@ PODS: | ||||
|     - Flutter | ||||
|   - wakelock_plus (0.0.1): | ||||
|     - Flutter | ||||
|   - WebRTC-SDK (125.6422.07) | ||||
|  | ||||
| DEPENDENCIES: | ||||
|   - Alamofire | ||||
|   - connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`) | ||||
|   - croppy (from `.symlinks/plugins/croppy/ios`) | ||||
|   - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) | ||||
|   - file_picker (from `.symlinks/plugins/file_picker/ios`) | ||||
| @@ -184,15 +210,23 @@ DEPENDENCIES: | ||||
|   - flutter_inappwebview_ios (from `.symlinks/plugins/flutter_inappwebview_ios/ios`) | ||||
|   - flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`) | ||||
|   - flutter_platform_alert (from `.symlinks/plugins/flutter_platform_alert/ios`) | ||||
|   - flutter_timezone (from `.symlinks/plugins/flutter_timezone/ios`) | ||||
|   - flutter_udid (from `.symlinks/plugins/flutter_udid/ios`) | ||||
|   - flutter_webrtc (from `.symlinks/plugins/flutter_webrtc/ios`) | ||||
|   - gal (from `.symlinks/plugins/gal/darwin`) | ||||
|   - image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`) | ||||
|   - irondash_engine_context (from `.symlinks/plugins/irondash_engine_context/ios`) | ||||
|   - Kingfisher (~> 8.0) | ||||
|   - livekit_client (from `.symlinks/plugins/livekit_client/ios`) | ||||
|   - media_kit_libs_ios_video (from `.symlinks/plugins/media_kit_libs_ios_video/ios`) | ||||
|   - media_kit_video (from `.symlinks/plugins/media_kit_video/ios`) | ||||
|   - native_exif (from `.symlinks/plugins/native_exif/ios`) | ||||
|   - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) | ||||
|   - pasteboard (from `.symlinks/plugins/pasteboard/ios`) | ||||
|   - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) | ||||
|   - record_ios (from `.symlinks/plugins/record_ios/ios`) | ||||
|   - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) | ||||
|   - sign_in_with_apple (from `.symlinks/plugins/sign_in_with_apple/ios`) | ||||
|   - sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`) | ||||
|   - sqlite3_flutter_libs (from `.symlinks/plugins/sqlite3_flutter_libs/darwin`) | ||||
|   - super_native_extensions (from `.symlinks/plugins/super_native_extensions/ios`) | ||||
| @@ -202,6 +236,7 @@ DEPENDENCIES: | ||||
|  | ||||
| SPEC REPOS: | ||||
|   trunk: | ||||
|     - Alamofire | ||||
|     - DKImagePickerController | ||||
|     - DKPhotoGallery | ||||
|     - Firebase | ||||
| @@ -219,8 +254,11 @@ SPEC REPOS: | ||||
|     - SDWebImage | ||||
|     - sqlite3 | ||||
|     - SwiftyGif | ||||
|     - WebRTC-SDK | ||||
|  | ||||
| EXTERNAL SOURCES: | ||||
|   connectivity_plus: | ||||
|     :path: ".symlinks/plugins/connectivity_plus/ios" | ||||
|   croppy: | ||||
|     :path: ".symlinks/plugins/croppy/ios" | ||||
|   device_info_plus: | ||||
| @@ -239,22 +277,38 @@ EXTERNAL SOURCES: | ||||
|     :path: ".symlinks/plugins/flutter_native_splash/ios" | ||||
|   flutter_platform_alert: | ||||
|     :path: ".symlinks/plugins/flutter_platform_alert/ios" | ||||
|   flutter_timezone: | ||||
|     :path: ".symlinks/plugins/flutter_timezone/ios" | ||||
|   flutter_udid: | ||||
|     :path: ".symlinks/plugins/flutter_udid/ios" | ||||
|   flutter_webrtc: | ||||
|     :path: ".symlinks/plugins/flutter_webrtc/ios" | ||||
|   gal: | ||||
|     :path: ".symlinks/plugins/gal/darwin" | ||||
|   image_picker_ios: | ||||
|     :path: ".symlinks/plugins/image_picker_ios/ios" | ||||
|   irondash_engine_context: | ||||
|     :path: ".symlinks/plugins/irondash_engine_context/ios" | ||||
|   livekit_client: | ||||
|     :path: ".symlinks/plugins/livekit_client/ios" | ||||
|   media_kit_libs_ios_video: | ||||
|     :path: ".symlinks/plugins/media_kit_libs_ios_video/ios" | ||||
|   media_kit_video: | ||||
|     :path: ".symlinks/plugins/media_kit_video/ios" | ||||
|   native_exif: | ||||
|     :path: ".symlinks/plugins/native_exif/ios" | ||||
|   package_info_plus: | ||||
|     :path: ".symlinks/plugins/package_info_plus/ios" | ||||
|   pasteboard: | ||||
|     :path: ".symlinks/plugins/pasteboard/ios" | ||||
|   path_provider_foundation: | ||||
|     :path: ".symlinks/plugins/path_provider_foundation/darwin" | ||||
|   record_ios: | ||||
|     :path: ".symlinks/plugins/record_ios/ios" | ||||
|   shared_preferences_foundation: | ||||
|     :path: ".symlinks/plugins/shared_preferences_foundation/darwin" | ||||
|   sign_in_with_apple: | ||||
|     :path: ".symlinks/plugins/sign_in_with_apple/ios" | ||||
|   sqflite_darwin: | ||||
|     :path: ".symlinks/plugins/sqflite_darwin/darwin" | ||||
|   sqlite3_flutter_libs: | ||||
| @@ -269,47 +323,58 @@ EXTERNAL SOURCES: | ||||
|     :path: ".symlinks/plugins/wakelock_plus/ios" | ||||
|  | ||||
| SPEC CHECKSUMS: | ||||
|   Alamofire: 7193b3b92c74a07f85569e1a6c4f4237291e7496 | ||||
|   connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd | ||||
|   croppy: 979e8ddc254f4642bffe7d52dc7193354b27ba30 | ||||
|   device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe | ||||
|   DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c | ||||
|   DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60 | ||||
|   file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be | ||||
|   Firebase: 1fe1c0a7d9aaea32efe01fbea5f0ebd8d70e53a2 | ||||
|   firebase_core: ba71b44041571da878cb624ce0d80250bcbe58ad | ||||
|   firebase_messaging: 13129fe2ca166d1ed2d095062d76cee88943d067 | ||||
|   FirebaseCore: 8344daef5e2661eb004b177488d6f9f0f24251b7 | ||||
|   FirebaseCoreInternal: ef4505d2afb1d0ebbc33162cb3795382904b5679 | ||||
|   FirebaseInstallations: 9980995bdd06ec8081dfb6ab364162bdd64245c3 | ||||
|   FirebaseMessaging: 2b9f56aa4ed286e1f0ce2ee1d413aabb8f9f5cb9 | ||||
|   Firebase: 3435bc66b4d494c2f22c79fd3aae4c1db6662327 | ||||
|   firebase_core: 700bac7ed92bb754fd70fbf01d72b36ecdd6d450 | ||||
|   firebase_messaging: 860c017fcfbb5e27c163062d1d3135388f3ef954 | ||||
|   FirebaseCore: c692c7f1c75305ab6aff2b367f25e11d73aa8bd0 | ||||
|   FirebaseCoreInternal: 29d7b3af4aaf0b8f3ed20b568c13df399b06f68c | ||||
|   FirebaseInstallations: 0ee9074f2c1e86561ace168ee1470dc67aabaf02 | ||||
|   FirebaseMessaging: 195bbdb73e6ca1dbc76cd46e73f3552c084ef6e4 | ||||
|   Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 | ||||
|   flutter_inappwebview_ios: b89ba3482b96fb25e00c967aae065701b66e9b99 | ||||
|   flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf | ||||
|   flutter_platform_alert: bf3b5fcd4ac14bd637e20527e9c471633071afd3 | ||||
|   flutter_timezone: 7c838e17ffd4645d261e87037e5bebf6d38fe544 | ||||
|   flutter_udid: f7c3884e6ec2951efe4f9de082257fc77c4d15e9 | ||||
|   flutter_webrtc: fd0d3bdef8766a0736dbbe2e5b7e85f1f3c52117 | ||||
|   gal: baecd024ebfd13c441269ca7404792a7152fde89 | ||||
|   GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7 | ||||
|   GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1 | ||||
|   image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a | ||||
|   irondash_engine_context: 8e58ca8e0212ee9d1c7dc6a42121849986c88486 | ||||
|   Kingfisher: 0621d0ac0c78fecb19f6dc5303bde2b52abaf2f5 | ||||
|   livekit_client: 9e901890552514206e5ff828903ed271531da264 | ||||
|   media_kit_libs_ios_video: 5a18affdb97d1f5d466dc79988b13eff6c5e2854 | ||||
|   media_kit_video: 1746e198cb697d1ffb734b1d05ec429d1fcd1474 | ||||
|   nanopb: fad817b59e0457d11a5dfbde799381cd727c1275 | ||||
|   native_exif: 0eb73d3d5b3ca892719228df8d2d1b13d1ae396c | ||||
|   OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94 | ||||
|   package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499 | ||||
|   pasteboard: 49088aeb6119d51f976a421db60d8e1ab079b63c | ||||
|   path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 | ||||
|   PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 | ||||
|   record_ios: fee1c924aa4879b882ebca2b4bce6011bcfc3d8b | ||||
|   SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c | ||||
|   SDWebImage: f84b0feeb08d2d11e6a9b843cb06d75ebf5b8868 | ||||
|   SDWebImage: f29024626962457f3470184232766516dee8dfea | ||||
|   shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 | ||||
|   sign_in_with_apple: c5dcc141574c8c54d5ac99dd2163c0c72ad22418 | ||||
|   sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0 | ||||
|   sqlite3: 3c950dc86011117c307eb0b28c4a7bb449dce9f1 | ||||
|   sqlite3_flutter_libs: 74334e3ef2dbdb7d37e50859bb45da43935779c4 | ||||
|   sqlite3: 1d85290c3321153511f6e900ede7a1608718bbd5 | ||||
|   sqlite3_flutter_libs: e7fc8c9ea2200ff3271f08f127842131746b70e2 | ||||
|   super_native_extensions: b763c02dc3a8fd078389f410bf15149179020cb4 | ||||
|   SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4 | ||||
|   url_launcher_ios: 694010445543906933d732453a59da0a173ae33d | ||||
|   volume_controller: 3657a1f65bedb98fa41ff7dc5793537919f31b12 | ||||
|   wakelock_plus: e29112ab3ef0b318e58cfa5c32326458be66b556 | ||||
|   WebRTC-SDK: dff00a3892bc570b6014e046297782084071657e | ||||
|  | ||||
| PODFILE CHECKSUM: 4c3fad73fbbc9053a824d880097bda7884240991 | ||||
| PODFILE CHECKSUM: 0c13198c20d0416ef589aeb2e1dac5c50262254f | ||||
|  | ||||
| COCOAPODS: 1.16.2 | ||||
|   | ||||
| @@ -10,12 +10,15 @@ | ||||
| 		1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; | ||||
| 		331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; | ||||
| 		3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; | ||||
| 		73CDD6812DEC00480059D95D /* SolianNotificationService.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 73CDD67A2DEC00480059D95D /* SolianNotificationService.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; | ||||
| 		73D4264B2DEB815D006C0AAE /* NotifyDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73D4264A2DEB815D006C0AAE /* NotifyDelegate.swift */; }; | ||||
| 		74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; | ||||
| 		755557017FD1B99AFC4F9127 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 29812C17FFBE7DBBC7203981 /* Pods_RunnerTests.framework */; }; | ||||
| 		97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; | ||||
| 		97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; | ||||
| 		97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; | ||||
| 		B87C0E607033790E71B54D73 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F6D834CA86410B09796B312B /* Pods_Runner.framework */; }; | ||||
| 		D1772CE196985AE8E8C9F2E5 /* Pods_SolianNotificationService.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 39FE4CC6223F0D3C0E1FFD04 /* Pods_SolianNotificationService.framework */; }; | ||||
| 		E7A0B456EF7AAA71D1397081 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 9AE244813FCDFAA941430393 /* GoogleService-Info.plist */; }; | ||||
| /* End PBXBuildFile section */ | ||||
|  | ||||
| @@ -27,9 +30,27 @@ | ||||
| 			remoteGlobalIDString = 97C146ED1CF9000F007C117D; | ||||
| 			remoteInfo = Runner; | ||||
| 		}; | ||||
| 		73CDD67F2DEC00480059D95D /* PBXContainerItemProxy */ = { | ||||
| 			isa = PBXContainerItemProxy; | ||||
| 			containerPortal = 97C146E61CF9000F007C117D /* Project object */; | ||||
| 			proxyType = 1; | ||||
| 			remoteGlobalIDString = 73CDD6792DEC00480059D95D; | ||||
| 			remoteInfo = SolianNotificationService; | ||||
| 		}; | ||||
| /* End PBXContainerItemProxy section */ | ||||
|  | ||||
| /* Begin PBXCopyFilesBuildPhase section */ | ||||
| 		73268D1D2DEAFD670076E970 /* Embed Foundation Extensions */ = { | ||||
| 			isa = PBXCopyFilesBuildPhase; | ||||
| 			buildActionMask = 2147483647; | ||||
| 			dstPath = ""; | ||||
| 			dstSubfolderSpec = 13; | ||||
| 			files = ( | ||||
| 				73CDD6812DEC00480059D95D /* SolianNotificationService.appex in Embed Foundation Extensions */, | ||||
| 			); | ||||
| 			name = "Embed Foundation Extensions"; | ||||
| 			runOnlyForDeploymentPostprocessing = 0; | ||||
| 		}; | ||||
| 		9705A1C41CF9048500538489 /* Embed Frameworks */ = { | ||||
| 			isa = PBXCopyFilesBuildPhase; | ||||
| 			buildActionMask = 2147483647; | ||||
| @@ -47,16 +68,23 @@ | ||||
| 		1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; }; | ||||
| 		1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; }; | ||||
| 		14DFD79BE7C26E51B117583C /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; }; | ||||
| 		192FDACE67D7CB6AED15C634 /* Pods-NotificationService.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NotificationService.debug.xcconfig"; path = "Target Support Files/Pods-NotificationService/Pods-NotificationService.debug.xcconfig"; sourceTree = "<group>"; }; | ||||
| 		1C14F71D23E4371602065522 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; }; | ||||
| 		252A83CE6862573BB856ED8E /* Pods-NotificationService.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NotificationService.release.xcconfig"; path = "Target Support Files/Pods-NotificationService/Pods-NotificationService.release.xcconfig"; sourceTree = "<group>"; }; | ||||
| 		29812C17FFBE7DBBC7203981 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; | ||||
| 		2D2457F8B2E6EF9C0F935035 /* Pods-NotificationService.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NotificationService.profile.xcconfig"; path = "Target Support Files/Pods-NotificationService/Pods-NotificationService.profile.xcconfig"; sourceTree = "<group>"; }; | ||||
| 		331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; }; | ||||
| 		331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; | ||||
| 		39FE4CC6223F0D3C0E1FFD04 /* Pods_SolianNotificationService.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SolianNotificationService.framework; sourceTree = BUILT_PRODUCTS_DIR; }; | ||||
| 		3A1C47BD29CC6AC2587D4DBE /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; }; | ||||
| 		3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; }; | ||||
| 		737E920B2DB6A9FF00BE9CDB /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = "<group>"; }; | ||||
| 		73CDD67A2DEC00480059D95D /* SolianNotificationService.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = SolianNotificationService.appex; sourceTree = BUILT_PRODUCTS_DIR; }; | ||||
| 		73D4264A2DEB815D006C0AAE /* NotifyDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotifyDelegate.swift; sourceTree = "<group>"; }; | ||||
| 		74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; }; | ||||
| 		74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; }; | ||||
| 		7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; }; | ||||
| 		8B40620B1EEBB09456406A3C /* Pods-SolianNotificationService.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SolianNotificationService.profile.xcconfig"; path = "Target Support Files/Pods-SolianNotificationService/Pods-SolianNotificationService.profile.xcconfig"; sourceTree = "<group>"; }; | ||||
| 		9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; }; | ||||
| 		9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; }; | ||||
| 		97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; | ||||
| @@ -66,10 +94,50 @@ | ||||
| 		97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; | ||||
| 		9AE244813FCDFAA941430393 /* GoogleService-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "Runner/GoogleService-Info.plist"; sourceTree = "<group>"; }; | ||||
| 		A499FDB2082EB000933AA8C5 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; }; | ||||
| 		AA0CA8A3E15DEE023BB27438 /* Pods_NotificationService.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_NotificationService.framework; sourceTree = BUILT_PRODUCTS_DIR; }; | ||||
| 		B93771F2A63E4148DC6142F7 /* Pods-SolianNotificationService.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SolianNotificationService.release.xcconfig"; path = "Target Support Files/Pods-SolianNotificationService/Pods-SolianNotificationService.release.xcconfig"; sourceTree = "<group>"; }; | ||||
| 		E6B10A9A85BECA2E576C91FF /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; }; | ||||
| 		F6D834CA86410B09796B312B /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; | ||||
| 		F830F535CB92E3F2E1653A11 /* Pods-SolianNotificationService.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SolianNotificationService.debug.xcconfig"; path = "Target Support Files/Pods-SolianNotificationService/Pods-SolianNotificationService.debug.xcconfig"; sourceTree = "<group>"; }; | ||||
| /* End PBXFileReference section */ | ||||
|  | ||||
| /* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ | ||||
| 		73CDD6822DEC00480059D95D /* Exceptions for "SolianNotificationService" folder in "SolianNotificationService" target */ = { | ||||
| 			isa = PBXFileSystemSynchronizedBuildFileExceptionSet; | ||||
| 			membershipExceptions = ( | ||||
| 				Info.plist, | ||||
| 			); | ||||
| 			target = 73CDD6792DEC00480059D95D /* SolianNotificationService */; | ||||
| 		}; | ||||
| 		73CDD68E2DEC00BE0059D95D /* Exceptions for "Services" folder in "SolianNotificationService" target */ = { | ||||
| 			isa = PBXFileSystemSynchronizedBuildFileExceptionSet; | ||||
| 			membershipExceptions = ( | ||||
| 				CloudFile.swift, | ||||
| 				DataExchange.swift, | ||||
| 			); | ||||
| 			target = 73CDD6792DEC00480059D95D /* SolianNotificationService */; | ||||
| 		}; | ||||
| /* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ | ||||
|  | ||||
| /* Begin PBXFileSystemSynchronizedRootGroup section */ | ||||
| 		73268D272DEB012A0076E970 /* Services */ = { | ||||
| 			isa = PBXFileSystemSynchronizedRootGroup; | ||||
| 			exceptions = ( | ||||
| 				73CDD68E2DEC00BE0059D95D /* Exceptions for "Services" folder in "SolianNotificationService" target */, | ||||
| 			); | ||||
| 			path = Services; | ||||
| 			sourceTree = "<group>"; | ||||
| 		}; | ||||
| 		73CDD67B2DEC00480059D95D /* SolianNotificationService */ = { | ||||
| 			isa = PBXFileSystemSynchronizedRootGroup; | ||||
| 			exceptions = ( | ||||
| 				73CDD6822DEC00480059D95D /* Exceptions for "SolianNotificationService" folder in "SolianNotificationService" target */, | ||||
| 			); | ||||
| 			path = SolianNotificationService; | ||||
| 			sourceTree = "<group>"; | ||||
| 		}; | ||||
| /* End PBXFileSystemSynchronizedRootGroup section */ | ||||
|  | ||||
| /* Begin PBXFrameworksBuildPhase section */ | ||||
| 		1DFF8FEBBD0CF5A990600776 /* Frameworks */ = { | ||||
| 			isa = PBXFrameworksBuildPhase; | ||||
| @@ -79,6 +147,14 @@ | ||||
| 			); | ||||
| 			runOnlyForDeploymentPostprocessing = 0; | ||||
| 		}; | ||||
| 		73CDD6772DEC00480059D95D /* Frameworks */ = { | ||||
| 			isa = PBXFrameworksBuildPhase; | ||||
| 			buildActionMask = 2147483647; | ||||
| 			files = ( | ||||
| 				D1772CE196985AE8E8C9F2E5 /* Pods_SolianNotificationService.framework in Frameworks */, | ||||
| 			); | ||||
| 			runOnlyForDeploymentPostprocessing = 0; | ||||
| 		}; | ||||
| 		97C146EB1CF9000F007C117D /* Frameworks */ = { | ||||
| 			isa = PBXFrameworksBuildPhase; | ||||
| 			buildActionMask = 2147483647; | ||||
| @@ -103,6 +179,8 @@ | ||||
| 			children = ( | ||||
| 				F6D834CA86410B09796B312B /* Pods_Runner.framework */, | ||||
| 				29812C17FFBE7DBBC7203981 /* Pods_RunnerTests.framework */, | ||||
| 				AA0CA8A3E15DEE023BB27438 /* Pods_NotificationService.framework */, | ||||
| 				39FE4CC6223F0D3C0E1FFD04 /* Pods_SolianNotificationService.framework */, | ||||
| 			); | ||||
| 			name = Frameworks; | ||||
| 			sourceTree = "<group>"; | ||||
| @@ -116,6 +194,12 @@ | ||||
| 				14DFD79BE7C26E51B117583C /* Pods-RunnerTests.debug.xcconfig */, | ||||
| 				14118AC858B441AB16B7309E /* Pods-RunnerTests.release.xcconfig */, | ||||
| 				E6B10A9A85BECA2E576C91FF /* Pods-RunnerTests.profile.xcconfig */, | ||||
| 				192FDACE67D7CB6AED15C634 /* Pods-NotificationService.debug.xcconfig */, | ||||
| 				252A83CE6862573BB856ED8E /* Pods-NotificationService.release.xcconfig */, | ||||
| 				2D2457F8B2E6EF9C0F935035 /* Pods-NotificationService.profile.xcconfig */, | ||||
| 				F830F535CB92E3F2E1653A11 /* Pods-SolianNotificationService.debug.xcconfig */, | ||||
| 				B93771F2A63E4148DC6142F7 /* Pods-SolianNotificationService.release.xcconfig */, | ||||
| 				8B40620B1EEBB09456406A3C /* Pods-SolianNotificationService.profile.xcconfig */, | ||||
| 			); | ||||
| 			path = Pods; | ||||
| 			sourceTree = "<group>"; | ||||
| @@ -136,6 +220,7 @@ | ||||
| 			children = ( | ||||
| 				9740EEB11CF90186004384FC /* Flutter */, | ||||
| 				97C146F01CF9000F007C117D /* Runner */, | ||||
| 				73CDD67B2DEC00480059D95D /* SolianNotificationService */, | ||||
| 				97C146EF1CF9000F007C117D /* Products */, | ||||
| 				331C8082294A63A400263BE5 /* RunnerTests */, | ||||
| 				91E124CE95BCB4DCD890160D /* Pods */, | ||||
| @@ -149,6 +234,7 @@ | ||||
| 			children = ( | ||||
| 				97C146EE1CF9000F007C117D /* Runner.app */, | ||||
| 				331C8081294A63A400263BE5 /* RunnerTests.xctest */, | ||||
| 				73CDD67A2DEC00480059D95D /* SolianNotificationService.appex */, | ||||
| 			); | ||||
| 			name = Products; | ||||
| 			sourceTree = "<group>"; | ||||
| @@ -156,6 +242,7 @@ | ||||
| 		97C146F01CF9000F007C117D /* Runner */ = { | ||||
| 			isa = PBXGroup; | ||||
| 			children = ( | ||||
| 				73268D272DEB012A0076E970 /* Services */, | ||||
| 				737E920B2DB6A9FF00BE9CDB /* Runner.entitlements */, | ||||
| 				97C146FA1CF9000F007C117D /* Main.storyboard */, | ||||
| 				97C146FD1CF9000F007C117D /* Assets.xcassets */, | ||||
| @@ -165,6 +252,7 @@ | ||||
| 				1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, | ||||
| 				74858FAE1ED2DC5600515810 /* AppDelegate.swift */, | ||||
| 				74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, | ||||
| 				73D4264A2DEB815D006C0AAE /* NotifyDelegate.swift */, | ||||
| 			); | ||||
| 			path = Runner; | ||||
| 			sourceTree = "<group>"; | ||||
| @@ -191,6 +279,27 @@ | ||||
| 			productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; | ||||
| 			productType = "com.apple.product-type.bundle.unit-test"; | ||||
| 		}; | ||||
| 		73CDD6792DEC00480059D95D /* SolianNotificationService */ = { | ||||
| 			isa = PBXNativeTarget; | ||||
| 			buildConfigurationList = 73CDD6832DEC00480059D95D /* Build configuration list for PBXNativeTarget "SolianNotificationService" */; | ||||
| 			buildPhases = ( | ||||
| 				5FC5C1EF9F17B7166B432FF2 /* [CP] Check Pods Manifest.lock */, | ||||
| 				73CDD6762DEC00480059D95D /* Sources */, | ||||
| 				73CDD6772DEC00480059D95D /* Frameworks */, | ||||
| 				73CDD6782DEC00480059D95D /* Resources */, | ||||
| 			); | ||||
| 			buildRules = ( | ||||
| 			); | ||||
| 			dependencies = ( | ||||
| 			); | ||||
| 			fileSystemSynchronizedGroups = ( | ||||
| 				73CDD67B2DEC00480059D95D /* SolianNotificationService */, | ||||
| 			); | ||||
| 			name = SolianNotificationService; | ||||
| 			productName = SolianNotificationService; | ||||
| 			productReference = 73CDD67A2DEC00480059D95D /* SolianNotificationService.appex */; | ||||
| 			productType = "com.apple.product-type.app-extension"; | ||||
| 		}; | ||||
| 		97C146ED1CF9000F007C117D /* Runner */ = { | ||||
| 			isa = PBXNativeTarget; | ||||
| 			buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; | ||||
| @@ -199,6 +308,7 @@ | ||||
| 				9740EEB61CF901F6004384FC /* Run Script */, | ||||
| 				97C146EA1CF9000F007C117D /* Sources */, | ||||
| 				97C146EB1CF9000F007C117D /* Frameworks */, | ||||
| 				73268D1D2DEAFD670076E970 /* Embed Foundation Extensions */, | ||||
| 				97C146EC1CF9000F007C117D /* Resources */, | ||||
| 				9705A1C41CF9048500538489 /* Embed Frameworks */, | ||||
| 				3B06AD1E1E4923F5004D2608 /* Thin Binary */, | ||||
| @@ -208,6 +318,10 @@ | ||||
| 			buildRules = ( | ||||
| 			); | ||||
| 			dependencies = ( | ||||
| 				73CDD6802DEC00480059D95D /* PBXTargetDependency */, | ||||
| 			); | ||||
| 			fileSystemSynchronizedGroups = ( | ||||
| 				73268D272DEB012A0076E970 /* Services */, | ||||
| 			); | ||||
| 			name = Runner; | ||||
| 			productName = Runner; | ||||
| @@ -221,6 +335,7 @@ | ||||
| 			isa = PBXProject; | ||||
| 			attributes = { | ||||
| 				BuildIndependentTargetsInParallel = YES; | ||||
| 				LastSwiftUpdateCheck = 1640; | ||||
| 				LastUpgradeCheck = 1510; | ||||
| 				ORGANIZATIONNAME = ""; | ||||
| 				TargetAttributes = { | ||||
| @@ -228,6 +343,9 @@ | ||||
| 						CreatedOnToolsVersion = 14.0; | ||||
| 						TestTargetID = 97C146ED1CF9000F007C117D; | ||||
| 					}; | ||||
| 					73CDD6792DEC00480059D95D = { | ||||
| 						CreatedOnToolsVersion = 16.4; | ||||
| 					}; | ||||
| 					97C146ED1CF9000F007C117D = { | ||||
| 						CreatedOnToolsVersion = 7.3.1; | ||||
| 						LastSwiftMigration = 1100; | ||||
| @@ -235,7 +353,6 @@ | ||||
| 				}; | ||||
| 			}; | ||||
| 			buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; | ||||
| 			compatibilityVersion = "Xcode 9.3"; | ||||
| 			developmentRegion = en; | ||||
| 			hasScannedForEncodings = 0; | ||||
| 			knownRegions = ( | ||||
| @@ -243,12 +360,14 @@ | ||||
| 				Base, | ||||
| 			); | ||||
| 			mainGroup = 97C146E51CF9000F007C117D; | ||||
| 			preferredProjectObjectVersion = 77; | ||||
| 			productRefGroup = 97C146EF1CF9000F007C117D /* Products */; | ||||
| 			projectDirPath = ""; | ||||
| 			projectRoot = ""; | ||||
| 			targets = ( | ||||
| 				97C146ED1CF9000F007C117D /* Runner */, | ||||
| 				331C8080294A63A400263BE5 /* RunnerTests */, | ||||
| 				73CDD6792DEC00480059D95D /* SolianNotificationService */, | ||||
| 			); | ||||
| 		}; | ||||
| /* End PBXProject section */ | ||||
| @@ -261,6 +380,13 @@ | ||||
| 			); | ||||
| 			runOnlyForDeploymentPostprocessing = 0; | ||||
| 		}; | ||||
| 		73CDD6782DEC00480059D95D /* Resources */ = { | ||||
| 			isa = PBXResourcesBuildPhase; | ||||
| 			buildActionMask = 2147483647; | ||||
| 			files = ( | ||||
| 			); | ||||
| 			runOnlyForDeploymentPostprocessing = 0; | ||||
| 		}; | ||||
| 		97C146EC1CF9000F007C117D /* Resources */ = { | ||||
| 			isa = PBXResourcesBuildPhase; | ||||
| 			buildActionMask = 2147483647; | ||||
| @@ -331,6 +457,28 @@ | ||||
| 			shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; | ||||
| 			showEnvVarsInLog = 0; | ||||
| 		}; | ||||
| 		5FC5C1EF9F17B7166B432FF2 /* [CP] Check Pods Manifest.lock */ = { | ||||
| 			isa = PBXShellScriptBuildPhase; | ||||
| 			buildActionMask = 2147483647; | ||||
| 			files = ( | ||||
| 			); | ||||
| 			inputFileListPaths = ( | ||||
| 			); | ||||
| 			inputPaths = ( | ||||
| 				"${PODS_PODFILE_DIR_PATH}/Podfile.lock", | ||||
| 				"${PODS_ROOT}/Manifest.lock", | ||||
| 			); | ||||
| 			name = "[CP] Check Pods Manifest.lock"; | ||||
| 			outputFileListPaths = ( | ||||
| 			); | ||||
| 			outputPaths = ( | ||||
| 				"$(DERIVED_FILE_DIR)/Pods-SolianNotificationService-checkManifestLockResult.txt", | ||||
| 			); | ||||
| 			runOnlyForDeploymentPostprocessing = 0; | ||||
| 			shellPath = /bin/sh; | ||||
| 			shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n    # print error to STDERR\n    echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n    exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; | ||||
| 			showEnvVarsInLog = 0; | ||||
| 		}; | ||||
| 		8C0351B03869BBF493808288 /* [CP] Embed Pods Frameworks */ = { | ||||
| 			isa = PBXShellScriptBuildPhase; | ||||
| 			buildActionMask = 2147483647; | ||||
| @@ -396,12 +544,20 @@ | ||||
| 			); | ||||
| 			runOnlyForDeploymentPostprocessing = 0; | ||||
| 		}; | ||||
| 		73CDD6762DEC00480059D95D /* Sources */ = { | ||||
| 			isa = PBXSourcesBuildPhase; | ||||
| 			buildActionMask = 2147483647; | ||||
| 			files = ( | ||||
| 			); | ||||
| 			runOnlyForDeploymentPostprocessing = 0; | ||||
| 		}; | ||||
| 		97C146EA1CF9000F007C117D /* Sources */ = { | ||||
| 			isa = PBXSourcesBuildPhase; | ||||
| 			buildActionMask = 2147483647; | ||||
| 			files = ( | ||||
| 				74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, | ||||
| 				1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, | ||||
| 				73D4264B2DEB815D006C0AAE /* NotifyDelegate.swift in Sources */, | ||||
| 			); | ||||
| 			runOnlyForDeploymentPostprocessing = 0; | ||||
| 		}; | ||||
| @@ -413,6 +569,11 @@ | ||||
| 			target = 97C146ED1CF9000F007C117D /* Runner */; | ||||
| 			targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; | ||||
| 		}; | ||||
| 		73CDD6802DEC00480059D95D /* PBXTargetDependency */ = { | ||||
| 			isa = PBXTargetDependency; | ||||
| 			target = 73CDD6792DEC00480059D95D /* SolianNotificationService */; | ||||
| 			targetProxy = 73CDD67F2DEC00480059D95D /* PBXContainerItemProxy */; | ||||
| 		}; | ||||
| /* End PBXTargetDependency section */ | ||||
|  | ||||
| /* Begin PBXVariantGroup section */ | ||||
| @@ -498,6 +659,7 @@ | ||||
| 				DEVELOPMENT_TEAM = W7HPZ53V6B; | ||||
| 				ENABLE_BITCODE = NO; | ||||
| 				INFOPLIST_FILE = Runner/Info.plist; | ||||
| 				INFOPLIST_KEY_CFBundleDisplayName = Solian; | ||||
| 				IPHONEOS_DEPLOYMENT_TARGET = 13.0; | ||||
| 				LD_RUNPATH_SEARCH_PATHS = ( | ||||
| 					"$(inherited)", | ||||
| @@ -561,6 +723,123 @@ | ||||
| 			}; | ||||
| 			name = Profile; | ||||
| 		}; | ||||
| 		73CDD6842DEC00480059D95D /* Debug */ = { | ||||
| 			isa = XCBuildConfiguration; | ||||
| 			baseConfigurationReference = F830F535CB92E3F2E1653A11 /* Pods-SolianNotificationService.debug.xcconfig */; | ||||
| 			buildSettings = { | ||||
| 				ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; | ||||
| 				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; | ||||
| 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; | ||||
| 				CLANG_ENABLE_OBJC_WEAK = YES; | ||||
| 				CLANG_WARN_DOCUMENTATION_COMMENTS = YES; | ||||
| 				CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; | ||||
| 				CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; | ||||
| 				CODE_SIGN_STYLE = Automatic; | ||||
| 				CURRENT_PROJECT_VERSION = 1; | ||||
| 				DEVELOPMENT_TEAM = W7HPZ53V6B; | ||||
| 				ENABLE_USER_SCRIPT_SANDBOXING = YES; | ||||
| 				GCC_C_LANGUAGE_STANDARD = gnu17; | ||||
| 				GENERATE_INFOPLIST_FILE = YES; | ||||
| 				INFOPLIST_FILE = SolianNotificationService/Info.plist; | ||||
| 				INFOPLIST_KEY_CFBundleDisplayName = SolianNotificationService; | ||||
| 				INFOPLIST_KEY_NSHumanReadableCopyright = ""; | ||||
| 				IPHONEOS_DEPLOYMENT_TARGET = 18.5; | ||||
| 				LD_RUNPATH_SEARCH_PATHS = ( | ||||
| 					"$(inherited)", | ||||
| 					"@executable_path/Frameworks", | ||||
| 					"@executable_path/../../Frameworks", | ||||
| 				); | ||||
| 				LOCALIZATION_PREFERS_STRING_CATALOGS = YES; | ||||
| 				MARKETING_VERSION = 1.0; | ||||
| 				MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; | ||||
| 				MTL_FAST_MATH = YES; | ||||
| 				PRODUCT_BUNDLE_IDENTIFIER = dev.solsynth.solian.SolianNotificationService; | ||||
| 				PRODUCT_NAME = "$(TARGET_NAME)"; | ||||
| 				SKIP_INSTALL = YES; | ||||
| 				SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; | ||||
| 				SWIFT_EMIT_LOC_STRINGS = YES; | ||||
| 				SWIFT_OPTIMIZATION_LEVEL = "-Onone"; | ||||
| 				SWIFT_VERSION = 5.0; | ||||
| 				TARGETED_DEVICE_FAMILY = "1,2"; | ||||
| 			}; | ||||
| 			name = Debug; | ||||
| 		}; | ||||
| 		73CDD6852DEC00480059D95D /* Release */ = { | ||||
| 			isa = XCBuildConfiguration; | ||||
| 			baseConfigurationReference = B93771F2A63E4148DC6142F7 /* Pods-SolianNotificationService.release.xcconfig */; | ||||
| 			buildSettings = { | ||||
| 				ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; | ||||
| 				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; | ||||
| 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; | ||||
| 				CLANG_ENABLE_OBJC_WEAK = YES; | ||||
| 				CLANG_WARN_DOCUMENTATION_COMMENTS = YES; | ||||
| 				CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; | ||||
| 				CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; | ||||
| 				CODE_SIGN_STYLE = Automatic; | ||||
| 				CURRENT_PROJECT_VERSION = 1; | ||||
| 				DEVELOPMENT_TEAM = W7HPZ53V6B; | ||||
| 				ENABLE_USER_SCRIPT_SANDBOXING = YES; | ||||
| 				GCC_C_LANGUAGE_STANDARD = gnu17; | ||||
| 				GENERATE_INFOPLIST_FILE = YES; | ||||
| 				INFOPLIST_FILE = SolianNotificationService/Info.plist; | ||||
| 				INFOPLIST_KEY_CFBundleDisplayName = SolianNotificationService; | ||||
| 				INFOPLIST_KEY_NSHumanReadableCopyright = ""; | ||||
| 				IPHONEOS_DEPLOYMENT_TARGET = 18.5; | ||||
| 				LD_RUNPATH_SEARCH_PATHS = ( | ||||
| 					"$(inherited)", | ||||
| 					"@executable_path/Frameworks", | ||||
| 					"@executable_path/../../Frameworks", | ||||
| 				); | ||||
| 				LOCALIZATION_PREFERS_STRING_CATALOGS = YES; | ||||
| 				MARKETING_VERSION = 1.0; | ||||
| 				MTL_FAST_MATH = YES; | ||||
| 				PRODUCT_BUNDLE_IDENTIFIER = dev.solsynth.solian.SolianNotificationService; | ||||
| 				PRODUCT_NAME = "$(TARGET_NAME)"; | ||||
| 				SKIP_INSTALL = YES; | ||||
| 				SWIFT_EMIT_LOC_STRINGS = YES; | ||||
| 				SWIFT_VERSION = 5.0; | ||||
| 				TARGETED_DEVICE_FAMILY = "1,2"; | ||||
| 			}; | ||||
| 			name = Release; | ||||
| 		}; | ||||
| 		73CDD6862DEC00480059D95D /* Profile */ = { | ||||
| 			isa = XCBuildConfiguration; | ||||
| 			baseConfigurationReference = 8B40620B1EEBB09456406A3C /* Pods-SolianNotificationService.profile.xcconfig */; | ||||
| 			buildSettings = { | ||||
| 				ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; | ||||
| 				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; | ||||
| 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; | ||||
| 				CLANG_ENABLE_OBJC_WEAK = YES; | ||||
| 				CLANG_WARN_DOCUMENTATION_COMMENTS = YES; | ||||
| 				CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; | ||||
| 				CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; | ||||
| 				CODE_SIGN_STYLE = Automatic; | ||||
| 				CURRENT_PROJECT_VERSION = 1; | ||||
| 				DEVELOPMENT_TEAM = W7HPZ53V6B; | ||||
| 				ENABLE_USER_SCRIPT_SANDBOXING = YES; | ||||
| 				GCC_C_LANGUAGE_STANDARD = gnu17; | ||||
| 				GENERATE_INFOPLIST_FILE = YES; | ||||
| 				INFOPLIST_FILE = SolianNotificationService/Info.plist; | ||||
| 				INFOPLIST_KEY_CFBundleDisplayName = SolianNotificationService; | ||||
| 				INFOPLIST_KEY_NSHumanReadableCopyright = ""; | ||||
| 				IPHONEOS_DEPLOYMENT_TARGET = 18.5; | ||||
| 				LD_RUNPATH_SEARCH_PATHS = ( | ||||
| 					"$(inherited)", | ||||
| 					"@executable_path/Frameworks", | ||||
| 					"@executable_path/../../Frameworks", | ||||
| 				); | ||||
| 				LOCALIZATION_PREFERS_STRING_CATALOGS = YES; | ||||
| 				MARKETING_VERSION = 1.0; | ||||
| 				MTL_FAST_MATH = YES; | ||||
| 				PRODUCT_BUNDLE_IDENTIFIER = dev.solsynth.solian.SolianNotificationService; | ||||
| 				PRODUCT_NAME = "$(TARGET_NAME)"; | ||||
| 				SKIP_INSTALL = YES; | ||||
| 				SWIFT_EMIT_LOC_STRINGS = YES; | ||||
| 				SWIFT_VERSION = 5.0; | ||||
| 				TARGETED_DEVICE_FAMILY = "1,2"; | ||||
| 			}; | ||||
| 			name = Profile; | ||||
| 		}; | ||||
| 		97C147031CF9000F007C117D /* Debug */ = { | ||||
| 			isa = XCBuildConfiguration; | ||||
| 			buildSettings = { | ||||
| @@ -683,6 +962,7 @@ | ||||
| 				DEVELOPMENT_TEAM = W7HPZ53V6B; | ||||
| 				ENABLE_BITCODE = NO; | ||||
| 				INFOPLIST_FILE = Runner/Info.plist; | ||||
| 				INFOPLIST_KEY_CFBundleDisplayName = Solian; | ||||
| 				IPHONEOS_DEPLOYMENT_TARGET = 13.0; | ||||
| 				LD_RUNPATH_SEARCH_PATHS = ( | ||||
| 					"$(inherited)", | ||||
| @@ -708,6 +988,7 @@ | ||||
| 				DEVELOPMENT_TEAM = W7HPZ53V6B; | ||||
| 				ENABLE_BITCODE = NO; | ||||
| 				INFOPLIST_FILE = Runner/Info.plist; | ||||
| 				INFOPLIST_KEY_CFBundleDisplayName = Solian; | ||||
| 				IPHONEOS_DEPLOYMENT_TARGET = 13.0; | ||||
| 				LD_RUNPATH_SEARCH_PATHS = ( | ||||
| 					"$(inherited)", | ||||
| @@ -734,6 +1015,16 @@ | ||||
| 			defaultConfigurationIsVisible = 0; | ||||
| 			defaultConfigurationName = Release; | ||||
| 		}; | ||||
| 		73CDD6832DEC00480059D95D /* Build configuration list for PBXNativeTarget "SolianNotificationService" */ = { | ||||
| 			isa = XCConfigurationList; | ||||
| 			buildConfigurations = ( | ||||
| 				73CDD6842DEC00480059D95D /* Debug */, | ||||
| 				73CDD6852DEC00480059D95D /* Release */, | ||||
| 				73CDD6862DEC00480059D95D /* Profile */, | ||||
| 			); | ||||
| 			defaultConfigurationIsVisible = 0; | ||||
| 			defaultConfigurationName = Release; | ||||
| 		}; | ||||
| 		97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { | ||||
| 			isa = XCConfigurationList; | ||||
| 			buildConfigurations = ( | ||||
|   | ||||
| @@ -26,6 +26,7 @@ | ||||
|       buildConfiguration = "Debug" | ||||
|       selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" | ||||
|       selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" | ||||
|       customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit" | ||||
|       shouldUseLaunchSchemeArgsEnv = "YES"> | ||||
|       <MacroExpansion> | ||||
|          <BuildableReference | ||||
| @@ -54,6 +55,7 @@ | ||||
|       buildConfiguration = "Debug" | ||||
|       selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" | ||||
|       selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" | ||||
|       customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit" | ||||
|       launchStyle = "0" | ||||
|       useCustomWorkingDirectory = "NO" | ||||
|       ignoresPersistentStateOnLaunch = "NO" | ||||
|   | ||||
| @@ -3,11 +3,15 @@ import UIKit | ||||
|  | ||||
| @main | ||||
| @objc class AppDelegate: FlutterAppDelegate { | ||||
|     let notifyDelegate = NotifyDelegate() | ||||
|      | ||||
|     override func application( | ||||
|         _ application: UIApplication, | ||||
|         didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? | ||||
|     ) -> Bool { | ||||
|         GeneratedPluginRegistrant.register(with: self)         | ||||
|         UNUserNotificationCenter.current().delegate = notifyDelegate | ||||
|          | ||||
|         GeneratedPluginRegistrant.register(with: self) | ||||
|         return super.application(application, didFinishLaunchingWithOptions: launchOptions) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,67 +1,83 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | ||||
| <plist version="1.0"> | ||||
| 	<dict> | ||||
| 		<key>CADisableMinimumFrameDurationOnPhone</key> | ||||
| 		<true/> | ||||
| 		<key>CFBundleDevelopmentRegion</key> | ||||
| 		<string>$(DEVELOPMENT_LANGUAGE)</string> | ||||
| 		<key>CFBundleDisplayName</key> | ||||
| 		<string>Island</string> | ||||
| 		<key>CFBundleExecutable</key> | ||||
| 		<string>$(EXECUTABLE_NAME)</string> | ||||
| 		<key>CFBundleIdentifier</key> | ||||
| 		<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> | ||||
| 		<key>CFBundleInfoDictionaryVersion</key> | ||||
| 		<string>6.0</string> | ||||
| 		<key>CFBundleName</key> | ||||
| 		<string>island</string> | ||||
| 		<key>CFBundlePackageType</key> | ||||
| 		<string>APPL</string> | ||||
| 		<key>CFBundleShortVersionString</key> | ||||
| 		<string>$(FLUTTER_BUILD_NAME)</string> | ||||
| 		<key>CFBundleSignature</key> | ||||
| 		<string>????</string> | ||||
| 		<key>CFBundleVersion</key> | ||||
| 		<string>$(FLUTTER_BUILD_NUMBER)</string> | ||||
| 		<key>LSRequiresIPhoneOS</key> | ||||
| 		<true/> | ||||
| 		<key>UIApplicationSupportsIndirectInputEvents</key> | ||||
| 		<true/> | ||||
| 		<key>UIBackgroundModes</key> | ||||
| 		<array> | ||||
| 			<string>fetch</string> | ||||
| 			<string>audio</string> | ||||
| 			<string>remote-notification</string> | ||||
| 		</array> | ||||
| 		<key>UILaunchStoryboardName</key> | ||||
| 		<string>LaunchScreen</string> | ||||
| 		<key>UIMainStoryboardFile</key> | ||||
| 		<string>Main</string> | ||||
| 		<key>UISupportedInterfaceOrientations</key> | ||||
| 		<array> | ||||
| 			<string>UIInterfaceOrientationPortrait</string> | ||||
| 			<string>UIInterfaceOrientationLandscapeLeft</string> | ||||
| 			<string>UIInterfaceOrientationLandscapeRight</string> | ||||
| 		</array> | ||||
| 		<key>UISupportedInterfaceOrientations~ipad</key> | ||||
| 		<array> | ||||
| 			<string>UIInterfaceOrientationPortrait</string> | ||||
| 			<string>UIInterfaceOrientationPortraitUpsideDown</string> | ||||
| 			<string>UIInterfaceOrientationLandscapeLeft</string> | ||||
| 			<string>UIInterfaceOrientationLandscapeRight</string> | ||||
| 		</array> | ||||
| 		<key>NSCalendarsUsageDescription</key> | ||||
| 		<string>Grant access to Calander help us to shows Solar Calander with your own events.</string> | ||||
| 		<key>NSCameraUsageDescription</key> | ||||
| 		<string>Grant access to Camera will allow Solian take photo or video for your post.</string> | ||||
| 		<key>NSMicrophoneUsageDescription</key> | ||||
| 		<string>Grant access to Microphone will allow Solian record audio for your post.</string> | ||||
| 		<key>NSPhotoLibraryAddUsageDescription</key> | ||||
| 		<string>Grant access to Photo Library will allow Solian download photo to album for you.</string> | ||||
| 		<key>NSPhotoLibraryUsageDescription</key> | ||||
| 		<string>Grant access to Photo Library will allow Solian upload photo or video for your post.</string> | ||||
| 		<key>UIStatusBarHidden</key> | ||||
| 		<false/> | ||||
| 	</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>PLIST_VERSION</key> | ||||
| 	<string>1</string> | ||||
| 	<key>BUNDLE_ID</key> | ||||
| 	<string>dev.solsynth.solian</string> | ||||
| 	<key>ITSAppUsesNonExemptEncryption</key> | ||||
| 	<false/> | ||||
| 	<key>CADisableMinimumFrameDurationOnPhone</key> | ||||
| 	<true/> | ||||
| 	<key>CFBundleDevelopmentRegion</key> | ||||
| 	<string>$(DEVELOPMENT_LANGUAGE)</string> | ||||
| 	<key>CFBundleDisplayName</key> | ||||
| 	<string>Solian</string> | ||||
| 	<key>CFBundleExecutable</key> | ||||
| 	<string>$(EXECUTABLE_NAME)</string> | ||||
| 	<key>CFBundleIdentifier</key> | ||||
| 	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> | ||||
| 	<key>CFBundleInfoDictionaryVersion</key> | ||||
| 	<string>6.0</string> | ||||
| 	<key>CFBundleName</key> | ||||
| 	<string>solian</string> | ||||
| 	<key>CFBundlePackageType</key> | ||||
| 	<string>APPL</string> | ||||
| 	<key>CFBundleShortVersionString</key> | ||||
| 	<string>$(FLUTTER_BUILD_NAME)</string> | ||||
| 	<key>CFBundleSignature</key> | ||||
| 	<string>????</string> | ||||
| 	<key>CFBundleVersion</key> | ||||
| 	<string>$(FLUTTER_BUILD_NUMBER)</string> | ||||
| 	<key>LSRequiresIPhoneOS</key> | ||||
| 	<true/> | ||||
| 	<key>NSCalendarsUsageDescription</key> | ||||
| 	<string>Grant access to Calander help us to shows Solar Calander with your own events.</string> | ||||
| 	<key>NSCameraUsageDescription</key> | ||||
| 	<string>Grant access to Camera will allow Solian take photo or video for your post.</string> | ||||
| 	<key>NSMicrophoneUsageDescription</key> | ||||
| 	<string>Grant access to Microphone will allow Solian record audio for your post.</string> | ||||
| 	<key>NSPhotoLibraryAddUsageDescription</key> | ||||
| 	<string>Grant access to Photo Library will allow Solian download photo to album for you.</string> | ||||
| 	<key>NSPhotoLibraryUsageDescription</key> | ||||
| 	<string>Grant access to Photo Library will allow Solian upload photo or video for your post.</string> | ||||
| 	<key>UIApplicationSupportsIndirectInputEvents</key> | ||||
| 	<true/> | ||||
| 	<key>UIBackgroundModes</key> | ||||
| 	<array> | ||||
| 		<string>fetch</string> | ||||
| 		<string>audio</string> | ||||
| 		<string>remote-notification</string> | ||||
| 		<string>voip</string> | ||||
| 	</array> | ||||
| 	<key>UILaunchStoryboardName</key> | ||||
| 	<string>LaunchScreen</string> | ||||
| 	<key>UIMainStoryboardFile</key> | ||||
| 	<string>Main</string> | ||||
| 	<key>UIStatusBarHidden</key> | ||||
| 	<false/> | ||||
| 	<key>UISupportedInterfaceOrientations</key> | ||||
| 	<array> | ||||
| 		<string>UIInterfaceOrientationPortrait</string> | ||||
| 		<string>UIInterfaceOrientationLandscapeLeft</string> | ||||
| 		<string>UIInterfaceOrientationLandscapeRight</string> | ||||
| 	</array> | ||||
| 	<key>UISupportedInterfaceOrientations~ipad</key> | ||||
| 	<array> | ||||
| 		<string>UIInterfaceOrientationPortrait</string> | ||||
| 		<string>UIInterfaceOrientationPortraitUpsideDown</string> | ||||
| 		<string>UIInterfaceOrientationLandscapeLeft</string> | ||||
| 		<string>UIInterfaceOrientationLandscapeRight</string> | ||||
| 	</array> | ||||
| 	<key>NSUserActivityTypes</key> | ||||
| 	<array> | ||||
| 		<string>INStartCallIntent</string> | ||||
| 		<string>INSendMessageIntent</string> | ||||
| 	</array> | ||||
| </dict> | ||||
| </plist> | ||||
|   | ||||
| @@ -1,153 +0,0 @@ | ||||
| // | ||||
| //  BlurHashDecoder.swift | ||||
| //  Runner | ||||
| // | ||||
| //  Created by LittleSheep on 2025/4/21. | ||||
| // | ||||
|  | ||||
| import UIKit | ||||
|  | ||||
| extension UIImage { | ||||
|     public convenience init?(blurHash: String, size: CGSize, punch: Float = 1) { | ||||
|         guard blurHash.count >= 6 else { return nil } | ||||
|          | ||||
|         let sizeFlag = String(blurHash[0]).decode83() | ||||
|         let numY = (sizeFlag / 9) + 1 | ||||
|         let numX = (sizeFlag % 9) + 1 | ||||
|          | ||||
|         let quantisedMaximumValue = String(blurHash[1]).decode83() | ||||
|         let maximumValue = Float(quantisedMaximumValue + 1) / 166 | ||||
|          | ||||
|         guard blurHash.count == 4 + 2 * numX * numY else { return nil } | ||||
|          | ||||
|         let colours: [(Float, Float, Float)] = (0 ..< numX * numY).map { i in | ||||
|             if i == 0 { | ||||
|                 let value = String(blurHash[2 ..< 6]).decode83() | ||||
|                 return decodeDC(value) | ||||
|             } else { | ||||
|                 let value = String(blurHash[4 + i * 2 ..< 4 + i * 2 + 2]).decode83() | ||||
|                 return decodeAC(value, maximumValue: maximumValue * punch) | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         let width = Int(size.width) | ||||
|         let height = Int(size.height) | ||||
|         let bytesPerRow = width * 3 | ||||
|         guard let data = CFDataCreateMutable(kCFAllocatorDefault, bytesPerRow * height) else { return nil } | ||||
|         CFDataSetLength(data, bytesPerRow * height) | ||||
|         guard let pixels = CFDataGetMutableBytePtr(data) else { return nil } | ||||
|          | ||||
|         for y in 0 ..< height { | ||||
|             for x in 0 ..< width { | ||||
|                 var r: Float = 0 | ||||
|                 var g: Float = 0 | ||||
|                 var b: Float = 0 | ||||
|                  | ||||
|                 for j in 0 ..< numY { | ||||
|                     for i in 0 ..< numX { | ||||
|                         let basis = cos(Float.pi * Float(x) * Float(i) / Float(width)) * cos(Float.pi * Float(y) * Float(j) / Float(height)) | ||||
|                         let colour = colours[i + j * numX] | ||||
|                         r += colour.0 * basis | ||||
|                         g += colour.1 * basis | ||||
|                         b += colour.2 * basis | ||||
|                     } | ||||
|                 } | ||||
|                  | ||||
|                 let intR = UInt8(linearTosRGB(r)) | ||||
|                 let intG = UInt8(linearTosRGB(g)) | ||||
|                 let intB = UInt8(linearTosRGB(b)) | ||||
|                  | ||||
|                 pixels[3 * x + 0 + y * bytesPerRow] = intR | ||||
|                 pixels[3 * x + 1 + y * bytesPerRow] = intG | ||||
|                 pixels[3 * x + 2 + y * bytesPerRow] = intB | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.none.rawValue) | ||||
|          | ||||
|         guard let provider = CGDataProvider(data: data) else { return nil } | ||||
|         guard let cgImage = CGImage(width: width, height: height, bitsPerComponent: 8, bitsPerPixel: 24, bytesPerRow: bytesPerRow, | ||||
|                                     space: CGColorSpaceCreateDeviceRGB(), bitmapInfo: bitmapInfo, provider: provider, decode: nil, shouldInterpolate: true, intent: .defaultIntent) else { return nil } | ||||
|          | ||||
|         self.init(cgImage: cgImage) | ||||
|     } | ||||
| } | ||||
|  | ||||
| private func decodeDC(_ value: Int) -> (Float, Float, Float) { | ||||
|     let intR = value >> 16 | ||||
|     let intG = (value >> 8) & 255 | ||||
|     let intB = value & 255 | ||||
|     return (sRGBToLinear(intR), sRGBToLinear(intG), sRGBToLinear(intB)) | ||||
| } | ||||
|  | ||||
| private func decodeAC(_ value: Int, maximumValue: Float) -> (Float, Float, Float) { | ||||
|     let quantR = value / (19 * 19) | ||||
|     let quantG = (value / 19) % 19 | ||||
|     let quantB = value % 19 | ||||
|      | ||||
|     let rgb = ( | ||||
|         signPow((Float(quantR) - 9) / 9, 2) * maximumValue, | ||||
|         signPow((Float(quantG) - 9) / 9, 2) * maximumValue, | ||||
|         signPow((Float(quantB) - 9) / 9, 2) * maximumValue | ||||
|     ) | ||||
|      | ||||
|     return rgb | ||||
| } | ||||
|  | ||||
| private func signPow(_ value: Float, _ exp: Float) -> Float { | ||||
|     return copysign(pow(abs(value), exp), value) | ||||
| } | ||||
|  | ||||
| private func linearTosRGB(_ value: Float) -> Int { | ||||
|     let v = max(0, min(1, value)) | ||||
|     if v <= 0.0031308 { return Int(v * 12.92 * 255 + 0.5) } | ||||
|     else { return Int((1.055 * pow(v, 1 / 2.4) - 0.055) * 255 + 0.5) } | ||||
| } | ||||
|  | ||||
| private func sRGBToLinear<Type: BinaryInteger>(_ value: Type) -> Float { | ||||
|     let v = Float(Int64(value)) / 255 | ||||
|     if v <= 0.04045 { return v / 12.92 } | ||||
|     else { return pow((v + 0.055) / 1.055, 2.4) } | ||||
| } | ||||
|  | ||||
| private let encodeCharacters: [String] = { | ||||
|     return "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#$%*+,-.:;=?@[]^_{|}~".map { String($0) } | ||||
| }() | ||||
|  | ||||
| private let decodeCharacters: [String: Int] = { | ||||
|     var dict: [String: Int] = [:] | ||||
|     for (index, character) in encodeCharacters.enumerated() { | ||||
|         dict[character] = index | ||||
|     } | ||||
|     return dict | ||||
| }() | ||||
|  | ||||
| extension String { | ||||
|     func decode83() -> Int { | ||||
|         var value: Int = 0 | ||||
|         for character in self { | ||||
|             if let digit = decodeCharacters[String(character)] { | ||||
|                 value = value * 83 + digit | ||||
|             } | ||||
|         } | ||||
|         return value | ||||
|     } | ||||
| } | ||||
|  | ||||
| private extension String { | ||||
|     subscript (offset: Int) -> Character { | ||||
|         return self[index(startIndex, offsetBy: offset)] | ||||
|     } | ||||
|      | ||||
|     subscript (bounds: CountableClosedRange<Int>) -> Substring { | ||||
|         let start = index(startIndex, offsetBy: bounds.lowerBound) | ||||
|         let end = index(startIndex, offsetBy: bounds.upperBound) | ||||
|         return self[start...end] | ||||
|     } | ||||
|      | ||||
|     subscript (bounds: CountableRange<Int>) -> Substring { | ||||
|         let start = index(startIndex, offsetBy: bounds.lowerBound) | ||||
|         let end = index(startIndex, offsetBy: bounds.upperBound) | ||||
|         return self[start..<end] | ||||
|     } | ||||
| } | ||||
| @@ -1,42 +0,0 @@ | ||||
| // | ||||
| //  ImageView.swift | ||||
| //  Runner | ||||
| // | ||||
| //  Created by LittleSheep on 2025/4/21. | ||||
| // | ||||
|  | ||||
| import UIKit | ||||
| import Kingfisher | ||||
|  | ||||
| class ImageView: UIImageView { | ||||
|     private var _size: CGSize = CGSize(width: 0, height: 0) | ||||
|      | ||||
|     // Initialize the image view | ||||
|     override init(frame: CGRect) { | ||||
|         super.init(frame: frame) | ||||
|         contentMode = .scaleAspectFill | ||||
|         clipsToBounds = true | ||||
|     } | ||||
|      | ||||
|     required init?(coder: NSCoder) { | ||||
|         super.init(coder: coder) | ||||
|         contentMode = .scaleAspectFill | ||||
|         clipsToBounds = true | ||||
|     } | ||||
|      | ||||
|     // Method to set the image from a URL using Kingfisher | ||||
|     func setImage(from url: URL, blurHash: String?) { | ||||
|         let placeholderImage = blurHash != nil ? UIImage.init(blurHash: blurHash!, size: _size) : nil | ||||
|         let processor = DownsamplingImageProcessor(size: _size) |> RoundCornerImageProcessor(cornerRadius: 20) | ||||
|          | ||||
|         self.kf.indicatorType = .activity | ||||
|         self.kf.setImage( | ||||
|             with: url, | ||||
|             placeholder: placeholderImage, | ||||
|             options: [ | ||||
|                 .processor(processor), | ||||
|                 .transition(.fade(0.3)) | ||||
|             ] | ||||
|         ) | ||||
|     } | ||||
| } | ||||
| @@ -1,62 +0,0 @@ | ||||
| // | ||||
| //  NativeImage.swift | ||||
| //  Runner | ||||
| // | ||||
| //  Created by LittleSheep on 2025/4/21. | ||||
| // | ||||
|  | ||||
| import Flutter | ||||
| import UIKit | ||||
| import Kingfisher | ||||
|  | ||||
| class FLNativeImageFactory : NSObject, FlutterPlatformViewFactory { | ||||
|     private var messenger: FlutterBinaryMessenger | ||||
|      | ||||
|     init(messenger: FlutterBinaryMessenger) { | ||||
|         self.messenger = messenger | ||||
|         super.init() | ||||
|     } | ||||
|      | ||||
|     func create( | ||||
|         withFrame frame: CGRect, | ||||
|         viewIdentifier viewId: Int64, | ||||
|         arguments args: Any? | ||||
|     ) -> FlutterPlatformView { | ||||
|         return FLNativeImage( | ||||
|             frame: frame, | ||||
|             viewIdentifier: viewId, | ||||
|             arguments: args, | ||||
|             binaryMessenger: messenger) | ||||
|     } | ||||
|      | ||||
|     /// Implementing this method is only necessary when the `arguments` in `createWithFrame` is not `nil`. | ||||
|     public func createArgsCodec() -> FlutterMessageCodec & NSObjectProtocol { | ||||
|         return FlutterStandardMessageCodec.sharedInstance() | ||||
|     } | ||||
| } | ||||
|  | ||||
| class FLNativeImage : NSObject, FlutterPlatformView { | ||||
|     private var _view: ImageView | ||||
|      | ||||
|     init( | ||||
|         frame: CGRect, | ||||
|         viewIdentifier viewId: Int64, | ||||
|         arguments args: Any?, | ||||
|         binaryMessenger messenger: FlutterBinaryMessenger? | ||||
|     ) { | ||||
|         _view = ImageView(frame: frame) | ||||
|         super.init() | ||||
|          | ||||
|         let argsMap = args as! [AnyHashable: Any] | ||||
|         let source = argsMap["src"] as! String | ||||
|         let blurHash = argsMap["blur"] as? String | ||||
|          | ||||
|         if let url = URL(string: source) { | ||||
|             _view.setImage(from: url, blurHash: blurHash) | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     func view() -> UIView { | ||||
|         return _view | ||||
|     } | ||||
| } | ||||
							
								
								
									
										49
									
								
								ios/Runner/NotifyDelegate.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								ios/Runner/NotifyDelegate.swift
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,49 @@ | ||||
| // | ||||
| //  NotifyDelegate.swift | ||||
| //  Runner | ||||
| // | ||||
| //  Created by LittleSheep on 2025/6/1. | ||||
| // | ||||
|  | ||||
| import Foundation | ||||
| import Alamofire | ||||
|  | ||||
| class NotifyDelegate: UIResponder, UNUserNotificationCenterDelegate { | ||||
|     func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { | ||||
|         if let textResponse = response as? UNTextInputNotificationResponse { | ||||
|             let content = response.notification.request.content | ||||
|             guard let metadata = content.userInfo["meta"] as? [AnyHashable: Any] else { | ||||
|                 return | ||||
|             } | ||||
|              | ||||
|             var token: String? = UserDefaults.standard.getFlutterToken() | ||||
|             if token == nil { | ||||
|                 return | ||||
|             } | ||||
|              | ||||
|             let serverUrl = UserDefaults.standard.getServerUrl() | ||||
|             let url = "\(serverUrl)/chat/\(metadata["room_id"] ?? "")/messages" | ||||
|              | ||||
|             let parameters: [String: Any?] = [ | ||||
|                 "content": textResponse.userText, | ||||
|                 "replied_message_id": metadata["message_id"] | ||||
|             ] | ||||
|              | ||||
|             AF.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default, headers: HTTPHeaders( | ||||
|                 [HTTPHeader(name: "Authorization", value: "AtField \(token!)")] | ||||
|             )) | ||||
|                 .validate() | ||||
|                 .responseString { response in | ||||
|                     switch response.result { | ||||
|                     case .success(_): | ||||
|                         break | ||||
|                     case .failure(let error): | ||||
|                         print("Failed to send chat reply message: \(error)") | ||||
|                         break | ||||
|                     } | ||||
|                 } | ||||
|         } | ||||
|          | ||||
|         completionHandler() | ||||
|     } | ||||
| } | ||||
| @@ -4,5 +4,17 @@ | ||||
| <dict> | ||||
| 	<key>aps-environment</key> | ||||
| 	<string>development</string> | ||||
| 	<key>com.apple.developer.applesignin</key> | ||||
| 	<array> | ||||
| 		<string>Default</string> | ||||
| 	</array> | ||||
| 	<key>com.apple.developer.associated-domains</key> | ||||
| 	<array> | ||||
| 		<string>webcredentials:solian.app</string> | ||||
| 	</array> | ||||
| 	<key>com.apple.developer.device-information.user-assigned-device-name</key> | ||||
| 	<true/> | ||||
| 	<key>com.apple.developer.usernotifications.communication</key> | ||||
| 	<true/> | ||||
| </dict> | ||||
| </plist> | ||||
|   | ||||
							
								
								
									
										14
									
								
								ios/Runner/Services/CloudFile.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								ios/Runner/Services/CloudFile.swift
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| // | ||||
| //  CloudFile.swift | ||||
| //  Runner | ||||
| // | ||||
| //  Created by LittleSheep on 2025/5/31. | ||||
| // | ||||
|  | ||||
| import Foundation | ||||
|  | ||||
| func getAttachmentUrl(for identifier: String) -> String { | ||||
|     let serverBaseUrl = "https://nt.solian.app" | ||||
|      | ||||
|     return identifier.starts(with: "http") ? identifier : "\(serverBaseUrl)/files/\(identifier)" | ||||
| } | ||||
							
								
								
									
										31
									
								
								ios/Runner/Services/DataExchange.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								ios/Runner/Services/DataExchange.swift
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | ||||
| // | ||||
| //  DataExchange.swift | ||||
| //  Runner | ||||
| // | ||||
| //  Created by LittleSheep on 2025/6/2. | ||||
| // | ||||
|  | ||||
| import Foundation | ||||
|  | ||||
| extension UserDefaults { | ||||
|     func getFlutterValue<T>(forKey key: String) -> T? { | ||||
|         let prefixedKey = "flutter.\(key)" | ||||
|         return self.object(forKey: prefixedKey) as? T | ||||
|     } | ||||
|  | ||||
|     func getFlutterToken(forKey key: String = "dyn_user_tk") -> String? { | ||||
|         let prefixedKey = "flutter.\(key)" | ||||
|         guard let jsonString = self.string(forKey: prefixedKey), | ||||
|               let data = jsonString.data(using: .utf8), | ||||
|               let jsonObject = try? JSONSerialization.jsonObject(with: data), | ||||
|               let jsonDict = jsonObject as? [String: Any], | ||||
|               let token = jsonDict["token"] as? String else { | ||||
|             return nil | ||||
|         } | ||||
|         return token | ||||
|     } | ||||
|      | ||||
|     func getServerUrl(forKey key: String = "app_server_url") -> String { | ||||
|         return self.getFlutterValue(forKey: key) ?? "https://nt.solian.app" | ||||
|     } | ||||
| } | ||||
							
								
								
									
										18
									
								
								ios/SolianNotificationService/Info.plist
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								ios/SolianNotificationService/Info.plist
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | ||||
| <plist version="1.0"> | ||||
| <dict> | ||||
| 	<key>NSUserActivityTypes</key> | ||||
| 	<array> | ||||
| 		<string>INStartCallIntent</string> | ||||
| 		<string>INSendMessageIntent</string> | ||||
| 	</array> | ||||
| 	<key>NSExtension</key> | ||||
| 	<dict> | ||||
| 		<key>NSExtensionPointIdentifier</key> | ||||
| 		<string>com.apple.usernotifications.service</string> | ||||
| 		<key>NSExtensionPrincipalClass</key> | ||||
| 		<string>$(PRODUCT_MODULE_NAME).NotificationService</string> | ||||
| 	</dict> | ||||
| </dict> | ||||
| </plist> | ||||
							
								
								
									
										221
									
								
								ios/SolianNotificationService/NotificationService.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										221
									
								
								ios/SolianNotificationService/NotificationService.swift
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,221 @@ | ||||
| // | ||||
| //  NotificationService.swift | ||||
| //  NotificationService | ||||
| // | ||||
| //  Created by LittleSheep on 2025/5/31. | ||||
| // | ||||
|  | ||||
| import UserNotifications | ||||
| import Intents | ||||
| import Kingfisher | ||||
| import UniformTypeIdentifiers | ||||
|  | ||||
| enum ParseNotificationPayloadError: Error { | ||||
|     case missingMetadata(String) | ||||
|     case missingAvatarUrl(String) | ||||
| } | ||||
|  | ||||
| class NotificationService: UNNotificationServiceExtension { | ||||
|      | ||||
|     private var contentHandler: ((UNNotificationContent) -> Void)? | ||||
|     private var bestAttemptContent: UNMutableNotificationContent? | ||||
|      | ||||
|     override func didReceive( | ||||
|         _ request: UNNotificationRequest, | ||||
|         withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void | ||||
|     ) { | ||||
|         self.contentHandler = contentHandler | ||||
|         guard let bestAttemptContent = request.content.mutableCopy() as? UNMutableNotificationContent else { | ||||
|             contentHandler(request.content) | ||||
|             return | ||||
|         } | ||||
|         self.bestAttemptContent = bestAttemptContent | ||||
|          | ||||
|         do { | ||||
|             try processNotification(request: request, content: bestAttemptContent) | ||||
|         } catch { | ||||
|             contentHandler(bestAttemptContent) | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     override func serviceExtensionTimeWillExpire() { | ||||
|         if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent { | ||||
|             contentHandler(bestAttemptContent) | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     private func processNotification(request: UNNotificationRequest, content: UNMutableNotificationContent) throws { | ||||
|         switch content.userInfo["type"] as? String { | ||||
|         case "messages.new": | ||||
|             try handleMessagingNotification(request: request, content: content) | ||||
|         default: | ||||
|             try handleDefaultNotification(content: content) | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     private func handleMessagingNotification(request: UNNotificationRequest, content: UNMutableNotificationContent) throws { | ||||
|         guard let meta = content.userInfo["meta"] as? [AnyHashable: Any] else { | ||||
|             throw ParseNotificationPayloadError.missingMetadata("The notification has no meta.") | ||||
|         } | ||||
|          | ||||
|         let pfpIdentifier = meta["pfp"] as? String | ||||
|          | ||||
|         let replyableMessageCategory = UNNotificationCategory( | ||||
|             identifier: content.categoryIdentifier, | ||||
|             actions: [ | ||||
|                 UNTextInputNotificationAction( | ||||
|                     identifier: "reply_action", | ||||
|                     title: "Reply", | ||||
|                     options: [] | ||||
|                 ), | ||||
|             ], | ||||
|             intentIdentifiers: [], | ||||
|             options: [] | ||||
|         ) | ||||
|          | ||||
|         UNUserNotificationCenter.current().setNotificationCategories([replyableMessageCategory]) | ||||
|         content.categoryIdentifier = replyableMessageCategory.identifier | ||||
|          | ||||
|         let metaCopy = meta as? [String: Any] ?? [:] | ||||
|         let pfpUrl = pfpIdentifier != nil ? getAttachmentUrl(for: pfpIdentifier!) : nil | ||||
|          | ||||
|         let targetSize = 512 | ||||
|         let scaleProcessor = ResizingImageProcessor(referenceSize: CGSize(width: targetSize, height: targetSize), mode: .aspectFit) | ||||
|          | ||||
|         KingfisherManager.shared.retrieveImage(with: URL(string: pfpUrl!)!, options: [.processor(scaleProcessor)], completionHandler: { result in | ||||
|             var image: Data? | ||||
|             switch result { | ||||
|             case .success(let value): | ||||
|                 image = value.image.pngData() | ||||
|             case .failure(let error): | ||||
|                 print("Unable to get pfp url: \(error)") | ||||
|             } | ||||
|              | ||||
|             let handle = INPersonHandle(value: "\(metaCopy["user_id"] ?? "")", type: .unknown) | ||||
|             let sender = INPerson( | ||||
|                 personHandle: handle, | ||||
|                 nameComponents: PersonNameComponents(nickname: "\(metaCopy["sender_name"] ?? "")"), | ||||
|                 displayName: content.title, | ||||
|                 image: image == nil ? nil : INImage(imageData: image!), | ||||
|                 contactIdentifier: nil, | ||||
|                 customIdentifier: nil | ||||
|             ) | ||||
|              | ||||
|             let intent = self.createMessageIntent(with: sender, meta: metaCopy, body: content.body) | ||||
|             self.donateInteraction(for: intent) | ||||
|             let updatedContent = try? request.content.updating(from: intent) | ||||
|             self.contentHandler?(updatedContent ?? content) | ||||
|         }) | ||||
|     } | ||||
|      | ||||
|     private func handleDefaultNotification(content: UNMutableNotificationContent) throws { | ||||
|         guard let meta = content.userInfo["meta"] as? [AnyHashable: Any] else { | ||||
|             throw ParseNotificationPayloadError.missingMetadata("The notification has no meta.") | ||||
|         } | ||||
|          | ||||
|         if let imageIdentifier = meta["image"] as? String { | ||||
|             attachMedia(to: content, withIdentifier: [imageIdentifier], fileType: UTType.webP, doScaleDown: true) | ||||
|         } else if let pfpIdentifier = meta["pfp"] as? String { | ||||
|             attachMedia(to: content, withIdentifier: [pfpIdentifier], fileType: UTType.webP, doScaleDown: true) | ||||
|         } else if let imagesIdentifier = meta["images"] as? Array<String> { | ||||
|             attachMedia(to: content, withIdentifier: imagesIdentifier, fileType: UTType.webP, doScaleDown: true) | ||||
|         } else { | ||||
|             contentHandler?(content) | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     private func attachMedia(to content: UNMutableNotificationContent, withIdentifier identifier: Array<String>, fileType type: UTType?, doScaleDown scaleDown: Bool = false) { | ||||
|         let attachmentUrls = identifier.compactMap { element in | ||||
|             return getAttachmentUrl(for: element) | ||||
|         } | ||||
|  | ||||
|         guard !attachmentUrls.isEmpty else { | ||||
|             print("Invalid URLs for attachments: \(attachmentUrls)") | ||||
|             return | ||||
|         } | ||||
|  | ||||
|         let targetSize = 512 | ||||
|         let scaleProcessor = ResizingImageProcessor(referenceSize: CGSize(width: targetSize, height: targetSize), mode: .aspectFit) | ||||
|  | ||||
|         for attachmentUrl in attachmentUrls { | ||||
|             guard let remoteUrl = URL(string: attachmentUrl) else { | ||||
|                 print("Invalid URL for attachment: \(attachmentUrl)") | ||||
|                 continue // Skip this URL and move to the next one | ||||
|             } | ||||
|  | ||||
|             KingfisherManager.shared.retrieveImage(with: remoteUrl, options: scaleDown ? [ | ||||
|                 .processor(scaleProcessor) | ||||
|             ] : nil) { [weak self] result in | ||||
|                 guard let self = self else { return } | ||||
|  | ||||
|                 switch result { | ||||
|                 case .success(let retrievalResult): | ||||
|                     // The image is either retrieved from cache or downloaded | ||||
|                     let tempDirectory = FileManager.default.temporaryDirectory | ||||
|                     let cachedFileUrl = tempDirectory.appendingPathComponent(UUID().uuidString) // Unique identifier for each file | ||||
|  | ||||
|                     do { | ||||
|                         // Write the image data to a temporary file for UNNotificationAttachment | ||||
|                         try retrievalResult.image.pngData()?.write(to: cachedFileUrl) | ||||
|                         self.attachLocalMedia(to: content, fileType: type?.identifier, from: cachedFileUrl, withIdentifier: attachmentUrl) | ||||
|                     } catch { | ||||
|                         print("Failed to write media to temporary file: \(error.localizedDescription)") | ||||
|                         self.contentHandler?(content) | ||||
|                     } | ||||
|  | ||||
|                 case .failure(let error): | ||||
|                     print("Failed to retrieve image: \(error.localizedDescription)") | ||||
|                     self.contentHandler?(content) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     private func attachLocalMedia(to content: UNMutableNotificationContent, fileType type: String?, from localUrl: URL, withIdentifier identifier: String) { | ||||
|         do { | ||||
|             let attachment = try UNNotificationAttachment(identifier: identifier, url: localUrl, options: [ | ||||
|                 UNNotificationAttachmentOptionsTypeHintKey: type as Any, | ||||
|                 UNNotificationAttachmentOptionsThumbnailHiddenKey: 0, | ||||
|             ]) | ||||
|             content.attachments = [attachment] | ||||
|         } catch let error as NSError { | ||||
|             // Log detailed error information | ||||
|             print("Failed to create attachment from file at \(localUrl.path)") | ||||
|             print("Error: \(error.localizedDescription)") | ||||
|              | ||||
|             // Check specific error codes if needed | ||||
|             if error.domain == NSCocoaErrorDomain { | ||||
|                 switch error.code { | ||||
|                 case NSFileReadNoSuchFileError: | ||||
|                     print("File does not exist at \(localUrl.path)") | ||||
|                 case NSFileReadNoPermissionError: | ||||
|                     print("No permission to read file at \(localUrl.path)") | ||||
|                 default: | ||||
|                     print("Unhandled file error: \(error.code)") | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         // Call content handler regardless of success or failure | ||||
|         self.contentHandler?(content) | ||||
|     } | ||||
|      | ||||
|     private func createMessageIntent(with sender: INPerson, meta: [AnyHashable: Any], body: String) -> INSendMessageIntent { | ||||
|         INSendMessageIntent( | ||||
|             recipients: nil, | ||||
|             outgoingMessageType: .outgoingMessageText, | ||||
|             content: body, | ||||
|             speakableGroupName: meta["room_name"] != nil ? INSpeakableString(spokenPhrase: meta["room_name"] as! String) : nil, | ||||
|             conversationIdentifier: "\(meta["room_id"] ?? "")", | ||||
|             serviceName: nil, | ||||
|             sender: sender, | ||||
|             attachments: nil | ||||
|         ) | ||||
|     } | ||||
|      | ||||
|     private func donateInteraction(for intent: INIntent) { | ||||
|         let interaction = INInteraction(intent: intent, response: nil) | ||||
|         interaction.direction = .incoming | ||||
|         interaction.donate(completion: nil) | ||||
|     } | ||||
| } | ||||
| @@ -186,7 +186,7 @@ class MessageRepository { | ||||
|   } | ||||
|  | ||||
|   Future<LocalChatMessage> sendMessage( | ||||
|     String atk, | ||||
|     String token, | ||||
|     String baseUrl, | ||||
|     String roomId, | ||||
|     String content, | ||||
| @@ -231,8 +231,8 @@ class MessageRepository { | ||||
|       for (var idx = 0; idx < attachments.length; idx++) { | ||||
|         final cloudFile = | ||||
|             await putMediaToCloud( | ||||
|               fileData: attachments[idx].data, | ||||
|               atk: atk, | ||||
|               fileData: attachments[idx], | ||||
|               atk: token, | ||||
|               baseUrl: baseUrl, | ||||
|               filename: attachments[idx].data.name ?? 'Post media', | ||||
|               mimetype: | ||||
| @@ -299,7 +299,7 @@ class MessageRepository { | ||||
|   } | ||||
|  | ||||
|   Future<LocalChatMessage> retryMessage(String pendingMessageId) async { | ||||
|     final message = pendingMessages[pendingMessageId]; | ||||
|     final message = await getMessageById(pendingMessageId); | ||||
|     if (message == null) { | ||||
|       throw Exception('Message not found'); | ||||
|     } | ||||
|   | ||||
| @@ -85,4 +85,5 @@ class DefaultFirebaseOptions { | ||||
|     storageBucket: 'solian-0x001.firebasestorage.app', | ||||
|     measurementId: 'G-JD1YEG9D6F', | ||||
|   ); | ||||
| } | ||||
|  | ||||
| } | ||||
| @@ -4,6 +4,7 @@ import 'dart:io'; | ||||
| import 'package:croppy/croppy.dart'; | ||||
| import 'package:easy_localization/easy_localization.dart' hide TextDirection; | ||||
| import 'package:firebase_core/firebase_core.dart'; | ||||
| import 'package:firebase_messaging/firebase_messaging.dart'; | ||||
| import 'package:flutter/foundation.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter_hooks/flutter_hooks.dart'; | ||||
| @@ -19,20 +20,41 @@ import 'package:island/pods/websocket.dart'; | ||||
| import 'package:island/route.dart'; | ||||
| import 'package:island/screens/auth/tabs.dart'; | ||||
| import 'package:island/services/notify.dart'; | ||||
| import 'package:island/services/timezone.dart'; | ||||
| import 'package:island/widgets/alert.dart'; | ||||
| import 'package:island/widgets/app_scaffold.dart'; | ||||
| import 'package:relative_time/relative_time.dart'; | ||||
| import 'package:shared_preferences/shared_preferences.dart'; | ||||
| import 'package:image_picker_platform_interface/image_picker_platform_interface.dart'; | ||||
| import 'package:flutter_native_splash/flutter_native_splash.dart'; | ||||
| import 'package:url_launcher/url_launcher_string.dart'; | ||||
|  | ||||
| void main() async { | ||||
|   final widgetsBinding = WidgetsFlutterBinding.ensureInitialized(); | ||||
|   if (!kIsWeb && (Platform.isAndroid || Platform.isIOS)) { | ||||
|     log( | ||||
|       "[SplashScreen] Keeping the flash screen to loading other resources...", | ||||
|     ); | ||||
|     FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding); | ||||
|   } | ||||
|  | ||||
|   await EasyLocalization.ensureInitialized(); | ||||
|   await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); | ||||
|   try { | ||||
|     await EasyLocalization.ensureInitialized(); | ||||
|     await Firebase.initializeApp( | ||||
|       options: DefaultFirebaseOptions.currentPlatform, | ||||
|     ); | ||||
|     log("[SplashScreen] Firebase is ready!"); | ||||
|   } catch (err) { | ||||
|     showErrorAlert(err); | ||||
|   } | ||||
|  | ||||
|   try { | ||||
|     log("[SplashScreen] Loading timezone database..."); | ||||
|     await initializeTzdb(); | ||||
|     log("[SplashScreen] Time zone database was loaded!"); | ||||
|   } catch (err) { | ||||
|     log("[SplashScreen] Failed to load timezone database... $err"); | ||||
|   } | ||||
|  | ||||
|   final prefs = await SharedPreferences.getInstance(); | ||||
|  | ||||
| @@ -43,6 +65,7 @@ void main() async { | ||||
|       appWindow.size = initialSize; | ||||
|       appWindow.alignment = Alignment.center; | ||||
|       appWindow.show(); | ||||
|       log("[SplashScreen] Desktop window is ready!"); | ||||
|     }); | ||||
|   } | ||||
|  | ||||
| @@ -52,10 +75,12 @@ void main() async { | ||||
|     if (imagePickerImplementation is ImagePickerAndroid) { | ||||
|       imagePickerImplementation.useAndroidPhotoPicker = true; | ||||
|     } | ||||
|     log("[SplashScreen] Android image picker is ready!"); | ||||
|   } | ||||
|  | ||||
|   if (!kIsWeb && (Platform.isAndroid || Platform.isIOS)) { | ||||
|     FlutterNativeSplash.remove(); | ||||
|     log("[SplashScreen] Now hiding the splash screen..."); | ||||
|   } | ||||
|  | ||||
|   runApp( | ||||
| @@ -79,7 +104,7 @@ void main() async { | ||||
|   ); | ||||
| } | ||||
|  | ||||
| final _appRouter = AppRouter(); | ||||
| final appRouter = AppRouter(); | ||||
|  | ||||
| class IslandApp extends HookConsumerWidget { | ||||
|   const IslandApp({super.key}); | ||||
| @@ -88,6 +113,33 @@ class IslandApp extends HookConsumerWidget { | ||||
|   Widget build(BuildContext context, WidgetRef ref) { | ||||
|     final theme = ref.watch(themeProvider); | ||||
|  | ||||
|     void handleMessage(RemoteMessage notification) { | ||||
|       if (notification.data['action_uri'] != null) { | ||||
|         var uri = notification.data['action_uri'] as String; | ||||
|         if (uri.startsWith('/')) { | ||||
|           // In-app routes | ||||
|           appRouter.pushPath(notification.data['action_uri']); | ||||
|         } else { | ||||
|           // External links | ||||
|           launchUrlString(uri); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     useEffect(() { | ||||
|       Future(() async { | ||||
|         RemoteMessage? initialMessage = | ||||
|             await FirebaseMessaging.instance.getInitialMessage(); | ||||
|         if (initialMessage != null) { | ||||
|           handleMessage(initialMessage); | ||||
|         } | ||||
|  | ||||
|         FirebaseMessaging.onMessageOpenedApp.listen(handleMessage); | ||||
|       }); | ||||
|  | ||||
|       return null; | ||||
|     }, []); | ||||
|  | ||||
|     useEffect(() { | ||||
|       // Load userinfo | ||||
|       final userNotifier = ref.read(userInfoProvider.notifier); | ||||
| @@ -112,7 +164,7 @@ class IslandApp extends HookConsumerWidget { | ||||
|       theme: theme?.light, | ||||
|       darkTheme: theme?.dark, | ||||
|       themeMode: ThemeMode.system, | ||||
|       routerConfig: _appRouter.config( | ||||
|       routerConfig: appRouter.config( | ||||
|         navigatorObservers: | ||||
|             () => [ | ||||
|               TabNavigationObserver( | ||||
| @@ -135,9 +187,9 @@ class IslandApp extends HookConsumerWidget { | ||||
|             OverlayEntry( | ||||
|               builder: | ||||
|                   (_) => WindowScaffold( | ||||
|                     router: _appRouter, | ||||
|                     router: appRouter, | ||||
|                     child: TabsNavigationWidget( | ||||
|                       router: _appRouter, | ||||
|                       router: appRouter, | ||||
|                       child: child ?? const SizedBox.shrink(), | ||||
|                     ), | ||||
|                   ), | ||||
|   | ||||
| @@ -5,18 +5,15 @@ part 'activity.freezed.dart'; | ||||
| part 'activity.g.dart'; | ||||
|  | ||||
| @freezed | ||||
| abstract class SnActivity with _$SnActivity { | ||||
| sealed class SnActivity with _$SnActivity { | ||||
|   const factory SnActivity({ | ||||
|     required String id, | ||||
|     required String type, | ||||
|     required String resourceIdentifier, | ||||
|     required int visibility, | ||||
|     required String accountId, | ||||
|     required SnAccount account, | ||||
|     required dynamic data, | ||||
|     required DateTime createdAt, | ||||
|     required DateTime updatedAt, | ||||
|     required dynamic deletedAt, | ||||
|     required DateTime? deletedAt, | ||||
|   }) = _SnActivity; | ||||
|  | ||||
|   factory SnActivity.fromJson(Map<String, dynamic> json) => | ||||
| @@ -24,7 +21,7 @@ abstract class SnActivity with _$SnActivity { | ||||
| } | ||||
|  | ||||
| @freezed | ||||
| abstract class SnCheckInResult with _$SnCheckInResult { | ||||
| sealed class SnCheckInResult with _$SnCheckInResult { | ||||
|   const factory SnCheckInResult({ | ||||
|     required String id, | ||||
|     required int level, | ||||
| @@ -41,7 +38,7 @@ abstract class SnCheckInResult with _$SnCheckInResult { | ||||
| } | ||||
|  | ||||
| @freezed | ||||
| abstract class SnFortuneTip with _$SnFortuneTip { | ||||
| sealed class SnFortuneTip with _$SnFortuneTip { | ||||
|   const factory SnFortuneTip({ | ||||
|     required bool isPositive, | ||||
|     required String title, | ||||
| @@ -53,7 +50,7 @@ abstract class SnFortuneTip with _$SnFortuneTip { | ||||
| } | ||||
|  | ||||
| @freezed | ||||
| abstract class SnEventCalendarEntry with _$SnEventCalendarEntry { | ||||
| sealed class SnEventCalendarEntry with _$SnEventCalendarEntry { | ||||
|   const factory SnEventCalendarEntry({ | ||||
|     required DateTime date, | ||||
|     required SnCheckInResult? checkInResult, | ||||
|   | ||||
| @@ -16,7 +16,7 @@ T _$identity<T>(T value) => value; | ||||
| /// @nodoc | ||||
| mixin _$SnActivity { | ||||
|  | ||||
|  String get id; String get type; String get resourceIdentifier; int get visibility; String get accountId; SnAccount get account; dynamic get data; DateTime get createdAt; DateTime get updatedAt; dynamic get deletedAt; | ||||
|  String get id; String get type; String get resourceIdentifier; dynamic get data; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; | ||||
| /// Create a copy of SnActivity | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @JsonKey(includeFromJson: false, includeToJson: false) | ||||
| @@ -29,16 +29,16 @@ $SnActivityCopyWith<SnActivity> get copyWith => _$SnActivityCopyWithImpl<SnActiv | ||||
|  | ||||
| @override | ||||
| bool operator ==(Object other) { | ||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is SnActivity&&(identical(other.id, id) || other.id == id)&&(identical(other.type, type) || other.type == type)&&(identical(other.resourceIdentifier, resourceIdentifier) || other.resourceIdentifier == resourceIdentifier)&&(identical(other.visibility, visibility) || other.visibility == visibility)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.account, account) || other.account == account)&&const DeepCollectionEquality().equals(other.data, data)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&const DeepCollectionEquality().equals(other.deletedAt, deletedAt)); | ||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is SnActivity&&(identical(other.id, id) || other.id == id)&&(identical(other.type, type) || other.type == type)&&(identical(other.resourceIdentifier, resourceIdentifier) || other.resourceIdentifier == resourceIdentifier)&&const DeepCollectionEquality().equals(other.data, data)&&(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,type,resourceIdentifier,visibility,accountId,account,const DeepCollectionEquality().hash(data),createdAt,updatedAt,const DeepCollectionEquality().hash(deletedAt)); | ||||
| int get hashCode => Object.hash(runtimeType,id,type,resourceIdentifier,const DeepCollectionEquality().hash(data),createdAt,updatedAt,deletedAt); | ||||
|  | ||||
| @override | ||||
| String toString() { | ||||
|   return 'SnActivity(id: $id, type: $type, resourceIdentifier: $resourceIdentifier, visibility: $visibility, accountId: $accountId, account: $account, data: $data, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; | ||||
|   return 'SnActivity(id: $id, type: $type, resourceIdentifier: $resourceIdentifier, data: $data, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; | ||||
| } | ||||
|  | ||||
|  | ||||
| @@ -49,11 +49,11 @@ abstract mixin class $SnActivityCopyWith<$Res>  { | ||||
|   factory $SnActivityCopyWith(SnActivity value, $Res Function(SnActivity) _then) = _$SnActivityCopyWithImpl; | ||||
| @useResult | ||||
| $Res call({ | ||||
|  String id, String type, String resourceIdentifier, int visibility, String accountId, SnAccount account, dynamic data, DateTime createdAt, DateTime updatedAt, dynamic deletedAt | ||||
|  String id, String type, String resourceIdentifier, dynamic data, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt | ||||
| }); | ||||
|  | ||||
|  | ||||
| $SnAccountCopyWith<$Res> get account; | ||||
|  | ||||
|  | ||||
| } | ||||
| /// @nodoc | ||||
| @@ -66,31 +66,19 @@ class _$SnActivityCopyWithImpl<$Res> | ||||
|  | ||||
| /// Create a copy of SnActivity | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? type = null,Object? resourceIdentifier = null,Object? visibility = null,Object? accountId = null,Object? account = null,Object? data = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) { | ||||
| @pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? type = null,Object? resourceIdentifier = null,Object? data = freezed,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,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable | ||||
| as String,resourceIdentifier: null == resourceIdentifier ? _self.resourceIdentifier : resourceIdentifier // ignore: cast_nullable_to_non_nullable | ||||
| as String,visibility: null == visibility ? _self.visibility : visibility // ignore: cast_nullable_to_non_nullable | ||||
| as int,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable | ||||
| as String,account: null == account ? _self.account : account // ignore: cast_nullable_to_non_nullable | ||||
| as SnAccount,data: freezed == data ? _self.data : data // ignore: cast_nullable_to_non_nullable | ||||
| as String,data: freezed == data ? _self.data : data // ignore: cast_nullable_to_non_nullable | ||||
| as dynamic,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 dynamic, | ||||
| as DateTime?, | ||||
|   )); | ||||
| } | ||||
| /// Create a copy of SnActivity | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @override | ||||
| @pragma('vm:prefer-inline') | ||||
| $SnAccountCopyWith<$Res> get account { | ||||
|    | ||||
|   return $SnAccountCopyWith<$Res>(_self.account, (value) { | ||||
|     return _then(_self.copyWith(account: value)); | ||||
|   }); | ||||
| } | ||||
|  | ||||
| } | ||||
|  | ||||
|  | ||||
| @@ -98,19 +86,16 @@ $SnAccountCopyWith<$Res> get account { | ||||
| @JsonSerializable() | ||||
|  | ||||
| class _SnActivity implements SnActivity { | ||||
|   const _SnActivity({required this.id, required this.type, required this.resourceIdentifier, required this.visibility, required this.accountId, required this.account, required this.data, required this.createdAt, required this.updatedAt, required this.deletedAt}); | ||||
|   const _SnActivity({required this.id, required this.type, required this.resourceIdentifier, required this.data, required this.createdAt, required this.updatedAt, required this.deletedAt}); | ||||
|   factory _SnActivity.fromJson(Map<String, dynamic> json) => _$SnActivityFromJson(json); | ||||
|  | ||||
| @override final  String id; | ||||
| @override final  String type; | ||||
| @override final  String resourceIdentifier; | ||||
| @override final  int visibility; | ||||
| @override final  String accountId; | ||||
| @override final  SnAccount account; | ||||
| @override final  dynamic data; | ||||
| @override final  DateTime createdAt; | ||||
| @override final  DateTime updatedAt; | ||||
| @override final  dynamic deletedAt; | ||||
| @override final  DateTime? deletedAt; | ||||
|  | ||||
| /// Create a copy of SnActivity | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @@ -125,16 +110,16 @@ Map<String, dynamic> toJson() { | ||||
|  | ||||
| @override | ||||
| bool operator ==(Object other) { | ||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnActivity&&(identical(other.id, id) || other.id == id)&&(identical(other.type, type) || other.type == type)&&(identical(other.resourceIdentifier, resourceIdentifier) || other.resourceIdentifier == resourceIdentifier)&&(identical(other.visibility, visibility) || other.visibility == visibility)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.account, account) || other.account == account)&&const DeepCollectionEquality().equals(other.data, data)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&const DeepCollectionEquality().equals(other.deletedAt, deletedAt)); | ||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnActivity&&(identical(other.id, id) || other.id == id)&&(identical(other.type, type) || other.type == type)&&(identical(other.resourceIdentifier, resourceIdentifier) || other.resourceIdentifier == resourceIdentifier)&&const DeepCollectionEquality().equals(other.data, data)&&(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,type,resourceIdentifier,visibility,accountId,account,const DeepCollectionEquality().hash(data),createdAt,updatedAt,const DeepCollectionEquality().hash(deletedAt)); | ||||
| int get hashCode => Object.hash(runtimeType,id,type,resourceIdentifier,const DeepCollectionEquality().hash(data),createdAt,updatedAt,deletedAt); | ||||
|  | ||||
| @override | ||||
| String toString() { | ||||
|   return 'SnActivity(id: $id, type: $type, resourceIdentifier: $resourceIdentifier, visibility: $visibility, accountId: $accountId, account: $account, data: $data, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; | ||||
|   return 'SnActivity(id: $id, type: $type, resourceIdentifier: $resourceIdentifier, data: $data, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; | ||||
| } | ||||
|  | ||||
|  | ||||
| @@ -145,11 +130,11 @@ abstract mixin class _$SnActivityCopyWith<$Res> implements $SnActivityCopyWith<$ | ||||
|   factory _$SnActivityCopyWith(_SnActivity value, $Res Function(_SnActivity) _then) = __$SnActivityCopyWithImpl; | ||||
| @override @useResult | ||||
| $Res call({ | ||||
|  String id, String type, String resourceIdentifier, int visibility, String accountId, SnAccount account, dynamic data, DateTime createdAt, DateTime updatedAt, dynamic deletedAt | ||||
|  String id, String type, String resourceIdentifier, dynamic data, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt | ||||
| }); | ||||
|  | ||||
|  | ||||
| @override $SnAccountCopyWith<$Res> get account; | ||||
|  | ||||
|  | ||||
| } | ||||
| /// @nodoc | ||||
| @@ -162,32 +147,20 @@ class __$SnActivityCopyWithImpl<$Res> | ||||
|  | ||||
| /// Create a copy of SnActivity | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? type = null,Object? resourceIdentifier = null,Object? visibility = null,Object? accountId = null,Object? account = null,Object? data = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) { | ||||
| @override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? type = null,Object? resourceIdentifier = null,Object? data = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) { | ||||
|   return _then(_SnActivity( | ||||
| id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable | ||||
| as String,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable | ||||
| as String,resourceIdentifier: null == resourceIdentifier ? _self.resourceIdentifier : resourceIdentifier // ignore: cast_nullable_to_non_nullable | ||||
| as String,visibility: null == visibility ? _self.visibility : visibility // ignore: cast_nullable_to_non_nullable | ||||
| as int,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable | ||||
| as String,account: null == account ? _self.account : account // ignore: cast_nullable_to_non_nullable | ||||
| as SnAccount,data: freezed == data ? _self.data : data // ignore: cast_nullable_to_non_nullable | ||||
| as String,data: freezed == data ? _self.data : data // ignore: cast_nullable_to_non_nullable | ||||
| as dynamic,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 dynamic, | ||||
| as DateTime?, | ||||
|   )); | ||||
| } | ||||
|  | ||||
| /// Create a copy of SnActivity | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @override | ||||
| @pragma('vm:prefer-inline') | ||||
| $SnAccountCopyWith<$Res> get account { | ||||
|    | ||||
|   return $SnAccountCopyWith<$Res>(_self.account, (value) { | ||||
|     return _then(_self.copyWith(account: value)); | ||||
|   }); | ||||
| } | ||||
|  | ||||
| } | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -10,13 +10,13 @@ _SnActivity _$SnActivityFromJson(Map<String, dynamic> json) => _SnActivity( | ||||
|   id: json['id'] as String, | ||||
|   type: json['type'] as String, | ||||
|   resourceIdentifier: json['resource_identifier'] as String, | ||||
|   visibility: (json['visibility'] as num).toInt(), | ||||
|   accountId: json['account_id'] as String, | ||||
|   account: SnAccount.fromJson(json['account'] as Map<String, dynamic>), | ||||
|   data: json['data'], | ||||
|   createdAt: DateTime.parse(json['created_at'] as String), | ||||
|   updatedAt: DateTime.parse(json['updated_at'] as String), | ||||
|   deletedAt: json['deleted_at'], | ||||
|   deletedAt: | ||||
|       json['deleted_at'] == null | ||||
|           ? null | ||||
|           : DateTime.parse(json['deleted_at'] as String), | ||||
| ); | ||||
|  | ||||
| Map<String, dynamic> _$SnActivityToJson(_SnActivity instance) => | ||||
| @@ -24,13 +24,10 @@ Map<String, dynamic> _$SnActivityToJson(_SnActivity instance) => | ||||
|       'id': instance.id, | ||||
|       'type': instance.type, | ||||
|       'resource_identifier': instance.resourceIdentifier, | ||||
|       'visibility': instance.visibility, | ||||
|       'account_id': instance.accountId, | ||||
|       'account': instance.account.toJson(), | ||||
|       'data': instance.data, | ||||
|       'created_at': instance.createdAt.toIso8601String(), | ||||
|       'updated_at': instance.updatedAt.toIso8601String(), | ||||
|       'deleted_at': instance.deletedAt, | ||||
|       'deleted_at': instance.deletedAt?.toIso8601String(), | ||||
|     }; | ||||
|  | ||||
| _SnCheckInResult _$SnCheckInResultFromJson(Map<String, dynamic> json) => | ||||
|   | ||||
| @@ -4,30 +4,32 @@ part 'auth.freezed.dart'; | ||||
| part 'auth.g.dart'; | ||||
|  | ||||
| @freezed | ||||
| abstract class AppTokenPair with _$AppTokenPair { | ||||
|   const factory AppTokenPair({ | ||||
|     required String accessToken, | ||||
|     required String refreshToken, | ||||
|   }) = _AppTokenPair; | ||||
| sealed class AppToken with _$AppToken { | ||||
|   const factory AppToken({required String token}) = _AppToken; | ||||
|  | ||||
|   factory AppTokenPair.fromJson(Map<String, dynamic> json) => | ||||
|       _$AppTokenPairFromJson(json); | ||||
|   factory AppToken.fromJson(Map<String, dynamic> json) => | ||||
|       _$AppTokenFromJson(json); | ||||
| } | ||||
|  | ||||
| @freezed | ||||
| abstract class SnAuthChallenge with _$SnAuthChallenge { | ||||
| sealed class SnAuthChallenge with _$SnAuthChallenge { | ||||
|   const factory SnAuthChallenge({ | ||||
|     required String id, | ||||
|     required DateTime expiredAt, | ||||
|     required int stepRemain, | ||||
|     required int stepTotal, | ||||
|     required int failedAttempts, | ||||
|     required int platform, | ||||
|     required int type, | ||||
|     required List<String> blacklistFactors, | ||||
|     required List<String> audiences, | ||||
|     required List<String> scopes, | ||||
|     required List<dynamic> audiences, | ||||
|     required List<dynamic> scopes, | ||||
|     required String ipAddress, | ||||
|     required String userAgent, | ||||
|     required String? deviceId, | ||||
|     required String deviceId, | ||||
|     required String? nonce, | ||||
|     required String? location, | ||||
|     required String accountId, | ||||
|     required DateTime createdAt, | ||||
|     required DateTime updatedAt, | ||||
|     required DateTime? deletedAt, | ||||
| @@ -38,15 +40,72 @@ abstract class SnAuthChallenge with _$SnAuthChallenge { | ||||
| } | ||||
|  | ||||
| @freezed | ||||
| abstract class SnAuthFactor with _$SnAuthFactor { | ||||
| sealed class SnAuthSession with _$SnAuthSession { | ||||
|   const factory SnAuthSession({ | ||||
|     required String id, | ||||
|     required String? label, | ||||
|     required DateTime lastGrantedAt, | ||||
|     required DateTime expiredAt, | ||||
|     required String accountId, | ||||
|     required String challengeId, | ||||
|     required SnAuthChallenge challenge, | ||||
|     required DateTime createdAt, | ||||
|     required DateTime updatedAt, | ||||
|     required DateTime? deletedAt, | ||||
|   }) = _SnAuthSession; | ||||
|  | ||||
|   factory SnAuthSession.fromJson(Map<String, dynamic> json) => | ||||
|       _$SnAuthSessionFromJson(json); | ||||
| } | ||||
|  | ||||
| @freezed | ||||
| sealed class SnAuthFactor with _$SnAuthFactor { | ||||
|   const factory SnAuthFactor({ | ||||
|     required String id, | ||||
|     required int type, | ||||
|     required DateTime createdAt, | ||||
|     required DateTime updatedAt, | ||||
|     required DateTime? deletedAt, | ||||
|     required DateTime? expiredAt, | ||||
|     required DateTime? enabledAt, | ||||
|     required int trustworthy, | ||||
|     required Map<String, dynamic>? createdResponse, | ||||
|   }) = _SnAuthFactor; | ||||
|  | ||||
|   factory SnAuthFactor.fromJson(Map<String, dynamic> json) => | ||||
|       _$SnAuthFactorFromJson(json); | ||||
| } | ||||
|  | ||||
| @freezed | ||||
| sealed class SnAuthDevice with _$SnAuthDevice { | ||||
|   const factory SnAuthDevice({ | ||||
|     required dynamic label, | ||||
|     required String userAgent, | ||||
|     required String deviceId, | ||||
|     required int platform, | ||||
|     required List<SnAuthSession> sessions, | ||||
|     // Not from backend, used for UI | ||||
|     @Default(false) bool isCurrent, | ||||
|   }) = _SnAuthDevice; | ||||
|  | ||||
|   factory SnAuthDevice.fromJson(Map<String, dynamic> json) => | ||||
|       _$SnAuthDeviceFromJson(json); | ||||
| } | ||||
|  | ||||
| @freezed | ||||
| sealed class SnAccountConnection with _$SnAccountConnection { | ||||
|   const factory SnAccountConnection({ | ||||
|     required String id, | ||||
|     required String accountId, | ||||
|     required String provider, | ||||
|     required String providedIdentifier, | ||||
|     @Default({}) Map<String, dynamic> meta, | ||||
|     required DateTime lastUsedAt, | ||||
|     required DateTime createdAt, | ||||
|     required DateTime updatedAt, | ||||
|     required DateTime? deletedAt, | ||||
|   }) = _SnAccountConnection; | ||||
|  | ||||
|   factory SnAccountConnection.fromJson(Map<String, dynamic> json) => | ||||
|       _$SnAccountConnectionFromJson(json); | ||||
| } | ||||
|   | ||||
| @@ -14,42 +14,42 @@ part of 'auth.dart'; | ||||
| T _$identity<T>(T value) => value; | ||||
|  | ||||
| /// @nodoc | ||||
| mixin _$AppTokenPair { | ||||
| mixin _$AppToken { | ||||
|  | ||||
|  String get accessToken; String get refreshToken; | ||||
| /// Create a copy of AppTokenPair | ||||
|  String get token; | ||||
| /// Create a copy of AppToken | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @JsonKey(includeFromJson: false, includeToJson: false) | ||||
| @pragma('vm:prefer-inline') | ||||
| $AppTokenPairCopyWith<AppTokenPair> get copyWith => _$AppTokenPairCopyWithImpl<AppTokenPair>(this as AppTokenPair, _$identity); | ||||
| $AppTokenCopyWith<AppToken> get copyWith => _$AppTokenCopyWithImpl<AppToken>(this as AppToken, _$identity); | ||||
|  | ||||
|   /// Serializes this AppTokenPair to a JSON map. | ||||
|   /// Serializes this AppToken to a JSON map. | ||||
|   Map<String, dynamic> toJson(); | ||||
|  | ||||
|  | ||||
| @override | ||||
| bool operator ==(Object other) { | ||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is AppTokenPair&&(identical(other.accessToken, accessToken) || other.accessToken == accessToken)&&(identical(other.refreshToken, refreshToken) || other.refreshToken == refreshToken)); | ||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is AppToken&&(identical(other.token, token) || other.token == token)); | ||||
| } | ||||
|  | ||||
| @JsonKey(includeFromJson: false, includeToJson: false) | ||||
| @override | ||||
| int get hashCode => Object.hash(runtimeType,accessToken,refreshToken); | ||||
| int get hashCode => Object.hash(runtimeType,token); | ||||
|  | ||||
| @override | ||||
| String toString() { | ||||
|   return 'AppTokenPair(accessToken: $accessToken, refreshToken: $refreshToken)'; | ||||
|   return 'AppToken(token: $token)'; | ||||
| } | ||||
|  | ||||
|  | ||||
| } | ||||
|  | ||||
| /// @nodoc | ||||
| abstract mixin class $AppTokenPairCopyWith<$Res>  { | ||||
|   factory $AppTokenPairCopyWith(AppTokenPair value, $Res Function(AppTokenPair) _then) = _$AppTokenPairCopyWithImpl; | ||||
| abstract mixin class $AppTokenCopyWith<$Res>  { | ||||
|   factory $AppTokenCopyWith(AppToken value, $Res Function(AppToken) _then) = _$AppTokenCopyWithImpl; | ||||
| @useResult | ||||
| $Res call({ | ||||
|  String accessToken, String refreshToken | ||||
|  String token | ||||
| }); | ||||
|  | ||||
|  | ||||
| @@ -57,19 +57,18 @@ $Res call({ | ||||
|  | ||||
| } | ||||
| /// @nodoc | ||||
| class _$AppTokenPairCopyWithImpl<$Res> | ||||
|     implements $AppTokenPairCopyWith<$Res> { | ||||
|   _$AppTokenPairCopyWithImpl(this._self, this._then); | ||||
| class _$AppTokenCopyWithImpl<$Res> | ||||
|     implements $AppTokenCopyWith<$Res> { | ||||
|   _$AppTokenCopyWithImpl(this._self, this._then); | ||||
|  | ||||
|   final AppTokenPair _self; | ||||
|   final $Res Function(AppTokenPair) _then; | ||||
|   final AppToken _self; | ||||
|   final $Res Function(AppToken) _then; | ||||
|  | ||||
| /// Create a copy of AppTokenPair | ||||
| /// Create a copy of AppToken | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @pragma('vm:prefer-inline') @override $Res call({Object? accessToken = null,Object? refreshToken = null,}) { | ||||
| @pragma('vm:prefer-inline') @override $Res call({Object? token = null,}) { | ||||
|   return _then(_self.copyWith( | ||||
| accessToken: null == accessToken ? _self.accessToken : accessToken // ignore: cast_nullable_to_non_nullable | ||||
| as String,refreshToken: null == refreshToken ? _self.refreshToken : refreshToken // ignore: cast_nullable_to_non_nullable | ||||
| token: null == token ? _self.token : token // ignore: cast_nullable_to_non_nullable | ||||
| as String, | ||||
|   )); | ||||
| } | ||||
| @@ -80,47 +79,46 @@ as String, | ||||
| /// @nodoc | ||||
| @JsonSerializable() | ||||
|  | ||||
| class _AppTokenPair implements AppTokenPair { | ||||
|   const _AppTokenPair({required this.accessToken, required this.refreshToken}); | ||||
|   factory _AppTokenPair.fromJson(Map<String, dynamic> json) => _$AppTokenPairFromJson(json); | ||||
| class _AppToken implements AppToken { | ||||
|   const _AppToken({required this.token}); | ||||
|   factory _AppToken.fromJson(Map<String, dynamic> json) => _$AppTokenFromJson(json); | ||||
|  | ||||
| @override final  String accessToken; | ||||
| @override final  String refreshToken; | ||||
| @override final  String token; | ||||
|  | ||||
| /// Create a copy of AppTokenPair | ||||
| /// Create a copy of AppToken | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @override @JsonKey(includeFromJson: false, includeToJson: false) | ||||
| @pragma('vm:prefer-inline') | ||||
| _$AppTokenPairCopyWith<_AppTokenPair> get copyWith => __$AppTokenPairCopyWithImpl<_AppTokenPair>(this, _$identity); | ||||
| _$AppTokenCopyWith<_AppToken> get copyWith => __$AppTokenCopyWithImpl<_AppToken>(this, _$identity); | ||||
|  | ||||
| @override | ||||
| Map<String, dynamic> toJson() { | ||||
|   return _$AppTokenPairToJson(this, ); | ||||
|   return _$AppTokenToJson(this, ); | ||||
| } | ||||
|  | ||||
| @override | ||||
| bool operator ==(Object other) { | ||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is _AppTokenPair&&(identical(other.accessToken, accessToken) || other.accessToken == accessToken)&&(identical(other.refreshToken, refreshToken) || other.refreshToken == refreshToken)); | ||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is _AppToken&&(identical(other.token, token) || other.token == token)); | ||||
| } | ||||
|  | ||||
| @JsonKey(includeFromJson: false, includeToJson: false) | ||||
| @override | ||||
| int get hashCode => Object.hash(runtimeType,accessToken,refreshToken); | ||||
| int get hashCode => Object.hash(runtimeType,token); | ||||
|  | ||||
| @override | ||||
| String toString() { | ||||
|   return 'AppTokenPair(accessToken: $accessToken, refreshToken: $refreshToken)'; | ||||
|   return 'AppToken(token: $token)'; | ||||
| } | ||||
|  | ||||
|  | ||||
| } | ||||
|  | ||||
| /// @nodoc | ||||
| abstract mixin class _$AppTokenPairCopyWith<$Res> implements $AppTokenPairCopyWith<$Res> { | ||||
|   factory _$AppTokenPairCopyWith(_AppTokenPair value, $Res Function(_AppTokenPair) _then) = __$AppTokenPairCopyWithImpl; | ||||
| abstract mixin class _$AppTokenCopyWith<$Res> implements $AppTokenCopyWith<$Res> { | ||||
|   factory _$AppTokenCopyWith(_AppToken value, $Res Function(_AppToken) _then) = __$AppTokenCopyWithImpl; | ||||
| @override @useResult | ||||
| $Res call({ | ||||
|  String accessToken, String refreshToken | ||||
|  String token | ||||
| }); | ||||
|  | ||||
|  | ||||
| @@ -128,19 +126,18 @@ $Res call({ | ||||
|  | ||||
| } | ||||
| /// @nodoc | ||||
| class __$AppTokenPairCopyWithImpl<$Res> | ||||
|     implements _$AppTokenPairCopyWith<$Res> { | ||||
|   __$AppTokenPairCopyWithImpl(this._self, this._then); | ||||
| class __$AppTokenCopyWithImpl<$Res> | ||||
|     implements _$AppTokenCopyWith<$Res> { | ||||
|   __$AppTokenCopyWithImpl(this._self, this._then); | ||||
|  | ||||
|   final _AppTokenPair _self; | ||||
|   final $Res Function(_AppTokenPair) _then; | ||||
|   final _AppToken _self; | ||||
|   final $Res Function(_AppToken) _then; | ||||
|  | ||||
| /// Create a copy of AppTokenPair | ||||
| /// Create a copy of AppToken | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @override @pragma('vm:prefer-inline') $Res call({Object? accessToken = null,Object? refreshToken = null,}) { | ||||
|   return _then(_AppTokenPair( | ||||
| accessToken: null == accessToken ? _self.accessToken : accessToken // ignore: cast_nullable_to_non_nullable | ||||
| as String,refreshToken: null == refreshToken ? _self.refreshToken : refreshToken // ignore: cast_nullable_to_non_nullable | ||||
| @override @pragma('vm:prefer-inline') $Res call({Object? token = null,}) { | ||||
|   return _then(_AppToken( | ||||
| token: null == token ? _self.token : token // ignore: cast_nullable_to_non_nullable | ||||
| as String, | ||||
|   )); | ||||
| } | ||||
| @@ -152,7 +149,7 @@ as String, | ||||
| /// @nodoc | ||||
| mixin _$SnAuthChallenge { | ||||
|  | ||||
|  String get id; DateTime get expiredAt; int get stepRemain; int get stepTotal; List<String> get blacklistFactors; List<String> get audiences; List<String> get scopes; String get ipAddress; String get userAgent; String? get deviceId; String? get nonce; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; | ||||
|  String get id; DateTime get expiredAt; int get stepRemain; int get stepTotal; int get failedAttempts; int get platform; int get type; List<String> get blacklistFactors; List<dynamic> get audiences; List<dynamic> get scopes; String get ipAddress; String get userAgent; String get deviceId; String? get nonce; String? get location; String get accountId; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; | ||||
| /// Create a copy of SnAuthChallenge | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @JsonKey(includeFromJson: false, includeToJson: false) | ||||
| @@ -165,16 +162,16 @@ $SnAuthChallengeCopyWith<SnAuthChallenge> get copyWith => _$SnAuthChallengeCopyW | ||||
|  | ||||
| @override | ||||
| bool operator ==(Object other) { | ||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is SnAuthChallenge&&(identical(other.id, id) || other.id == id)&&(identical(other.expiredAt, expiredAt) || other.expiredAt == expiredAt)&&(identical(other.stepRemain, stepRemain) || other.stepRemain == stepRemain)&&(identical(other.stepTotal, stepTotal) || other.stepTotal == stepTotal)&&const DeepCollectionEquality().equals(other.blacklistFactors, blacklistFactors)&&const DeepCollectionEquality().equals(other.audiences, audiences)&&const DeepCollectionEquality().equals(other.scopes, scopes)&&(identical(other.ipAddress, ipAddress) || other.ipAddress == ipAddress)&&(identical(other.userAgent, userAgent) || other.userAgent == userAgent)&&(identical(other.deviceId, deviceId) || other.deviceId == deviceId)&&(identical(other.nonce, nonce) || other.nonce == nonce)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)); | ||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is SnAuthChallenge&&(identical(other.id, id) || other.id == id)&&(identical(other.expiredAt, expiredAt) || other.expiredAt == expiredAt)&&(identical(other.stepRemain, stepRemain) || other.stepRemain == stepRemain)&&(identical(other.stepTotal, stepTotal) || other.stepTotal == stepTotal)&&(identical(other.failedAttempts, failedAttempts) || other.failedAttempts == failedAttempts)&&(identical(other.platform, platform) || other.platform == platform)&&(identical(other.type, type) || other.type == type)&&const DeepCollectionEquality().equals(other.blacklistFactors, blacklistFactors)&&const DeepCollectionEquality().equals(other.audiences, audiences)&&const DeepCollectionEquality().equals(other.scopes, scopes)&&(identical(other.ipAddress, ipAddress) || other.ipAddress == ipAddress)&&(identical(other.userAgent, userAgent) || other.userAgent == userAgent)&&(identical(other.deviceId, deviceId) || other.deviceId == deviceId)&&(identical(other.nonce, nonce) || other.nonce == nonce)&&(identical(other.location, location) || other.location == location)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)); | ||||
| } | ||||
|  | ||||
| @JsonKey(includeFromJson: false, includeToJson: false) | ||||
| @override | ||||
| int get hashCode => Object.hash(runtimeType,id,expiredAt,stepRemain,stepTotal,const DeepCollectionEquality().hash(blacklistFactors),const DeepCollectionEquality().hash(audiences),const DeepCollectionEquality().hash(scopes),ipAddress,userAgent,deviceId,nonce,createdAt,updatedAt,deletedAt); | ||||
| int get hashCode => Object.hashAll([runtimeType,id,expiredAt,stepRemain,stepTotal,failedAttempts,platform,type,const DeepCollectionEquality().hash(blacklistFactors),const DeepCollectionEquality().hash(audiences),const DeepCollectionEquality().hash(scopes),ipAddress,userAgent,deviceId,nonce,location,accountId,createdAt,updatedAt,deletedAt]); | ||||
|  | ||||
| @override | ||||
| String toString() { | ||||
|   return 'SnAuthChallenge(id: $id, expiredAt: $expiredAt, stepRemain: $stepRemain, stepTotal: $stepTotal, blacklistFactors: $blacklistFactors, audiences: $audiences, scopes: $scopes, ipAddress: $ipAddress, userAgent: $userAgent, deviceId: $deviceId, nonce: $nonce, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; | ||||
|   return 'SnAuthChallenge(id: $id, expiredAt: $expiredAt, stepRemain: $stepRemain, stepTotal: $stepTotal, failedAttempts: $failedAttempts, platform: $platform, type: $type, blacklistFactors: $blacklistFactors, audiences: $audiences, scopes: $scopes, ipAddress: $ipAddress, userAgent: $userAgent, deviceId: $deviceId, nonce: $nonce, location: $location, accountId: $accountId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; | ||||
| } | ||||
|  | ||||
|  | ||||
| @@ -185,7 +182,7 @@ abstract mixin class $SnAuthChallengeCopyWith<$Res>  { | ||||
|   factory $SnAuthChallengeCopyWith(SnAuthChallenge value, $Res Function(SnAuthChallenge) _then) = _$SnAuthChallengeCopyWithImpl; | ||||
| @useResult | ||||
| $Res call({ | ||||
|  String id, DateTime expiredAt, int stepRemain, int stepTotal, List<String> blacklistFactors, List<String> audiences, List<String> scopes, String ipAddress, String userAgent, String? deviceId, String? nonce, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt | ||||
|  String id, DateTime expiredAt, int stepRemain, int stepTotal, int failedAttempts, int platform, int type, List<String> blacklistFactors, List<dynamic> audiences, List<dynamic> scopes, String ipAddress, String userAgent, String deviceId, String? nonce, String? location, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt | ||||
| }); | ||||
|  | ||||
|  | ||||
| @@ -202,20 +199,25 @@ class _$SnAuthChallengeCopyWithImpl<$Res> | ||||
|  | ||||
| /// Create a copy of SnAuthChallenge | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? expiredAt = null,Object? stepRemain = null,Object? stepTotal = null,Object? blacklistFactors = null,Object? audiences = null,Object? scopes = null,Object? ipAddress = null,Object? userAgent = null,Object? deviceId = freezed,Object? nonce = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) { | ||||
| @pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? expiredAt = null,Object? stepRemain = null,Object? stepTotal = null,Object? failedAttempts = null,Object? platform = null,Object? type = null,Object? blacklistFactors = null,Object? audiences = null,Object? scopes = null,Object? ipAddress = null,Object? userAgent = null,Object? deviceId = null,Object? nonce = freezed,Object? location = freezed,Object? accountId = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) { | ||||
|   return _then(_self.copyWith( | ||||
| id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable | ||||
| as String,expiredAt: null == expiredAt ? _self.expiredAt : expiredAt // ignore: cast_nullable_to_non_nullable | ||||
| as DateTime,stepRemain: null == stepRemain ? _self.stepRemain : stepRemain // ignore: cast_nullable_to_non_nullable | ||||
| as int,stepTotal: null == stepTotal ? _self.stepTotal : stepTotal // ignore: cast_nullable_to_non_nullable | ||||
| as int,failedAttempts: null == failedAttempts ? _self.failedAttempts : failedAttempts // ignore: cast_nullable_to_non_nullable | ||||
| as int,platform: null == platform ? _self.platform : platform // ignore: cast_nullable_to_non_nullable | ||||
| as int,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable | ||||
| as int,blacklistFactors: null == blacklistFactors ? _self.blacklistFactors : blacklistFactors // ignore: cast_nullable_to_non_nullable | ||||
| as List<String>,audiences: null == audiences ? _self.audiences : audiences // ignore: cast_nullable_to_non_nullable | ||||
| as List<String>,scopes: null == scopes ? _self.scopes : scopes // ignore: cast_nullable_to_non_nullable | ||||
| as List<String>,ipAddress: null == ipAddress ? _self.ipAddress : ipAddress // ignore: cast_nullable_to_non_nullable | ||||
| as List<dynamic>,scopes: null == scopes ? _self.scopes : scopes // ignore: cast_nullable_to_non_nullable | ||||
| as List<dynamic>,ipAddress: null == ipAddress ? _self.ipAddress : ipAddress // ignore: cast_nullable_to_non_nullable | ||||
| as String,userAgent: null == userAgent ? _self.userAgent : userAgent // ignore: cast_nullable_to_non_nullable | ||||
| as String,deviceId: freezed == deviceId ? _self.deviceId : deviceId // ignore: cast_nullable_to_non_nullable | ||||
| as String?,nonce: freezed == nonce ? _self.nonce : nonce // ignore: cast_nullable_to_non_nullable | ||||
| as String?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable | ||||
| as String,deviceId: null == deviceId ? _self.deviceId : deviceId // ignore: cast_nullable_to_non_nullable | ||||
| as String,nonce: freezed == nonce ? _self.nonce : nonce // ignore: cast_nullable_to_non_nullable | ||||
| as String?,location: freezed == location ? _self.location : location // ignore: cast_nullable_to_non_nullable | ||||
| as String?,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable | ||||
| as String,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?, | ||||
| @@ -229,13 +231,16 @@ as DateTime?, | ||||
| @JsonSerializable() | ||||
|  | ||||
| class _SnAuthChallenge implements SnAuthChallenge { | ||||
|   const _SnAuthChallenge({required this.id, required this.expiredAt, required this.stepRemain, required this.stepTotal, required final  List<String> blacklistFactors, required final  List<String> audiences, required final  List<String> scopes, required this.ipAddress, required this.userAgent, required this.deviceId, required this.nonce, required this.createdAt, required this.updatedAt, required this.deletedAt}): _blacklistFactors = blacklistFactors,_audiences = audiences,_scopes = scopes; | ||||
|   const _SnAuthChallenge({required this.id, required this.expiredAt, required this.stepRemain, required this.stepTotal, required this.failedAttempts, required this.platform, required this.type, required final  List<String> blacklistFactors, required final  List<dynamic> audiences, required final  List<dynamic> scopes, required this.ipAddress, required this.userAgent, required this.deviceId, required this.nonce, required this.location, required this.accountId, required this.createdAt, required this.updatedAt, required this.deletedAt}): _blacklistFactors = blacklistFactors,_audiences = audiences,_scopes = scopes; | ||||
|   factory _SnAuthChallenge.fromJson(Map<String, dynamic> json) => _$SnAuthChallengeFromJson(json); | ||||
|  | ||||
| @override final  String id; | ||||
| @override final  DateTime expiredAt; | ||||
| @override final  int stepRemain; | ||||
| @override final  int stepTotal; | ||||
| @override final  int failedAttempts; | ||||
| @override final  int platform; | ||||
| @override final  int type; | ||||
|  final  List<String> _blacklistFactors; | ||||
| @override List<String> get blacklistFactors { | ||||
|   if (_blacklistFactors is EqualUnmodifiableListView) return _blacklistFactors; | ||||
| @@ -243,15 +248,15 @@ class _SnAuthChallenge implements SnAuthChallenge { | ||||
|   return EqualUnmodifiableListView(_blacklistFactors); | ||||
| } | ||||
|  | ||||
|  final  List<String> _audiences; | ||||
| @override List<String> get audiences { | ||||
|  final  List<dynamic> _audiences; | ||||
| @override List<dynamic> get audiences { | ||||
|   if (_audiences is EqualUnmodifiableListView) return _audiences; | ||||
|   // ignore: implicit_dynamic_type | ||||
|   return EqualUnmodifiableListView(_audiences); | ||||
| } | ||||
|  | ||||
|  final  List<String> _scopes; | ||||
| @override List<String> get scopes { | ||||
|  final  List<dynamic> _scopes; | ||||
| @override List<dynamic> get scopes { | ||||
|   if (_scopes is EqualUnmodifiableListView) return _scopes; | ||||
|   // ignore: implicit_dynamic_type | ||||
|   return EqualUnmodifiableListView(_scopes); | ||||
| @@ -259,8 +264,10 @@ class _SnAuthChallenge implements SnAuthChallenge { | ||||
|  | ||||
| @override final  String ipAddress; | ||||
| @override final  String userAgent; | ||||
| @override final  String? deviceId; | ||||
| @override final  String deviceId; | ||||
| @override final  String? nonce; | ||||
| @override final  String? location; | ||||
| @override final  String accountId; | ||||
| @override final  DateTime createdAt; | ||||
| @override final  DateTime updatedAt; | ||||
| @override final  DateTime? deletedAt; | ||||
| @@ -278,16 +285,16 @@ Map<String, dynamic> toJson() { | ||||
|  | ||||
| @override | ||||
| bool operator ==(Object other) { | ||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnAuthChallenge&&(identical(other.id, id) || other.id == id)&&(identical(other.expiredAt, expiredAt) || other.expiredAt == expiredAt)&&(identical(other.stepRemain, stepRemain) || other.stepRemain == stepRemain)&&(identical(other.stepTotal, stepTotal) || other.stepTotal == stepTotal)&&const DeepCollectionEquality().equals(other._blacklistFactors, _blacklistFactors)&&const DeepCollectionEquality().equals(other._audiences, _audiences)&&const DeepCollectionEquality().equals(other._scopes, _scopes)&&(identical(other.ipAddress, ipAddress) || other.ipAddress == ipAddress)&&(identical(other.userAgent, userAgent) || other.userAgent == userAgent)&&(identical(other.deviceId, deviceId) || other.deviceId == deviceId)&&(identical(other.nonce, nonce) || other.nonce == nonce)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)); | ||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnAuthChallenge&&(identical(other.id, id) || other.id == id)&&(identical(other.expiredAt, expiredAt) || other.expiredAt == expiredAt)&&(identical(other.stepRemain, stepRemain) || other.stepRemain == stepRemain)&&(identical(other.stepTotal, stepTotal) || other.stepTotal == stepTotal)&&(identical(other.failedAttempts, failedAttempts) || other.failedAttempts == failedAttempts)&&(identical(other.platform, platform) || other.platform == platform)&&(identical(other.type, type) || other.type == type)&&const DeepCollectionEquality().equals(other._blacklistFactors, _blacklistFactors)&&const DeepCollectionEquality().equals(other._audiences, _audiences)&&const DeepCollectionEquality().equals(other._scopes, _scopes)&&(identical(other.ipAddress, ipAddress) || other.ipAddress == ipAddress)&&(identical(other.userAgent, userAgent) || other.userAgent == userAgent)&&(identical(other.deviceId, deviceId) || other.deviceId == deviceId)&&(identical(other.nonce, nonce) || other.nonce == nonce)&&(identical(other.location, location) || other.location == location)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)); | ||||
| } | ||||
|  | ||||
| @JsonKey(includeFromJson: false, includeToJson: false) | ||||
| @override | ||||
| int get hashCode => Object.hash(runtimeType,id,expiredAt,stepRemain,stepTotal,const DeepCollectionEquality().hash(_blacklistFactors),const DeepCollectionEquality().hash(_audiences),const DeepCollectionEquality().hash(_scopes),ipAddress,userAgent,deviceId,nonce,createdAt,updatedAt,deletedAt); | ||||
| int get hashCode => Object.hashAll([runtimeType,id,expiredAt,stepRemain,stepTotal,failedAttempts,platform,type,const DeepCollectionEquality().hash(_blacklistFactors),const DeepCollectionEquality().hash(_audiences),const DeepCollectionEquality().hash(_scopes),ipAddress,userAgent,deviceId,nonce,location,accountId,createdAt,updatedAt,deletedAt]); | ||||
|  | ||||
| @override | ||||
| String toString() { | ||||
|   return 'SnAuthChallenge(id: $id, expiredAt: $expiredAt, stepRemain: $stepRemain, stepTotal: $stepTotal, blacklistFactors: $blacklistFactors, audiences: $audiences, scopes: $scopes, ipAddress: $ipAddress, userAgent: $userAgent, deviceId: $deviceId, nonce: $nonce, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; | ||||
|   return 'SnAuthChallenge(id: $id, expiredAt: $expiredAt, stepRemain: $stepRemain, stepTotal: $stepTotal, failedAttempts: $failedAttempts, platform: $platform, type: $type, blacklistFactors: $blacklistFactors, audiences: $audiences, scopes: $scopes, ipAddress: $ipAddress, userAgent: $userAgent, deviceId: $deviceId, nonce: $nonce, location: $location, accountId: $accountId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; | ||||
| } | ||||
|  | ||||
|  | ||||
| @@ -298,7 +305,7 @@ abstract mixin class _$SnAuthChallengeCopyWith<$Res> implements $SnAuthChallenge | ||||
|   factory _$SnAuthChallengeCopyWith(_SnAuthChallenge value, $Res Function(_SnAuthChallenge) _then) = __$SnAuthChallengeCopyWithImpl; | ||||
| @override @useResult | ||||
| $Res call({ | ||||
|  String id, DateTime expiredAt, int stepRemain, int stepTotal, List<String> blacklistFactors, List<String> audiences, List<String> scopes, String ipAddress, String userAgent, String? deviceId, String? nonce, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt | ||||
|  String id, DateTime expiredAt, int stepRemain, int stepTotal, int failedAttempts, int platform, int type, List<String> blacklistFactors, List<dynamic> audiences, List<dynamic> scopes, String ipAddress, String userAgent, String deviceId, String? nonce, String? location, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt | ||||
| }); | ||||
|  | ||||
|  | ||||
| @@ -315,20 +322,25 @@ class __$SnAuthChallengeCopyWithImpl<$Res> | ||||
|  | ||||
| /// Create a copy of SnAuthChallenge | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? expiredAt = null,Object? stepRemain = null,Object? stepTotal = null,Object? blacklistFactors = null,Object? audiences = null,Object? scopes = null,Object? ipAddress = null,Object? userAgent = null,Object? deviceId = freezed,Object? nonce = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) { | ||||
| @override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? expiredAt = null,Object? stepRemain = null,Object? stepTotal = null,Object? failedAttempts = null,Object? platform = null,Object? type = null,Object? blacklistFactors = null,Object? audiences = null,Object? scopes = null,Object? ipAddress = null,Object? userAgent = null,Object? deviceId = null,Object? nonce = freezed,Object? location = freezed,Object? accountId = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) { | ||||
|   return _then(_SnAuthChallenge( | ||||
| id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable | ||||
| as String,expiredAt: null == expiredAt ? _self.expiredAt : expiredAt // ignore: cast_nullable_to_non_nullable | ||||
| as DateTime,stepRemain: null == stepRemain ? _self.stepRemain : stepRemain // ignore: cast_nullable_to_non_nullable | ||||
| as int,stepTotal: null == stepTotal ? _self.stepTotal : stepTotal // ignore: cast_nullable_to_non_nullable | ||||
| as int,failedAttempts: null == failedAttempts ? _self.failedAttempts : failedAttempts // ignore: cast_nullable_to_non_nullable | ||||
| as int,platform: null == platform ? _self.platform : platform // ignore: cast_nullable_to_non_nullable | ||||
| as int,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable | ||||
| as int,blacklistFactors: null == blacklistFactors ? _self._blacklistFactors : blacklistFactors // ignore: cast_nullable_to_non_nullable | ||||
| as List<String>,audiences: null == audiences ? _self._audiences : audiences // ignore: cast_nullable_to_non_nullable | ||||
| as List<String>,scopes: null == scopes ? _self._scopes : scopes // ignore: cast_nullable_to_non_nullable | ||||
| as List<String>,ipAddress: null == ipAddress ? _self.ipAddress : ipAddress // ignore: cast_nullable_to_non_nullable | ||||
| as List<dynamic>,scopes: null == scopes ? _self._scopes : scopes // ignore: cast_nullable_to_non_nullable | ||||
| as List<dynamic>,ipAddress: null == ipAddress ? _self.ipAddress : ipAddress // ignore: cast_nullable_to_non_nullable | ||||
| as String,userAgent: null == userAgent ? _self.userAgent : userAgent // ignore: cast_nullable_to_non_nullable | ||||
| as String,deviceId: freezed == deviceId ? _self.deviceId : deviceId // ignore: cast_nullable_to_non_nullable | ||||
| as String?,nonce: freezed == nonce ? _self.nonce : nonce // ignore: cast_nullable_to_non_nullable | ||||
| as String?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable | ||||
| as String,deviceId: null == deviceId ? _self.deviceId : deviceId // ignore: cast_nullable_to_non_nullable | ||||
| as String,nonce: freezed == nonce ? _self.nonce : nonce // ignore: cast_nullable_to_non_nullable | ||||
| as String?,location: freezed == location ? _self.location : location // ignore: cast_nullable_to_non_nullable | ||||
| as String?,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable | ||||
| as String,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?, | ||||
| @@ -339,10 +351,188 @@ as DateTime?, | ||||
| } | ||||
|  | ||||
|  | ||||
| /// @nodoc | ||||
| mixin _$SnAuthSession { | ||||
|  | ||||
|  String get id; String? get label; DateTime get lastGrantedAt; DateTime get expiredAt; String get accountId; String get challengeId; SnAuthChallenge get challenge; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; | ||||
| /// Create a copy of SnAuthSession | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @JsonKey(includeFromJson: false, includeToJson: false) | ||||
| @pragma('vm:prefer-inline') | ||||
| $SnAuthSessionCopyWith<SnAuthSession> get copyWith => _$SnAuthSessionCopyWithImpl<SnAuthSession>(this as SnAuthSession, _$identity); | ||||
|  | ||||
|   /// Serializes this SnAuthSession to a JSON map. | ||||
|   Map<String, dynamic> toJson(); | ||||
|  | ||||
|  | ||||
| @override | ||||
| bool operator ==(Object other) { | ||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is SnAuthSession&&(identical(other.id, id) || other.id == id)&&(identical(other.label, label) || other.label == label)&&(identical(other.lastGrantedAt, lastGrantedAt) || other.lastGrantedAt == lastGrantedAt)&&(identical(other.expiredAt, expiredAt) || other.expiredAt == expiredAt)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.challengeId, challengeId) || other.challengeId == challengeId)&&(identical(other.challenge, challenge) || other.challenge == challenge)&&(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,label,lastGrantedAt,expiredAt,accountId,challengeId,challenge,createdAt,updatedAt,deletedAt); | ||||
|  | ||||
| @override | ||||
| String toString() { | ||||
|   return 'SnAuthSession(id: $id, label: $label, lastGrantedAt: $lastGrantedAt, expiredAt: $expiredAt, accountId: $accountId, challengeId: $challengeId, challenge: $challenge, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; | ||||
| } | ||||
|  | ||||
|  | ||||
| } | ||||
|  | ||||
| /// @nodoc | ||||
| abstract mixin class $SnAuthSessionCopyWith<$Res>  { | ||||
|   factory $SnAuthSessionCopyWith(SnAuthSession value, $Res Function(SnAuthSession) _then) = _$SnAuthSessionCopyWithImpl; | ||||
| @useResult | ||||
| $Res call({ | ||||
|  String id, String? label, DateTime lastGrantedAt, DateTime expiredAt, String accountId, String challengeId, SnAuthChallenge challenge, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt | ||||
| }); | ||||
|  | ||||
|  | ||||
| $SnAuthChallengeCopyWith<$Res> get challenge; | ||||
|  | ||||
| } | ||||
| /// @nodoc | ||||
| class _$SnAuthSessionCopyWithImpl<$Res> | ||||
|     implements $SnAuthSessionCopyWith<$Res> { | ||||
|   _$SnAuthSessionCopyWithImpl(this._self, this._then); | ||||
|  | ||||
|   final SnAuthSession _self; | ||||
|   final $Res Function(SnAuthSession) _then; | ||||
|  | ||||
| /// Create a copy of SnAuthSession | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? label = freezed,Object? lastGrantedAt = null,Object? expiredAt = null,Object? accountId = null,Object? challengeId = null,Object? challenge = 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,label: freezed == label ? _self.label : label // ignore: cast_nullable_to_non_nullable | ||||
| as String?,lastGrantedAt: null == lastGrantedAt ? _self.lastGrantedAt : lastGrantedAt // ignore: cast_nullable_to_non_nullable | ||||
| as DateTime,expiredAt: null == expiredAt ? _self.expiredAt : expiredAt // ignore: cast_nullable_to_non_nullable | ||||
| as DateTime,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable | ||||
| as String,challengeId: null == challengeId ? _self.challengeId : challengeId // ignore: cast_nullable_to_non_nullable | ||||
| as String,challenge: null == challenge ? _self.challenge : challenge // ignore: cast_nullable_to_non_nullable | ||||
| as SnAuthChallenge,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?, | ||||
|   )); | ||||
| } | ||||
| /// Create a copy of SnAuthSession | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @override | ||||
| @pragma('vm:prefer-inline') | ||||
| $SnAuthChallengeCopyWith<$Res> get challenge { | ||||
|    | ||||
|   return $SnAuthChallengeCopyWith<$Res>(_self.challenge, (value) { | ||||
|     return _then(_self.copyWith(challenge: value)); | ||||
|   }); | ||||
| } | ||||
| } | ||||
|  | ||||
|  | ||||
| /// @nodoc | ||||
| @JsonSerializable() | ||||
|  | ||||
| class _SnAuthSession implements SnAuthSession { | ||||
|   const _SnAuthSession({required this.id, required this.label, required this.lastGrantedAt, required this.expiredAt, required this.accountId, required this.challengeId, required this.challenge, required this.createdAt, required this.updatedAt, required this.deletedAt}); | ||||
|   factory _SnAuthSession.fromJson(Map<String, dynamic> json) => _$SnAuthSessionFromJson(json); | ||||
|  | ||||
| @override final  String id; | ||||
| @override final  String? label; | ||||
| @override final  DateTime lastGrantedAt; | ||||
| @override final  DateTime expiredAt; | ||||
| @override final  String accountId; | ||||
| @override final  String challengeId; | ||||
| @override final  SnAuthChallenge challenge; | ||||
| @override final  DateTime createdAt; | ||||
| @override final  DateTime updatedAt; | ||||
| @override final  DateTime? deletedAt; | ||||
|  | ||||
| /// Create a copy of SnAuthSession | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @override @JsonKey(includeFromJson: false, includeToJson: false) | ||||
| @pragma('vm:prefer-inline') | ||||
| _$SnAuthSessionCopyWith<_SnAuthSession> get copyWith => __$SnAuthSessionCopyWithImpl<_SnAuthSession>(this, _$identity); | ||||
|  | ||||
| @override | ||||
| Map<String, dynamic> toJson() { | ||||
|   return _$SnAuthSessionToJson(this, ); | ||||
| } | ||||
|  | ||||
| @override | ||||
| bool operator ==(Object other) { | ||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnAuthSession&&(identical(other.id, id) || other.id == id)&&(identical(other.label, label) || other.label == label)&&(identical(other.lastGrantedAt, lastGrantedAt) || other.lastGrantedAt == lastGrantedAt)&&(identical(other.expiredAt, expiredAt) || other.expiredAt == expiredAt)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.challengeId, challengeId) || other.challengeId == challengeId)&&(identical(other.challenge, challenge) || other.challenge == challenge)&&(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,label,lastGrantedAt,expiredAt,accountId,challengeId,challenge,createdAt,updatedAt,deletedAt); | ||||
|  | ||||
| @override | ||||
| String toString() { | ||||
|   return 'SnAuthSession(id: $id, label: $label, lastGrantedAt: $lastGrantedAt, expiredAt: $expiredAt, accountId: $accountId, challengeId: $challengeId, challenge: $challenge, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; | ||||
| } | ||||
|  | ||||
|  | ||||
| } | ||||
|  | ||||
| /// @nodoc | ||||
| abstract mixin class _$SnAuthSessionCopyWith<$Res> implements $SnAuthSessionCopyWith<$Res> { | ||||
|   factory _$SnAuthSessionCopyWith(_SnAuthSession value, $Res Function(_SnAuthSession) _then) = __$SnAuthSessionCopyWithImpl; | ||||
| @override @useResult | ||||
| $Res call({ | ||||
|  String id, String? label, DateTime lastGrantedAt, DateTime expiredAt, String accountId, String challengeId, SnAuthChallenge challenge, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt | ||||
| }); | ||||
|  | ||||
|  | ||||
| @override $SnAuthChallengeCopyWith<$Res> get challenge; | ||||
|  | ||||
| } | ||||
| /// @nodoc | ||||
| class __$SnAuthSessionCopyWithImpl<$Res> | ||||
|     implements _$SnAuthSessionCopyWith<$Res> { | ||||
|   __$SnAuthSessionCopyWithImpl(this._self, this._then); | ||||
|  | ||||
|   final _SnAuthSession _self; | ||||
|   final $Res Function(_SnAuthSession) _then; | ||||
|  | ||||
| /// Create a copy of SnAuthSession | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? label = freezed,Object? lastGrantedAt = null,Object? expiredAt = null,Object? accountId = null,Object? challengeId = null,Object? challenge = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) { | ||||
|   return _then(_SnAuthSession( | ||||
| id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable | ||||
| as String,label: freezed == label ? _self.label : label // ignore: cast_nullable_to_non_nullable | ||||
| as String?,lastGrantedAt: null == lastGrantedAt ? _self.lastGrantedAt : lastGrantedAt // ignore: cast_nullable_to_non_nullable | ||||
| as DateTime,expiredAt: null == expiredAt ? _self.expiredAt : expiredAt // ignore: cast_nullable_to_non_nullable | ||||
| as DateTime,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable | ||||
| as String,challengeId: null == challengeId ? _self.challengeId : challengeId // ignore: cast_nullable_to_non_nullable | ||||
| as String,challenge: null == challenge ? _self.challenge : challenge // ignore: cast_nullable_to_non_nullable | ||||
| as SnAuthChallenge,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?, | ||||
|   )); | ||||
| } | ||||
|  | ||||
| /// Create a copy of SnAuthSession | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @override | ||||
| @pragma('vm:prefer-inline') | ||||
| $SnAuthChallengeCopyWith<$Res> get challenge { | ||||
|    | ||||
|   return $SnAuthChallengeCopyWith<$Res>(_self.challenge, (value) { | ||||
|     return _then(_self.copyWith(challenge: value)); | ||||
|   }); | ||||
| } | ||||
| } | ||||
|  | ||||
|  | ||||
| /// @nodoc | ||||
| mixin _$SnAuthFactor { | ||||
|  | ||||
|  String get id; int get type; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; | ||||
|  String get id; int get type; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; DateTime? get expiredAt; DateTime? get enabledAt; int get trustworthy; Map<String, dynamic>? get createdResponse; | ||||
| /// Create a copy of SnAuthFactor | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @JsonKey(includeFromJson: false, includeToJson: false) | ||||
| @@ -355,16 +545,16 @@ $SnAuthFactorCopyWith<SnAuthFactor> get copyWith => _$SnAuthFactorCopyWithImpl<S | ||||
|  | ||||
| @override | ||||
| bool operator ==(Object other) { | ||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is SnAuthFactor&&(identical(other.id, id) || other.id == id)&&(identical(other.type, type) || other.type == type)&&(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 SnAuthFactor&&(identical(other.id, id) || other.id == id)&&(identical(other.type, type) || other.type == type)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.expiredAt, expiredAt) || other.expiredAt == expiredAt)&&(identical(other.enabledAt, enabledAt) || other.enabledAt == enabledAt)&&(identical(other.trustworthy, trustworthy) || other.trustworthy == trustworthy)&&const DeepCollectionEquality().equals(other.createdResponse, createdResponse)); | ||||
| } | ||||
|  | ||||
| @JsonKey(includeFromJson: false, includeToJson: false) | ||||
| @override | ||||
| int get hashCode => Object.hash(runtimeType,id,type,createdAt,updatedAt,deletedAt); | ||||
| int get hashCode => Object.hash(runtimeType,id,type,createdAt,updatedAt,deletedAt,expiredAt,enabledAt,trustworthy,const DeepCollectionEquality().hash(createdResponse)); | ||||
|  | ||||
| @override | ||||
| String toString() { | ||||
|   return 'SnAuthFactor(id: $id, type: $type, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; | ||||
|   return 'SnAuthFactor(id: $id, type: $type, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, expiredAt: $expiredAt, enabledAt: $enabledAt, trustworthy: $trustworthy, createdResponse: $createdResponse)'; | ||||
| } | ||||
|  | ||||
|  | ||||
| @@ -375,7 +565,7 @@ abstract mixin class $SnAuthFactorCopyWith<$Res>  { | ||||
|   factory $SnAuthFactorCopyWith(SnAuthFactor value, $Res Function(SnAuthFactor) _then) = _$SnAuthFactorCopyWithImpl; | ||||
| @useResult | ||||
| $Res call({ | ||||
|  String id, int type, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt | ||||
|  String id, int type, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, DateTime? expiredAt, DateTime? enabledAt, int trustworthy, Map<String, dynamic>? createdResponse | ||||
| }); | ||||
|  | ||||
|  | ||||
| @@ -392,14 +582,18 @@ class _$SnAuthFactorCopyWithImpl<$Res> | ||||
|  | ||||
| /// Create a copy of SnAuthFactor | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? type = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) { | ||||
| @pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? type = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? expiredAt = freezed,Object? enabledAt = freezed,Object? trustworthy = null,Object? createdResponse = freezed,}) { | ||||
|   return _then(_self.copyWith( | ||||
| id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable | ||||
| as String,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable | ||||
| as int,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?, | ||||
| as DateTime?,expiredAt: freezed == expiredAt ? _self.expiredAt : expiredAt // ignore: cast_nullable_to_non_nullable | ||||
| as DateTime?,enabledAt: freezed == enabledAt ? _self.enabledAt : enabledAt // ignore: cast_nullable_to_non_nullable | ||||
| as DateTime?,trustworthy: null == trustworthy ? _self.trustworthy : trustworthy // ignore: cast_nullable_to_non_nullable | ||||
| as int,createdResponse: freezed == createdResponse ? _self.createdResponse : createdResponse // ignore: cast_nullable_to_non_nullable | ||||
| as Map<String, dynamic>?, | ||||
|   )); | ||||
| } | ||||
|  | ||||
| @@ -410,7 +604,7 @@ as DateTime?, | ||||
| @JsonSerializable() | ||||
|  | ||||
| class _SnAuthFactor implements SnAuthFactor { | ||||
|   const _SnAuthFactor({required this.id, required this.type, required this.createdAt, required this.updatedAt, required this.deletedAt}); | ||||
|   const _SnAuthFactor({required this.id, required this.type, required this.createdAt, required this.updatedAt, required this.deletedAt, required this.expiredAt, required this.enabledAt, required this.trustworthy, required final  Map<String, dynamic>? createdResponse}): _createdResponse = createdResponse; | ||||
|   factory _SnAuthFactor.fromJson(Map<String, dynamic> json) => _$SnAuthFactorFromJson(json); | ||||
|  | ||||
| @override final  String id; | ||||
| @@ -418,6 +612,18 @@ class _SnAuthFactor implements SnAuthFactor { | ||||
| @override final  DateTime createdAt; | ||||
| @override final  DateTime updatedAt; | ||||
| @override final  DateTime? deletedAt; | ||||
| @override final  DateTime? expiredAt; | ||||
| @override final  DateTime? enabledAt; | ||||
| @override final  int trustworthy; | ||||
|  final  Map<String, dynamic>? _createdResponse; | ||||
| @override Map<String, dynamic>? get createdResponse { | ||||
|   final value = _createdResponse; | ||||
|   if (value == null) return null; | ||||
|   if (_createdResponse is EqualUnmodifiableMapView) return _createdResponse; | ||||
|   // ignore: implicit_dynamic_type | ||||
|   return EqualUnmodifiableMapView(value); | ||||
| } | ||||
|  | ||||
|  | ||||
| /// Create a copy of SnAuthFactor | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @@ -432,16 +638,16 @@ Map<String, dynamic> toJson() { | ||||
|  | ||||
| @override | ||||
| bool operator ==(Object other) { | ||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnAuthFactor&&(identical(other.id, id) || other.id == id)&&(identical(other.type, type) || other.type == type)&&(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 _SnAuthFactor&&(identical(other.id, id) || other.id == id)&&(identical(other.type, type) || other.type == type)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.expiredAt, expiredAt) || other.expiredAt == expiredAt)&&(identical(other.enabledAt, enabledAt) || other.enabledAt == enabledAt)&&(identical(other.trustworthy, trustworthy) || other.trustworthy == trustworthy)&&const DeepCollectionEquality().equals(other._createdResponse, _createdResponse)); | ||||
| } | ||||
|  | ||||
| @JsonKey(includeFromJson: false, includeToJson: false) | ||||
| @override | ||||
| int get hashCode => Object.hash(runtimeType,id,type,createdAt,updatedAt,deletedAt); | ||||
| int get hashCode => Object.hash(runtimeType,id,type,createdAt,updatedAt,deletedAt,expiredAt,enabledAt,trustworthy,const DeepCollectionEquality().hash(_createdResponse)); | ||||
|  | ||||
| @override | ||||
| String toString() { | ||||
|   return 'SnAuthFactor(id: $id, type: $type, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; | ||||
|   return 'SnAuthFactor(id: $id, type: $type, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, expiredAt: $expiredAt, enabledAt: $enabledAt, trustworthy: $trustworthy, createdResponse: $createdResponse)'; | ||||
| } | ||||
|  | ||||
|  | ||||
| @@ -452,7 +658,7 @@ abstract mixin class _$SnAuthFactorCopyWith<$Res> implements $SnAuthFactorCopyWi | ||||
|   factory _$SnAuthFactorCopyWith(_SnAuthFactor value, $Res Function(_SnAuthFactor) _then) = __$SnAuthFactorCopyWithImpl; | ||||
| @override @useResult | ||||
| $Res call({ | ||||
|  String id, int type, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt | ||||
|  String id, int type, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, DateTime? expiredAt, DateTime? enabledAt, int trustworthy, Map<String, dynamic>? createdResponse | ||||
| }); | ||||
|  | ||||
|  | ||||
| @@ -469,13 +675,336 @@ class __$SnAuthFactorCopyWithImpl<$Res> | ||||
|  | ||||
| /// Create a copy of SnAuthFactor | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? type = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) { | ||||
| @override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? type = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? expiredAt = freezed,Object? enabledAt = freezed,Object? trustworthy = null,Object? createdResponse = freezed,}) { | ||||
|   return _then(_SnAuthFactor( | ||||
| id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable | ||||
| as String,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable | ||||
| as int,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?,expiredAt: freezed == expiredAt ? _self.expiredAt : expiredAt // ignore: cast_nullable_to_non_nullable | ||||
| as DateTime?,enabledAt: freezed == enabledAt ? _self.enabledAt : enabledAt // ignore: cast_nullable_to_non_nullable | ||||
| as DateTime?,trustworthy: null == trustworthy ? _self.trustworthy : trustworthy // ignore: cast_nullable_to_non_nullable | ||||
| as int,createdResponse: freezed == createdResponse ? _self._createdResponse : createdResponse // ignore: cast_nullable_to_non_nullable | ||||
| as Map<String, dynamic>?, | ||||
|   )); | ||||
| } | ||||
|  | ||||
|  | ||||
| } | ||||
|  | ||||
|  | ||||
| /// @nodoc | ||||
| mixin _$SnAuthDevice { | ||||
|  | ||||
|  dynamic get label; String get userAgent; String get deviceId; int get platform; List<SnAuthSession> get sessions;// Not from backend, used for UI | ||||
|  bool get isCurrent; | ||||
| /// Create a copy of SnAuthDevice | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @JsonKey(includeFromJson: false, includeToJson: false) | ||||
| @pragma('vm:prefer-inline') | ||||
| $SnAuthDeviceCopyWith<SnAuthDevice> get copyWith => _$SnAuthDeviceCopyWithImpl<SnAuthDevice>(this as SnAuthDevice, _$identity); | ||||
|  | ||||
|   /// Serializes this SnAuthDevice to a JSON map. | ||||
|   Map<String, dynamic> toJson(); | ||||
|  | ||||
|  | ||||
| @override | ||||
| bool operator ==(Object other) { | ||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is SnAuthDevice&&const DeepCollectionEquality().equals(other.label, label)&&(identical(other.userAgent, userAgent) || other.userAgent == userAgent)&&(identical(other.deviceId, deviceId) || other.deviceId == deviceId)&&(identical(other.platform, platform) || other.platform == platform)&&const DeepCollectionEquality().equals(other.sessions, sessions)&&(identical(other.isCurrent, isCurrent) || other.isCurrent == isCurrent)); | ||||
| } | ||||
|  | ||||
| @JsonKey(includeFromJson: false, includeToJson: false) | ||||
| @override | ||||
| int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(label),userAgent,deviceId,platform,const DeepCollectionEquality().hash(sessions),isCurrent); | ||||
|  | ||||
| @override | ||||
| String toString() { | ||||
|   return 'SnAuthDevice(label: $label, userAgent: $userAgent, deviceId: $deviceId, platform: $platform, sessions: $sessions, isCurrent: $isCurrent)'; | ||||
| } | ||||
|  | ||||
|  | ||||
| } | ||||
|  | ||||
| /// @nodoc | ||||
| abstract mixin class $SnAuthDeviceCopyWith<$Res>  { | ||||
|   factory $SnAuthDeviceCopyWith(SnAuthDevice value, $Res Function(SnAuthDevice) _then) = _$SnAuthDeviceCopyWithImpl; | ||||
| @useResult | ||||
| $Res call({ | ||||
|  dynamic label, String userAgent, String deviceId, int platform, List<SnAuthSession> sessions, bool isCurrent | ||||
| }); | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| } | ||||
| /// @nodoc | ||||
| class _$SnAuthDeviceCopyWithImpl<$Res> | ||||
|     implements $SnAuthDeviceCopyWith<$Res> { | ||||
|   _$SnAuthDeviceCopyWithImpl(this._self, this._then); | ||||
|  | ||||
|   final SnAuthDevice _self; | ||||
|   final $Res Function(SnAuthDevice) _then; | ||||
|  | ||||
| /// Create a copy of SnAuthDevice | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @pragma('vm:prefer-inline') @override $Res call({Object? label = freezed,Object? userAgent = null,Object? deviceId = null,Object? platform = null,Object? sessions = null,Object? isCurrent = null,}) { | ||||
|   return _then(_self.copyWith( | ||||
| label: freezed == label ? _self.label : label // ignore: cast_nullable_to_non_nullable | ||||
| as dynamic,userAgent: null == userAgent ? _self.userAgent : userAgent // ignore: cast_nullable_to_non_nullable | ||||
| as String,deviceId: null == deviceId ? _self.deviceId : deviceId // ignore: cast_nullable_to_non_nullable | ||||
| as String,platform: null == platform ? _self.platform : platform // ignore: cast_nullable_to_non_nullable | ||||
| as int,sessions: null == sessions ? _self.sessions : sessions // ignore: cast_nullable_to_non_nullable | ||||
| as List<SnAuthSession>,isCurrent: null == isCurrent ? _self.isCurrent : isCurrent // ignore: cast_nullable_to_non_nullable | ||||
| as bool, | ||||
|   )); | ||||
| } | ||||
|  | ||||
| } | ||||
|  | ||||
|  | ||||
| /// @nodoc | ||||
| @JsonSerializable() | ||||
|  | ||||
| class _SnAuthDevice implements SnAuthDevice { | ||||
|   const _SnAuthDevice({required this.label, required this.userAgent, required this.deviceId, required this.platform, required final  List<SnAuthSession> sessions, this.isCurrent = false}): _sessions = sessions; | ||||
|   factory _SnAuthDevice.fromJson(Map<String, dynamic> json) => _$SnAuthDeviceFromJson(json); | ||||
|  | ||||
| @override final  dynamic label; | ||||
| @override final  String userAgent; | ||||
| @override final  String deviceId; | ||||
| @override final  int platform; | ||||
|  final  List<SnAuthSession> _sessions; | ||||
| @override List<SnAuthSession> get sessions { | ||||
|   if (_sessions is EqualUnmodifiableListView) return _sessions; | ||||
|   // ignore: implicit_dynamic_type | ||||
|   return EqualUnmodifiableListView(_sessions); | ||||
| } | ||||
|  | ||||
| // Not from backend, used for UI | ||||
| @override@JsonKey() final  bool isCurrent; | ||||
|  | ||||
| /// Create a copy of SnAuthDevice | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @override @JsonKey(includeFromJson: false, includeToJson: false) | ||||
| @pragma('vm:prefer-inline') | ||||
| _$SnAuthDeviceCopyWith<_SnAuthDevice> get copyWith => __$SnAuthDeviceCopyWithImpl<_SnAuthDevice>(this, _$identity); | ||||
|  | ||||
| @override | ||||
| Map<String, dynamic> toJson() { | ||||
|   return _$SnAuthDeviceToJson(this, ); | ||||
| } | ||||
|  | ||||
| @override | ||||
| bool operator ==(Object other) { | ||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnAuthDevice&&const DeepCollectionEquality().equals(other.label, label)&&(identical(other.userAgent, userAgent) || other.userAgent == userAgent)&&(identical(other.deviceId, deviceId) || other.deviceId == deviceId)&&(identical(other.platform, platform) || other.platform == platform)&&const DeepCollectionEquality().equals(other._sessions, _sessions)&&(identical(other.isCurrent, isCurrent) || other.isCurrent == isCurrent)); | ||||
| } | ||||
|  | ||||
| @JsonKey(includeFromJson: false, includeToJson: false) | ||||
| @override | ||||
| int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(label),userAgent,deviceId,platform,const DeepCollectionEquality().hash(_sessions),isCurrent); | ||||
|  | ||||
| @override | ||||
| String toString() { | ||||
|   return 'SnAuthDevice(label: $label, userAgent: $userAgent, deviceId: $deviceId, platform: $platform, sessions: $sessions, isCurrent: $isCurrent)'; | ||||
| } | ||||
|  | ||||
|  | ||||
| } | ||||
|  | ||||
| /// @nodoc | ||||
| abstract mixin class _$SnAuthDeviceCopyWith<$Res> implements $SnAuthDeviceCopyWith<$Res> { | ||||
|   factory _$SnAuthDeviceCopyWith(_SnAuthDevice value, $Res Function(_SnAuthDevice) _then) = __$SnAuthDeviceCopyWithImpl; | ||||
| @override @useResult | ||||
| $Res call({ | ||||
|  dynamic label, String userAgent, String deviceId, int platform, List<SnAuthSession> sessions, bool isCurrent | ||||
| }); | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| } | ||||
| /// @nodoc | ||||
| class __$SnAuthDeviceCopyWithImpl<$Res> | ||||
|     implements _$SnAuthDeviceCopyWith<$Res> { | ||||
|   __$SnAuthDeviceCopyWithImpl(this._self, this._then); | ||||
|  | ||||
|   final _SnAuthDevice _self; | ||||
|   final $Res Function(_SnAuthDevice) _then; | ||||
|  | ||||
| /// Create a copy of SnAuthDevice | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @override @pragma('vm:prefer-inline') $Res call({Object? label = freezed,Object? userAgent = null,Object? deviceId = null,Object? platform = null,Object? sessions = null,Object? isCurrent = null,}) { | ||||
|   return _then(_SnAuthDevice( | ||||
| label: freezed == label ? _self.label : label // ignore: cast_nullable_to_non_nullable | ||||
| as dynamic,userAgent: null == userAgent ? _self.userAgent : userAgent // ignore: cast_nullable_to_non_nullable | ||||
| as String,deviceId: null == deviceId ? _self.deviceId : deviceId // ignore: cast_nullable_to_non_nullable | ||||
| as String,platform: null == platform ? _self.platform : platform // ignore: cast_nullable_to_non_nullable | ||||
| as int,sessions: null == sessions ? _self._sessions : sessions // ignore: cast_nullable_to_non_nullable | ||||
| as List<SnAuthSession>,isCurrent: null == isCurrent ? _self.isCurrent : isCurrent // ignore: cast_nullable_to_non_nullable | ||||
| as bool, | ||||
|   )); | ||||
| } | ||||
|  | ||||
|  | ||||
| } | ||||
|  | ||||
|  | ||||
| /// @nodoc | ||||
| mixin _$SnAccountConnection { | ||||
|  | ||||
|  String get id; String get accountId; String get provider; String get providedIdentifier; Map<String, dynamic> get meta; DateTime get lastUsedAt; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; | ||||
| /// Create a copy of SnAccountConnection | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @JsonKey(includeFromJson: false, includeToJson: false) | ||||
| @pragma('vm:prefer-inline') | ||||
| $SnAccountConnectionCopyWith<SnAccountConnection> get copyWith => _$SnAccountConnectionCopyWithImpl<SnAccountConnection>(this as SnAccountConnection, _$identity); | ||||
|  | ||||
|   /// Serializes this SnAccountConnection to a JSON map. | ||||
|   Map<String, dynamic> toJson(); | ||||
|  | ||||
|  | ||||
| @override | ||||
| bool operator ==(Object other) { | ||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is SnAccountConnection&&(identical(other.id, id) || other.id == id)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.provider, provider) || other.provider == provider)&&(identical(other.providedIdentifier, providedIdentifier) || other.providedIdentifier == providedIdentifier)&&const DeepCollectionEquality().equals(other.meta, meta)&&(identical(other.lastUsedAt, lastUsedAt) || other.lastUsedAt == lastUsedAt)&&(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,accountId,provider,providedIdentifier,const DeepCollectionEquality().hash(meta),lastUsedAt,createdAt,updatedAt,deletedAt); | ||||
|  | ||||
| @override | ||||
| String toString() { | ||||
|   return 'SnAccountConnection(id: $id, accountId: $accountId, provider: $provider, providedIdentifier: $providedIdentifier, meta: $meta, lastUsedAt: $lastUsedAt, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; | ||||
| } | ||||
|  | ||||
|  | ||||
| } | ||||
|  | ||||
| /// @nodoc | ||||
| abstract mixin class $SnAccountConnectionCopyWith<$Res>  { | ||||
|   factory $SnAccountConnectionCopyWith(SnAccountConnection value, $Res Function(SnAccountConnection) _then) = _$SnAccountConnectionCopyWithImpl; | ||||
| @useResult | ||||
| $Res call({ | ||||
|  String id, String accountId, String provider, String providedIdentifier, Map<String, dynamic> meta, DateTime lastUsedAt, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt | ||||
| }); | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| } | ||||
| /// @nodoc | ||||
| class _$SnAccountConnectionCopyWithImpl<$Res> | ||||
|     implements $SnAccountConnectionCopyWith<$Res> { | ||||
|   _$SnAccountConnectionCopyWithImpl(this._self, this._then); | ||||
|  | ||||
|   final SnAccountConnection _self; | ||||
|   final $Res Function(SnAccountConnection) _then; | ||||
|  | ||||
| /// Create a copy of SnAccountConnection | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? accountId = null,Object? provider = null,Object? providedIdentifier = null,Object? meta = null,Object? lastUsedAt = 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,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable | ||||
| as String,provider: null == provider ? _self.provider : provider // ignore: cast_nullable_to_non_nullable | ||||
| as String,providedIdentifier: null == providedIdentifier ? _self.providedIdentifier : providedIdentifier // ignore: cast_nullable_to_non_nullable | ||||
| as String,meta: null == meta ? _self.meta : meta // ignore: cast_nullable_to_non_nullable | ||||
| as Map<String, dynamic>,lastUsedAt: null == lastUsedAt ? _self.lastUsedAt : lastUsedAt // ignore: cast_nullable_to_non_nullable | ||||
| as DateTime,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable | ||||
| as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable | ||||
| as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable | ||||
| as DateTime?, | ||||
|   )); | ||||
| } | ||||
|  | ||||
| } | ||||
|  | ||||
|  | ||||
| /// @nodoc | ||||
| @JsonSerializable() | ||||
|  | ||||
| class _SnAccountConnection implements SnAccountConnection { | ||||
|   const _SnAccountConnection({required this.id, required this.accountId, required this.provider, required this.providedIdentifier, final  Map<String, dynamic> meta = const {}, required this.lastUsedAt, required this.createdAt, required this.updatedAt, required this.deletedAt}): _meta = meta; | ||||
|   factory _SnAccountConnection.fromJson(Map<String, dynamic> json) => _$SnAccountConnectionFromJson(json); | ||||
|  | ||||
| @override final  String id; | ||||
| @override final  String accountId; | ||||
| @override final  String provider; | ||||
| @override final  String providedIdentifier; | ||||
|  final  Map<String, dynamic> _meta; | ||||
| @override@JsonKey() Map<String, dynamic> get meta { | ||||
|   if (_meta is EqualUnmodifiableMapView) return _meta; | ||||
|   // ignore: implicit_dynamic_type | ||||
|   return EqualUnmodifiableMapView(_meta); | ||||
| } | ||||
|  | ||||
| @override final  DateTime lastUsedAt; | ||||
| @override final  DateTime createdAt; | ||||
| @override final  DateTime updatedAt; | ||||
| @override final  DateTime? deletedAt; | ||||
|  | ||||
| /// Create a copy of SnAccountConnection | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @override @JsonKey(includeFromJson: false, includeToJson: false) | ||||
| @pragma('vm:prefer-inline') | ||||
| _$SnAccountConnectionCopyWith<_SnAccountConnection> get copyWith => __$SnAccountConnectionCopyWithImpl<_SnAccountConnection>(this, _$identity); | ||||
|  | ||||
| @override | ||||
| Map<String, dynamic> toJson() { | ||||
|   return _$SnAccountConnectionToJson(this, ); | ||||
| } | ||||
|  | ||||
| @override | ||||
| bool operator ==(Object other) { | ||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnAccountConnection&&(identical(other.id, id) || other.id == id)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.provider, provider) || other.provider == provider)&&(identical(other.providedIdentifier, providedIdentifier) || other.providedIdentifier == providedIdentifier)&&const DeepCollectionEquality().equals(other._meta, _meta)&&(identical(other.lastUsedAt, lastUsedAt) || other.lastUsedAt == lastUsedAt)&&(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,accountId,provider,providedIdentifier,const DeepCollectionEquality().hash(_meta),lastUsedAt,createdAt,updatedAt,deletedAt); | ||||
|  | ||||
| @override | ||||
| String toString() { | ||||
|   return 'SnAccountConnection(id: $id, accountId: $accountId, provider: $provider, providedIdentifier: $providedIdentifier, meta: $meta, lastUsedAt: $lastUsedAt, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; | ||||
| } | ||||
|  | ||||
|  | ||||
| } | ||||
|  | ||||
| /// @nodoc | ||||
| abstract mixin class _$SnAccountConnectionCopyWith<$Res> implements $SnAccountConnectionCopyWith<$Res> { | ||||
|   factory _$SnAccountConnectionCopyWith(_SnAccountConnection value, $Res Function(_SnAccountConnection) _then) = __$SnAccountConnectionCopyWithImpl; | ||||
| @override @useResult | ||||
| $Res call({ | ||||
|  String id, String accountId, String provider, String providedIdentifier, Map<String, dynamic> meta, DateTime lastUsedAt, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt | ||||
| }); | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| } | ||||
| /// @nodoc | ||||
| class __$SnAccountConnectionCopyWithImpl<$Res> | ||||
|     implements _$SnAccountConnectionCopyWith<$Res> { | ||||
|   __$SnAccountConnectionCopyWithImpl(this._self, this._then); | ||||
|  | ||||
|   final _SnAccountConnection _self; | ||||
|   final $Res Function(_SnAccountConnection) _then; | ||||
|  | ||||
| /// Create a copy of SnAccountConnection | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? accountId = null,Object? provider = null,Object? providedIdentifier = null,Object? meta = null,Object? lastUsedAt = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) { | ||||
|   return _then(_SnAccountConnection( | ||||
| id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable | ||||
| as String,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable | ||||
| as String,provider: null == provider ? _self.provider : provider // ignore: cast_nullable_to_non_nullable | ||||
| as String,providedIdentifier: null == providedIdentifier ? _self.providedIdentifier : providedIdentifier // ignore: cast_nullable_to_non_nullable | ||||
| as String,meta: null == meta ? _self._meta : meta // ignore: cast_nullable_to_non_nullable | ||||
| as Map<String, dynamic>,lastUsedAt: null == lastUsedAt ? _self.lastUsedAt : lastUsedAt // ignore: cast_nullable_to_non_nullable | ||||
| as DateTime,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable | ||||
| as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable | ||||
| as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable | ||||
| as DateTime?, | ||||
|   )); | ||||
| } | ||||
|   | ||||
| @@ -6,17 +6,12 @@ part of 'auth.dart'; | ||||
| // JsonSerializableGenerator | ||||
| // ************************************************************************** | ||||
|  | ||||
| _AppTokenPair _$AppTokenPairFromJson(Map<String, dynamic> json) => | ||||
|     _AppTokenPair( | ||||
|       accessToken: json['access_token'] as String, | ||||
|       refreshToken: json['refresh_token'] as String, | ||||
|     ); | ||||
| _AppToken _$AppTokenFromJson(Map<String, dynamic> json) => | ||||
|     _AppToken(token: json['token'] as String); | ||||
|  | ||||
| Map<String, dynamic> _$AppTokenPairToJson(_AppTokenPair instance) => | ||||
|     <String, dynamic>{ | ||||
|       'access_token': instance.accessToken, | ||||
|       'refresh_token': instance.refreshToken, | ||||
|     }; | ||||
| Map<String, dynamic> _$AppTokenToJson(_AppToken instance) => <String, dynamic>{ | ||||
|   'token': instance.token, | ||||
| }; | ||||
|  | ||||
| _SnAuthChallenge _$SnAuthChallengeFromJson(Map<String, dynamic> json) => | ||||
|     _SnAuthChallenge( | ||||
| @@ -24,18 +19,21 @@ _SnAuthChallenge _$SnAuthChallengeFromJson(Map<String, dynamic> json) => | ||||
|       expiredAt: DateTime.parse(json['expired_at'] as String), | ||||
|       stepRemain: (json['step_remain'] as num).toInt(), | ||||
|       stepTotal: (json['step_total'] as num).toInt(), | ||||
|       failedAttempts: (json['failed_attempts'] as num).toInt(), | ||||
|       platform: (json['platform'] as num).toInt(), | ||||
|       type: (json['type'] as num).toInt(), | ||||
|       blacklistFactors: | ||||
|           (json['blacklist_factors'] as List<dynamic>) | ||||
|               .map((e) => e as String) | ||||
|               .toList(), | ||||
|       audiences: | ||||
|           (json['audiences'] as List<dynamic>).map((e) => e as String).toList(), | ||||
|       scopes: | ||||
|           (json['scopes'] as List<dynamic>).map((e) => e as String).toList(), | ||||
|       audiences: json['audiences'] as List<dynamic>, | ||||
|       scopes: json['scopes'] as List<dynamic>, | ||||
|       ipAddress: json['ip_address'] as String, | ||||
|       userAgent: json['user_agent'] as String, | ||||
|       deviceId: json['device_id'] as String?, | ||||
|       deviceId: json['device_id'] as String, | ||||
|       nonce: json['nonce'] as String?, | ||||
|       location: json['location'] as String?, | ||||
|       accountId: json['account_id'] as String, | ||||
|       createdAt: DateTime.parse(json['created_at'] as String), | ||||
|       updatedAt: DateTime.parse(json['updated_at'] as String), | ||||
|       deletedAt: | ||||
| @@ -50,6 +48,9 @@ Map<String, dynamic> _$SnAuthChallengeToJson(_SnAuthChallenge instance) => | ||||
|       'expired_at': instance.expiredAt.toIso8601String(), | ||||
|       'step_remain': instance.stepRemain, | ||||
|       'step_total': instance.stepTotal, | ||||
|       'failed_attempts': instance.failedAttempts, | ||||
|       'platform': instance.platform, | ||||
|       'type': instance.type, | ||||
|       'blacklist_factors': instance.blacklistFactors, | ||||
|       'audiences': instance.audiences, | ||||
|       'scopes': instance.scopes, | ||||
| @@ -57,6 +58,41 @@ Map<String, dynamic> _$SnAuthChallengeToJson(_SnAuthChallenge instance) => | ||||
|       'user_agent': instance.userAgent, | ||||
|       'device_id': instance.deviceId, | ||||
|       'nonce': instance.nonce, | ||||
|       'location': instance.location, | ||||
|       'account_id': instance.accountId, | ||||
|       'created_at': instance.createdAt.toIso8601String(), | ||||
|       'updated_at': instance.updatedAt.toIso8601String(), | ||||
|       'deleted_at': instance.deletedAt?.toIso8601String(), | ||||
|     }; | ||||
|  | ||||
| _SnAuthSession _$SnAuthSessionFromJson(Map<String, dynamic> json) => | ||||
|     _SnAuthSession( | ||||
|       id: json['id'] as String, | ||||
|       label: json['label'] as String?, | ||||
|       lastGrantedAt: DateTime.parse(json['last_granted_at'] as String), | ||||
|       expiredAt: DateTime.parse(json['expired_at'] as String), | ||||
|       accountId: json['account_id'] as String, | ||||
|       challengeId: json['challenge_id'] as String, | ||||
|       challenge: SnAuthChallenge.fromJson( | ||||
|         json['challenge'] as Map<String, dynamic>, | ||||
|       ), | ||||
|       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> _$SnAuthSessionToJson(_SnAuthSession instance) => | ||||
|     <String, dynamic>{ | ||||
|       'id': instance.id, | ||||
|       'label': instance.label, | ||||
|       'last_granted_at': instance.lastGrantedAt.toIso8601String(), | ||||
|       'expired_at': instance.expiredAt.toIso8601String(), | ||||
|       'account_id': instance.accountId, | ||||
|       'challenge_id': instance.challengeId, | ||||
|       'challenge': instance.challenge.toJson(), | ||||
|       'created_at': instance.createdAt.toIso8601String(), | ||||
|       'updated_at': instance.updatedAt.toIso8601String(), | ||||
|       'deleted_at': instance.deletedAt?.toIso8601String(), | ||||
| @@ -72,6 +108,16 @@ _SnAuthFactor _$SnAuthFactorFromJson(Map<String, dynamic> json) => | ||||
|           json['deleted_at'] == null | ||||
|               ? null | ||||
|               : DateTime.parse(json['deleted_at'] as String), | ||||
|       expiredAt: | ||||
|           json['expired_at'] == null | ||||
|               ? null | ||||
|               : DateTime.parse(json['expired_at'] as String), | ||||
|       enabledAt: | ||||
|           json['enabled_at'] == null | ||||
|               ? null | ||||
|               : DateTime.parse(json['enabled_at'] as String), | ||||
|       trustworthy: (json['trustworthy'] as num).toInt(), | ||||
|       createdResponse: json['created_response'] as Map<String, dynamic>?, | ||||
|     ); | ||||
|  | ||||
| Map<String, dynamic> _$SnAuthFactorToJson(_SnAuthFactor instance) => | ||||
| @@ -81,4 +127,61 @@ Map<String, dynamic> _$SnAuthFactorToJson(_SnAuthFactor instance) => | ||||
|       'created_at': instance.createdAt.toIso8601String(), | ||||
|       'updated_at': instance.updatedAt.toIso8601String(), | ||||
|       'deleted_at': instance.deletedAt?.toIso8601String(), | ||||
|       'expired_at': instance.expiredAt?.toIso8601String(), | ||||
|       'enabled_at': instance.enabledAt?.toIso8601String(), | ||||
|       'trustworthy': instance.trustworthy, | ||||
|       'created_response': instance.createdResponse, | ||||
|     }; | ||||
|  | ||||
| _SnAuthDevice _$SnAuthDeviceFromJson(Map<String, dynamic> json) => | ||||
|     _SnAuthDevice( | ||||
|       label: json['label'], | ||||
|       userAgent: json['user_agent'] as String, | ||||
|       deviceId: json['device_id'] as String, | ||||
|       platform: (json['platform'] as num).toInt(), | ||||
|       sessions: | ||||
|           (json['sessions'] as List<dynamic>) | ||||
|               .map((e) => SnAuthSession.fromJson(e as Map<String, dynamic>)) | ||||
|               .toList(), | ||||
|       isCurrent: json['is_current'] as bool? ?? false, | ||||
|     ); | ||||
|  | ||||
| Map<String, dynamic> _$SnAuthDeviceToJson(_SnAuthDevice instance) => | ||||
|     <String, dynamic>{ | ||||
|       'label': instance.label, | ||||
|       'user_agent': instance.userAgent, | ||||
|       'device_id': instance.deviceId, | ||||
|       'platform': instance.platform, | ||||
|       'sessions': instance.sessions.map((e) => e.toJson()).toList(), | ||||
|       'is_current': instance.isCurrent, | ||||
|     }; | ||||
|  | ||||
| _SnAccountConnection _$SnAccountConnectionFromJson(Map<String, dynamic> json) => | ||||
|     _SnAccountConnection( | ||||
|       id: json['id'] as String, | ||||
|       accountId: json['account_id'] as String, | ||||
|       provider: json['provider'] as String, | ||||
|       providedIdentifier: json['provided_identifier'] as String, | ||||
|       meta: json['meta'] as Map<String, dynamic>? ?? const {}, | ||||
|       lastUsedAt: DateTime.parse(json['last_used_at'] as String), | ||||
|       createdAt: DateTime.parse(json['created_at'] as String), | ||||
|       updatedAt: DateTime.parse(json['updated_at'] as String), | ||||
|       deletedAt: | ||||
|           json['deleted_at'] == null | ||||
|               ? null | ||||
|               : DateTime.parse(json['deleted_at'] as String), | ||||
|     ); | ||||
|  | ||||
| Map<String, dynamic> _$SnAccountConnectionToJson( | ||||
|   _SnAccountConnection instance, | ||||
| ) => <String, dynamic>{ | ||||
|   'id': instance.id, | ||||
|   'account_id': instance.accountId, | ||||
|   'provider': instance.provider, | ||||
|   'provided_identifier': instance.providedIdentifier, | ||||
|   'meta': instance.meta, | ||||
|   'last_used_at': instance.lastUsedAt.toIso8601String(), | ||||
|   'created_at': instance.createdAt.toIso8601String(), | ||||
|   'updated_at': instance.updatedAt.toIso8601String(), | ||||
|   'deleted_at': instance.deletedAt?.toIso8601String(), | ||||
| }; | ||||
|   | ||||
| @@ -7,16 +7,14 @@ part 'chat.freezed.dart'; | ||||
| part 'chat.g.dart'; | ||||
|  | ||||
| @freezed | ||||
| abstract class SnChatRoom with _$SnChatRoom { | ||||
| sealed class SnChatRoom with _$SnChatRoom { | ||||
|   const factory SnChatRoom({ | ||||
|     required String id, | ||||
|     required String? name, | ||||
|     required String? description, | ||||
|     required int type, | ||||
|     required bool isPublic, | ||||
|     required String? pictureId, | ||||
|     required SnCloudFile? picture, | ||||
|     required String? backgroundId, | ||||
|     required SnCloudFile? background, | ||||
|     required String? realmId, | ||||
|     required SnRealm? realm, | ||||
| @@ -31,12 +29,13 @@ abstract class SnChatRoom with _$SnChatRoom { | ||||
| } | ||||
|  | ||||
| @freezed | ||||
| abstract class SnChatMessage with _$SnChatMessage { | ||||
| sealed class SnChatMessage with _$SnChatMessage { | ||||
|   const factory SnChatMessage({ | ||||
|     required DateTime createdAt, | ||||
|     required DateTime updatedAt, | ||||
|     DateTime? deletedAt, | ||||
|     required String id, | ||||
|     @Default('text') String type, | ||||
|     String? content, | ||||
|     String? nonce, | ||||
|     @Default({}) Map<String, dynamic> meta, | ||||
| @@ -56,7 +55,7 @@ abstract class SnChatMessage with _$SnChatMessage { | ||||
| } | ||||
|  | ||||
| @freezed | ||||
| abstract class SnChatReaction with _$SnChatReaction { | ||||
| sealed class SnChatReaction with _$SnChatReaction { | ||||
|   const factory SnChatReaction({ | ||||
|     required DateTime createdAt, | ||||
|     required DateTime updatedAt, | ||||
| @@ -74,7 +73,7 @@ abstract class SnChatReaction with _$SnChatReaction { | ||||
| } | ||||
|  | ||||
| @freezed | ||||
| abstract class SnChatMember with _$SnChatMember { | ||||
| sealed class SnChatMember with _$SnChatMember { | ||||
|   const factory SnChatMember({ | ||||
|     required DateTime createdAt, | ||||
|     required DateTime updatedAt, | ||||
| @@ -88,7 +87,11 @@ abstract class SnChatMember with _$SnChatMember { | ||||
|     required int role, | ||||
|     required int notify, | ||||
|     required DateTime? joinedAt, | ||||
|     required DateTime? breakUntil, | ||||
|     required DateTime? timeoutUntil, | ||||
|     required bool isBot, | ||||
|     // Frontend data | ||||
|     DateTime? lastTyped, | ||||
|   }) = _SnChatMember; | ||||
|  | ||||
|   factory SnChatMember.fromJson(Map<String, dynamic> json) => | ||||
| @@ -96,7 +99,7 @@ abstract class SnChatMember with _$SnChatMember { | ||||
| } | ||||
|  | ||||
| @freezed | ||||
| abstract class SnChatSummary with _$SnChatSummary { | ||||
| sealed class SnChatSummary with _$SnChatSummary { | ||||
|   const factory SnChatSummary({ | ||||
|     required int unreadCount, | ||||
|     required SnChatMessage lastMessage, | ||||
| @@ -113,7 +116,7 @@ class MessageChangeAction { | ||||
| } | ||||
|  | ||||
| @freezed | ||||
| abstract class MessageChange with _$MessageChange { | ||||
| sealed class MessageChange with _$MessageChange { | ||||
|   const factory MessageChange({ | ||||
|     required String messageId, | ||||
|     required String action, | ||||
| @@ -126,7 +129,7 @@ abstract class MessageChange with _$MessageChange { | ||||
| } | ||||
|  | ||||
| @freezed | ||||
| abstract class MessageSyncResponse with _$MessageSyncResponse { | ||||
| sealed class MessageSyncResponse with _$MessageSyncResponse { | ||||
|   const factory MessageSyncResponse({ | ||||
|     @Default([]) List<MessageChange> changes, | ||||
|     required DateTime currentTimestamp, | ||||
| @@ -137,12 +140,52 @@ abstract class MessageSyncResponse with _$MessageSyncResponse { | ||||
| } | ||||
|  | ||||
| @freezed | ||||
| abstract class ChatRealtimeJoinResponse with _$ChatRealtimeJoinResponse { | ||||
| sealed class ChatRealtimeJoinResponse with _$ChatRealtimeJoinResponse { | ||||
|   const factory ChatRealtimeJoinResponse({ | ||||
|     required String provider, | ||||
|     required String endpoint, | ||||
|     required String token, | ||||
|     required Map<String, dynamic> config, | ||||
|     required String callId, | ||||
|     required String roomName, | ||||
|     required bool isAdmin, | ||||
|     required List<CallParticipant> participants, | ||||
|   }) = _ChatRealtimeJoinResponse; | ||||
|  | ||||
|   factory ChatRealtimeJoinResponse.fromJson(Map<String, dynamic> json) => | ||||
|       _$ChatRealtimeJoinResponseFromJson(json); | ||||
| } | ||||
|  | ||||
| @freezed | ||||
| sealed class CallParticipant with _$CallParticipant { | ||||
|   const factory CallParticipant({ | ||||
|     required String identity, | ||||
|     required String name, | ||||
|     required DateTime joinedAt, | ||||
|     required String? accountId, | ||||
|     required SnChatMember? profile, | ||||
|   }) = _CallParticipant; | ||||
|  | ||||
|   factory CallParticipant.fromJson(Map<String, dynamic> json) => | ||||
|       _$CallParticipantFromJson(json); | ||||
| } | ||||
|  | ||||
| @freezed | ||||
| sealed class SnRealtimeCall with _$SnRealtimeCall { | ||||
|   const factory SnRealtimeCall({ | ||||
|     required String id, | ||||
|     required DateTime createdAt, | ||||
|     required DateTime updatedAt, | ||||
|     required DateTime? deletedAt, | ||||
|     required DateTime? endedAt, | ||||
|     required String senderId, | ||||
|     required SnChatMember sender, | ||||
|     required String roomId, | ||||
|     required SnChatRoom room, | ||||
|     required Map<String, dynamic> upstreamConfig, | ||||
|     String? providerName, | ||||
|     String? sessionId, | ||||
|   }) = _SnRealtimeCall; | ||||
|  | ||||
|   factory SnRealtimeCall.fromJson(Map<String, dynamic> json) => | ||||
|       _$SnRealtimeCallFromJson(json); | ||||
| } | ||||
|   | ||||
| @@ -16,7 +16,7 @@ T _$identity<T>(T value) => value; | ||||
| /// @nodoc | ||||
| mixin _$SnChatRoom { | ||||
|  | ||||
|  String get id; String? get name; String? get description; int get type; bool get isPublic; String? get pictureId; SnCloudFile? get picture; String? get backgroundId; SnCloudFile? get background; String? get realmId; SnRealm? get realm; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; List<SnChatMember>? get members; | ||||
|  String get id; String? get name; String? get description; int get type; bool get isPublic; SnCloudFile? get picture; SnCloudFile? get background; String? get realmId; SnRealm? get realm; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; List<SnChatMember>? get members; | ||||
| /// Create a copy of SnChatRoom | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @JsonKey(includeFromJson: false, includeToJson: false) | ||||
| @@ -29,16 +29,16 @@ $SnChatRoomCopyWith<SnChatRoom> get copyWith => _$SnChatRoomCopyWithImpl<SnChatR | ||||
|  | ||||
| @override | ||||
| bool operator ==(Object other) { | ||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is SnChatRoom&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&(identical(other.type, type) || other.type == type)&&(identical(other.isPublic, isPublic) || other.isPublic == isPublic)&&(identical(other.pictureId, pictureId) || other.pictureId == pictureId)&&(identical(other.picture, picture) || other.picture == picture)&&(identical(other.backgroundId, backgroundId) || other.backgroundId == backgroundId)&&(identical(other.background, background) || other.background == background)&&(identical(other.realmId, realmId) || other.realmId == realmId)&&(identical(other.realm, realm) || other.realm == realm)&&(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.members, members)); | ||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is SnChatRoom&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&(identical(other.type, type) || other.type == type)&&(identical(other.isPublic, isPublic) || other.isPublic == isPublic)&&(identical(other.picture, picture) || other.picture == picture)&&(identical(other.background, background) || other.background == background)&&(identical(other.realmId, realmId) || other.realmId == realmId)&&(identical(other.realm, realm) || other.realm == realm)&&(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.members, members)); | ||||
| } | ||||
|  | ||||
| @JsonKey(includeFromJson: false, includeToJson: false) | ||||
| @override | ||||
| int get hashCode => Object.hash(runtimeType,id,name,description,type,isPublic,pictureId,picture,backgroundId,background,realmId,realm,createdAt,updatedAt,deletedAt,const DeepCollectionEquality().hash(members)); | ||||
| int get hashCode => Object.hash(runtimeType,id,name,description,type,isPublic,picture,background,realmId,realm,createdAt,updatedAt,deletedAt,const DeepCollectionEquality().hash(members)); | ||||
|  | ||||
| @override | ||||
| String toString() { | ||||
|   return 'SnChatRoom(id: $id, name: $name, description: $description, type: $type, isPublic: $isPublic, pictureId: $pictureId, picture: $picture, backgroundId: $backgroundId, background: $background, realmId: $realmId, realm: $realm, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, members: $members)'; | ||||
|   return 'SnChatRoom(id: $id, name: $name, description: $description, type: $type, isPublic: $isPublic, picture: $picture, background: $background, realmId: $realmId, realm: $realm, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, members: $members)'; | ||||
| } | ||||
|  | ||||
|  | ||||
| @@ -49,7 +49,7 @@ abstract mixin class $SnChatRoomCopyWith<$Res>  { | ||||
|   factory $SnChatRoomCopyWith(SnChatRoom value, $Res Function(SnChatRoom) _then) = _$SnChatRoomCopyWithImpl; | ||||
| @useResult | ||||
| $Res call({ | ||||
|  String id, String? name, String? description, int type, bool isPublic, String? pictureId, SnCloudFile? picture, String? backgroundId, SnCloudFile? background, String? realmId, SnRealm? realm, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, List<SnChatMember>? members | ||||
|  String id, String? name, String? description, int type, bool isPublic, SnCloudFile? picture, SnCloudFile? background, String? realmId, SnRealm? realm, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, List<SnChatMember>? members | ||||
| }); | ||||
|  | ||||
|  | ||||
| @@ -66,17 +66,15 @@ class _$SnChatRoomCopyWithImpl<$Res> | ||||
|  | ||||
| /// Create a copy of SnChatRoom | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? name = freezed,Object? description = freezed,Object? type = null,Object? isPublic = null,Object? pictureId = freezed,Object? picture = freezed,Object? backgroundId = freezed,Object? background = freezed,Object? realmId = freezed,Object? realm = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? members = freezed,}) { | ||||
| @pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? name = freezed,Object? description = freezed,Object? type = null,Object? isPublic = null,Object? picture = freezed,Object? background = freezed,Object? realmId = freezed,Object? realm = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? members = freezed,}) { | ||||
|   return _then(_self.copyWith( | ||||
| id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable | ||||
| as String,name: freezed == name ? _self.name : name // ignore: cast_nullable_to_non_nullable | ||||
| as String?,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable | ||||
| as String?,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable | ||||
| as int,isPublic: null == isPublic ? _self.isPublic : isPublic // ignore: cast_nullable_to_non_nullable | ||||
| as bool,pictureId: freezed == pictureId ? _self.pictureId : pictureId // ignore: cast_nullable_to_non_nullable | ||||
| as String?,picture: freezed == picture ? _self.picture : picture // ignore: cast_nullable_to_non_nullable | ||||
| as SnCloudFile?,backgroundId: freezed == backgroundId ? _self.backgroundId : backgroundId // ignore: cast_nullable_to_non_nullable | ||||
| as String?,background: freezed == background ? _self.background : background // ignore: cast_nullable_to_non_nullable | ||||
| as bool,picture: freezed == picture ? _self.picture : picture // ignore: cast_nullable_to_non_nullable | ||||
| as SnCloudFile?,background: freezed == background ? _self.background : background // ignore: cast_nullable_to_non_nullable | ||||
| as SnCloudFile?,realmId: freezed == realmId ? _self.realmId : realmId // ignore: cast_nullable_to_non_nullable | ||||
| as String?,realm: freezed == realm ? _self.realm : realm // ignore: cast_nullable_to_non_nullable | ||||
| as SnRealm?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable | ||||
| @@ -130,7 +128,7 @@ $SnRealmCopyWith<$Res>? get realm { | ||||
| @JsonSerializable() | ||||
|  | ||||
| class _SnChatRoom implements SnChatRoom { | ||||
|   const _SnChatRoom({required this.id, required this.name, required this.description, required this.type, required this.isPublic, required this.pictureId, required this.picture, required this.backgroundId, required this.background, required this.realmId, required this.realm, required this.createdAt, required this.updatedAt, required this.deletedAt, required final  List<SnChatMember>? members}): _members = members; | ||||
|   const _SnChatRoom({required this.id, required this.name, required this.description, required this.type, required this.isPublic, required this.picture, required this.background, required this.realmId, required this.realm, required this.createdAt, required this.updatedAt, required this.deletedAt, required final  List<SnChatMember>? members}): _members = members; | ||||
|   factory _SnChatRoom.fromJson(Map<String, dynamic> json) => _$SnChatRoomFromJson(json); | ||||
|  | ||||
| @override final  String id; | ||||
| @@ -138,9 +136,7 @@ class _SnChatRoom implements SnChatRoom { | ||||
| @override final  String? description; | ||||
| @override final  int type; | ||||
| @override final  bool isPublic; | ||||
| @override final  String? pictureId; | ||||
| @override final  SnCloudFile? picture; | ||||
| @override final  String? backgroundId; | ||||
| @override final  SnCloudFile? background; | ||||
| @override final  String? realmId; | ||||
| @override final  SnRealm? realm; | ||||
| @@ -170,16 +166,16 @@ Map<String, dynamic> toJson() { | ||||
|  | ||||
| @override | ||||
| bool operator ==(Object other) { | ||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnChatRoom&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&(identical(other.type, type) || other.type == type)&&(identical(other.isPublic, isPublic) || other.isPublic == isPublic)&&(identical(other.pictureId, pictureId) || other.pictureId == pictureId)&&(identical(other.picture, picture) || other.picture == picture)&&(identical(other.backgroundId, backgroundId) || other.backgroundId == backgroundId)&&(identical(other.background, background) || other.background == background)&&(identical(other.realmId, realmId) || other.realmId == realmId)&&(identical(other.realm, realm) || other.realm == realm)&&(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._members, _members)); | ||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnChatRoom&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&(identical(other.type, type) || other.type == type)&&(identical(other.isPublic, isPublic) || other.isPublic == isPublic)&&(identical(other.picture, picture) || other.picture == picture)&&(identical(other.background, background) || other.background == background)&&(identical(other.realmId, realmId) || other.realmId == realmId)&&(identical(other.realm, realm) || other.realm == realm)&&(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._members, _members)); | ||||
| } | ||||
|  | ||||
| @JsonKey(includeFromJson: false, includeToJson: false) | ||||
| @override | ||||
| int get hashCode => Object.hash(runtimeType,id,name,description,type,isPublic,pictureId,picture,backgroundId,background,realmId,realm,createdAt,updatedAt,deletedAt,const DeepCollectionEquality().hash(_members)); | ||||
| int get hashCode => Object.hash(runtimeType,id,name,description,type,isPublic,picture,background,realmId,realm,createdAt,updatedAt,deletedAt,const DeepCollectionEquality().hash(_members)); | ||||
|  | ||||
| @override | ||||
| String toString() { | ||||
|   return 'SnChatRoom(id: $id, name: $name, description: $description, type: $type, isPublic: $isPublic, pictureId: $pictureId, picture: $picture, backgroundId: $backgroundId, background: $background, realmId: $realmId, realm: $realm, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, members: $members)'; | ||||
|   return 'SnChatRoom(id: $id, name: $name, description: $description, type: $type, isPublic: $isPublic, picture: $picture, background: $background, realmId: $realmId, realm: $realm, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, members: $members)'; | ||||
| } | ||||
|  | ||||
|  | ||||
| @@ -190,7 +186,7 @@ abstract mixin class _$SnChatRoomCopyWith<$Res> implements $SnChatRoomCopyWith<$ | ||||
|   factory _$SnChatRoomCopyWith(_SnChatRoom value, $Res Function(_SnChatRoom) _then) = __$SnChatRoomCopyWithImpl; | ||||
| @override @useResult | ||||
| $Res call({ | ||||
|  String id, String? name, String? description, int type, bool isPublic, String? pictureId, SnCloudFile? picture, String? backgroundId, SnCloudFile? background, String? realmId, SnRealm? realm, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, List<SnChatMember>? members | ||||
|  String id, String? name, String? description, int type, bool isPublic, SnCloudFile? picture, SnCloudFile? background, String? realmId, SnRealm? realm, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, List<SnChatMember>? members | ||||
| }); | ||||
|  | ||||
|  | ||||
| @@ -207,17 +203,15 @@ class __$SnChatRoomCopyWithImpl<$Res> | ||||
|  | ||||
| /// Create a copy of SnChatRoom | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? name = freezed,Object? description = freezed,Object? type = null,Object? isPublic = null,Object? pictureId = freezed,Object? picture = freezed,Object? backgroundId = freezed,Object? background = freezed,Object? realmId = freezed,Object? realm = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? members = freezed,}) { | ||||
| @override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? name = freezed,Object? description = freezed,Object? type = null,Object? isPublic = null,Object? picture = freezed,Object? background = freezed,Object? realmId = freezed,Object? realm = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? members = freezed,}) { | ||||
|   return _then(_SnChatRoom( | ||||
| id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable | ||||
| as String,name: freezed == name ? _self.name : name // ignore: cast_nullable_to_non_nullable | ||||
| as String?,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable | ||||
| as String?,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable | ||||
| as int,isPublic: null == isPublic ? _self.isPublic : isPublic // ignore: cast_nullable_to_non_nullable | ||||
| as bool,pictureId: freezed == pictureId ? _self.pictureId : pictureId // ignore: cast_nullable_to_non_nullable | ||||
| as String?,picture: freezed == picture ? _self.picture : picture // ignore: cast_nullable_to_non_nullable | ||||
| as SnCloudFile?,backgroundId: freezed == backgroundId ? _self.backgroundId : backgroundId // ignore: cast_nullable_to_non_nullable | ||||
| as String?,background: freezed == background ? _self.background : background // ignore: cast_nullable_to_non_nullable | ||||
| as bool,picture: freezed == picture ? _self.picture : picture // ignore: cast_nullable_to_non_nullable | ||||
| as SnCloudFile?,background: freezed == background ? _self.background : background // ignore: cast_nullable_to_non_nullable | ||||
| as SnCloudFile?,realmId: freezed == realmId ? _self.realmId : realmId // ignore: cast_nullable_to_non_nullable | ||||
| as String?,realm: freezed == realm ? _self.realm : realm // ignore: cast_nullable_to_non_nullable | ||||
| as SnRealm?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable | ||||
| @@ -271,7 +265,7 @@ $SnRealmCopyWith<$Res>? get realm { | ||||
| /// @nodoc | ||||
| mixin _$SnChatMessage { | ||||
|  | ||||
|  DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; String get id; String? get content; String? get nonce; Map<String, dynamic> get meta; List<String> get membersMetioned; DateTime? get editedAt; List<SnCloudFile> get attachments; List<SnChatReaction> get reactions; String? get repliedMessageId; String? get forwardedMessageId; String get senderId; SnChatMember get sender; String get chatRoomId; | ||||
|  DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; String get id; String get type; String? get content; String? get nonce; Map<String, dynamic> get meta; List<String> get membersMetioned; DateTime? get editedAt; List<SnCloudFile> get attachments; List<SnChatReaction> get reactions; String? get repliedMessageId; String? get forwardedMessageId; String get senderId; SnChatMember get sender; String get chatRoomId; | ||||
| /// Create a copy of SnChatMessage | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @JsonKey(includeFromJson: false, includeToJson: false) | ||||
| @@ -284,16 +278,16 @@ $SnChatMessageCopyWith<SnChatMessage> get copyWith => _$SnChatMessageCopyWithImp | ||||
|  | ||||
| @override | ||||
| bool operator ==(Object other) { | ||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is SnChatMessage&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.id, id) || other.id == id)&&(identical(other.content, content) || other.content == content)&&(identical(other.nonce, nonce) || other.nonce == nonce)&&const DeepCollectionEquality().equals(other.meta, meta)&&const DeepCollectionEquality().equals(other.membersMetioned, membersMetioned)&&(identical(other.editedAt, editedAt) || other.editedAt == editedAt)&&const DeepCollectionEquality().equals(other.attachments, attachments)&&const DeepCollectionEquality().equals(other.reactions, reactions)&&(identical(other.repliedMessageId, repliedMessageId) || other.repliedMessageId == repliedMessageId)&&(identical(other.forwardedMessageId, forwardedMessageId) || other.forwardedMessageId == forwardedMessageId)&&(identical(other.senderId, senderId) || other.senderId == senderId)&&(identical(other.sender, sender) || other.sender == sender)&&(identical(other.chatRoomId, chatRoomId) || other.chatRoomId == chatRoomId)); | ||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is SnChatMessage&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.id, id) || other.id == id)&&(identical(other.type, type) || other.type == type)&&(identical(other.content, content) || other.content == content)&&(identical(other.nonce, nonce) || other.nonce == nonce)&&const DeepCollectionEquality().equals(other.meta, meta)&&const DeepCollectionEquality().equals(other.membersMetioned, membersMetioned)&&(identical(other.editedAt, editedAt) || other.editedAt == editedAt)&&const DeepCollectionEquality().equals(other.attachments, attachments)&&const DeepCollectionEquality().equals(other.reactions, reactions)&&(identical(other.repliedMessageId, repliedMessageId) || other.repliedMessageId == repliedMessageId)&&(identical(other.forwardedMessageId, forwardedMessageId) || other.forwardedMessageId == forwardedMessageId)&&(identical(other.senderId, senderId) || other.senderId == senderId)&&(identical(other.sender, sender) || other.sender == sender)&&(identical(other.chatRoomId, chatRoomId) || other.chatRoomId == chatRoomId)); | ||||
| } | ||||
|  | ||||
| @JsonKey(includeFromJson: false, includeToJson: false) | ||||
| @override | ||||
| int get hashCode => Object.hash(runtimeType,createdAt,updatedAt,deletedAt,id,content,nonce,const DeepCollectionEquality().hash(meta),const DeepCollectionEquality().hash(membersMetioned),editedAt,const DeepCollectionEquality().hash(attachments),const DeepCollectionEquality().hash(reactions),repliedMessageId,forwardedMessageId,senderId,sender,chatRoomId); | ||||
| int get hashCode => Object.hash(runtimeType,createdAt,updatedAt,deletedAt,id,type,content,nonce,const DeepCollectionEquality().hash(meta),const DeepCollectionEquality().hash(membersMetioned),editedAt,const DeepCollectionEquality().hash(attachments),const DeepCollectionEquality().hash(reactions),repliedMessageId,forwardedMessageId,senderId,sender,chatRoomId); | ||||
|  | ||||
| @override | ||||
| String toString() { | ||||
|   return 'SnChatMessage(createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, id: $id, content: $content, nonce: $nonce, meta: $meta, membersMetioned: $membersMetioned, editedAt: $editedAt, attachments: $attachments, reactions: $reactions, repliedMessageId: $repliedMessageId, forwardedMessageId: $forwardedMessageId, senderId: $senderId, sender: $sender, chatRoomId: $chatRoomId)'; | ||||
|   return 'SnChatMessage(createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, id: $id, type: $type, content: $content, nonce: $nonce, meta: $meta, membersMetioned: $membersMetioned, editedAt: $editedAt, attachments: $attachments, reactions: $reactions, repliedMessageId: $repliedMessageId, forwardedMessageId: $forwardedMessageId, senderId: $senderId, sender: $sender, chatRoomId: $chatRoomId)'; | ||||
| } | ||||
|  | ||||
|  | ||||
| @@ -304,7 +298,7 @@ abstract mixin class $SnChatMessageCopyWith<$Res>  { | ||||
|   factory $SnChatMessageCopyWith(SnChatMessage value, $Res Function(SnChatMessage) _then) = _$SnChatMessageCopyWithImpl; | ||||
| @useResult | ||||
| $Res call({ | ||||
|  DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, String id, String? content, String? nonce, Map<String, dynamic> meta, List<String> membersMetioned, DateTime? editedAt, List<SnCloudFile> attachments, List<SnChatReaction> reactions, String? repliedMessageId, String? forwardedMessageId, String senderId, SnChatMember sender, String chatRoomId | ||||
|  DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, String id, String type, String? content, String? nonce, Map<String, dynamic> meta, List<String> membersMetioned, DateTime? editedAt, List<SnCloudFile> attachments, List<SnChatReaction> reactions, String? repliedMessageId, String? forwardedMessageId, String senderId, SnChatMember sender, String chatRoomId | ||||
| }); | ||||
|  | ||||
|  | ||||
| @@ -321,12 +315,13 @@ class _$SnChatMessageCopyWithImpl<$Res> | ||||
|  | ||||
| /// Create a copy of SnChatMessage | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @pragma('vm:prefer-inline') @override $Res call({Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? id = null,Object? content = freezed,Object? nonce = freezed,Object? meta = null,Object? membersMetioned = null,Object? editedAt = freezed,Object? attachments = null,Object? reactions = null,Object? repliedMessageId = freezed,Object? forwardedMessageId = freezed,Object? senderId = null,Object? sender = null,Object? chatRoomId = null,}) { | ||||
| @pragma('vm:prefer-inline') @override $Res call({Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? id = null,Object? type = null,Object? content = freezed,Object? nonce = freezed,Object? meta = null,Object? membersMetioned = null,Object? editedAt = freezed,Object? attachments = null,Object? reactions = null,Object? repliedMessageId = freezed,Object? forwardedMessageId = freezed,Object? senderId = null,Object? sender = null,Object? chatRoomId = null,}) { | ||||
|   return _then(_self.copyWith( | ||||
| 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?,id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable | ||||
| as String,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable | ||||
| as String,content: freezed == content ? _self.content : content // ignore: cast_nullable_to_non_nullable | ||||
| as String?,nonce: freezed == nonce ? _self.nonce : nonce // ignore: cast_nullable_to_non_nullable | ||||
| as String?,meta: null == meta ? _self.meta : meta // ignore: cast_nullable_to_non_nullable | ||||
| @@ -359,13 +354,14 @@ $SnChatMemberCopyWith<$Res> get sender { | ||||
| @JsonSerializable() | ||||
|  | ||||
| class _SnChatMessage implements SnChatMessage { | ||||
|   const _SnChatMessage({required this.createdAt, required this.updatedAt, this.deletedAt, required this.id, this.content, this.nonce, final  Map<String, dynamic> meta = const {}, final  List<String> membersMetioned = const [], this.editedAt, final  List<SnCloudFile> attachments = const [], final  List<SnChatReaction> reactions = const [], this.repliedMessageId, this.forwardedMessageId, required this.senderId, required this.sender, required this.chatRoomId}): _meta = meta,_membersMetioned = membersMetioned,_attachments = attachments,_reactions = reactions; | ||||
|   const _SnChatMessage({required this.createdAt, required this.updatedAt, this.deletedAt, required this.id, this.type = 'text', this.content, this.nonce, final  Map<String, dynamic> meta = const {}, final  List<String> membersMetioned = const [], this.editedAt, final  List<SnCloudFile> attachments = const [], final  List<SnChatReaction> reactions = const [], this.repliedMessageId, this.forwardedMessageId, required this.senderId, required this.sender, required this.chatRoomId}): _meta = meta,_membersMetioned = membersMetioned,_attachments = attachments,_reactions = reactions; | ||||
|   factory _SnChatMessage.fromJson(Map<String, dynamic> json) => _$SnChatMessageFromJson(json); | ||||
|  | ||||
| @override final  DateTime createdAt; | ||||
| @override final  DateTime updatedAt; | ||||
| @override final  DateTime? deletedAt; | ||||
| @override final  String id; | ||||
| @override@JsonKey() final  String type; | ||||
| @override final  String? content; | ||||
| @override final  String? nonce; | ||||
|  final  Map<String, dynamic> _meta; | ||||
| @@ -416,16 +412,16 @@ Map<String, dynamic> toJson() { | ||||
|  | ||||
| @override | ||||
| bool operator ==(Object other) { | ||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnChatMessage&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.id, id) || other.id == id)&&(identical(other.content, content) || other.content == content)&&(identical(other.nonce, nonce) || other.nonce == nonce)&&const DeepCollectionEquality().equals(other._meta, _meta)&&const DeepCollectionEquality().equals(other._membersMetioned, _membersMetioned)&&(identical(other.editedAt, editedAt) || other.editedAt == editedAt)&&const DeepCollectionEquality().equals(other._attachments, _attachments)&&const DeepCollectionEquality().equals(other._reactions, _reactions)&&(identical(other.repliedMessageId, repliedMessageId) || other.repliedMessageId == repliedMessageId)&&(identical(other.forwardedMessageId, forwardedMessageId) || other.forwardedMessageId == forwardedMessageId)&&(identical(other.senderId, senderId) || other.senderId == senderId)&&(identical(other.sender, sender) || other.sender == sender)&&(identical(other.chatRoomId, chatRoomId) || other.chatRoomId == chatRoomId)); | ||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnChatMessage&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.id, id) || other.id == id)&&(identical(other.type, type) || other.type == type)&&(identical(other.content, content) || other.content == content)&&(identical(other.nonce, nonce) || other.nonce == nonce)&&const DeepCollectionEquality().equals(other._meta, _meta)&&const DeepCollectionEquality().equals(other._membersMetioned, _membersMetioned)&&(identical(other.editedAt, editedAt) || other.editedAt == editedAt)&&const DeepCollectionEquality().equals(other._attachments, _attachments)&&const DeepCollectionEquality().equals(other._reactions, _reactions)&&(identical(other.repliedMessageId, repliedMessageId) || other.repliedMessageId == repliedMessageId)&&(identical(other.forwardedMessageId, forwardedMessageId) || other.forwardedMessageId == forwardedMessageId)&&(identical(other.senderId, senderId) || other.senderId == senderId)&&(identical(other.sender, sender) || other.sender == sender)&&(identical(other.chatRoomId, chatRoomId) || other.chatRoomId == chatRoomId)); | ||||
| } | ||||
|  | ||||
| @JsonKey(includeFromJson: false, includeToJson: false) | ||||
| @override | ||||
| int get hashCode => Object.hash(runtimeType,createdAt,updatedAt,deletedAt,id,content,nonce,const DeepCollectionEquality().hash(_meta),const DeepCollectionEquality().hash(_membersMetioned),editedAt,const DeepCollectionEquality().hash(_attachments),const DeepCollectionEquality().hash(_reactions),repliedMessageId,forwardedMessageId,senderId,sender,chatRoomId); | ||||
| int get hashCode => Object.hash(runtimeType,createdAt,updatedAt,deletedAt,id,type,content,nonce,const DeepCollectionEquality().hash(_meta),const DeepCollectionEquality().hash(_membersMetioned),editedAt,const DeepCollectionEquality().hash(_attachments),const DeepCollectionEquality().hash(_reactions),repliedMessageId,forwardedMessageId,senderId,sender,chatRoomId); | ||||
|  | ||||
| @override | ||||
| String toString() { | ||||
|   return 'SnChatMessage(createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, id: $id, content: $content, nonce: $nonce, meta: $meta, membersMetioned: $membersMetioned, editedAt: $editedAt, attachments: $attachments, reactions: $reactions, repliedMessageId: $repliedMessageId, forwardedMessageId: $forwardedMessageId, senderId: $senderId, sender: $sender, chatRoomId: $chatRoomId)'; | ||||
|   return 'SnChatMessage(createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, id: $id, type: $type, content: $content, nonce: $nonce, meta: $meta, membersMetioned: $membersMetioned, editedAt: $editedAt, attachments: $attachments, reactions: $reactions, repliedMessageId: $repliedMessageId, forwardedMessageId: $forwardedMessageId, senderId: $senderId, sender: $sender, chatRoomId: $chatRoomId)'; | ||||
| } | ||||
|  | ||||
|  | ||||
| @@ -436,7 +432,7 @@ abstract mixin class _$SnChatMessageCopyWith<$Res> implements $SnChatMessageCopy | ||||
|   factory _$SnChatMessageCopyWith(_SnChatMessage value, $Res Function(_SnChatMessage) _then) = __$SnChatMessageCopyWithImpl; | ||||
| @override @useResult | ||||
| $Res call({ | ||||
|  DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, String id, String? content, String? nonce, Map<String, dynamic> meta, List<String> membersMetioned, DateTime? editedAt, List<SnCloudFile> attachments, List<SnChatReaction> reactions, String? repliedMessageId, String? forwardedMessageId, String senderId, SnChatMember sender, String chatRoomId | ||||
|  DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, String id, String type, String? content, String? nonce, Map<String, dynamic> meta, List<String> membersMetioned, DateTime? editedAt, List<SnCloudFile> attachments, List<SnChatReaction> reactions, String? repliedMessageId, String? forwardedMessageId, String senderId, SnChatMember sender, String chatRoomId | ||||
| }); | ||||
|  | ||||
|  | ||||
| @@ -453,12 +449,13 @@ class __$SnChatMessageCopyWithImpl<$Res> | ||||
|  | ||||
| /// Create a copy of SnChatMessage | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @override @pragma('vm:prefer-inline') $Res call({Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? id = null,Object? content = freezed,Object? nonce = freezed,Object? meta = null,Object? membersMetioned = null,Object? editedAt = freezed,Object? attachments = null,Object? reactions = null,Object? repliedMessageId = freezed,Object? forwardedMessageId = freezed,Object? senderId = null,Object? sender = null,Object? chatRoomId = null,}) { | ||||
| @override @pragma('vm:prefer-inline') $Res call({Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? id = null,Object? type = null,Object? content = freezed,Object? nonce = freezed,Object? meta = null,Object? membersMetioned = null,Object? editedAt = freezed,Object? attachments = null,Object? reactions = null,Object? repliedMessageId = freezed,Object? forwardedMessageId = freezed,Object? senderId = null,Object? sender = null,Object? chatRoomId = null,}) { | ||||
|   return _then(_SnChatMessage( | ||||
| 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?,id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable | ||||
| as String,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable | ||||
| as String,content: freezed == content ? _self.content : content // ignore: cast_nullable_to_non_nullable | ||||
| as String?,nonce: freezed == nonce ? _self.nonce : nonce // ignore: cast_nullable_to_non_nullable | ||||
| as String?,meta: null == meta ? _self._meta : meta // ignore: cast_nullable_to_non_nullable | ||||
| @@ -666,7 +663,8 @@ $SnChatMemberCopyWith<$Res> get sender { | ||||
| /// @nodoc | ||||
| mixin _$SnChatMember { | ||||
|  | ||||
|  DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; String get id; String get chatRoomId; SnChatRoom? get chatRoom; String get accountId; SnAccount get account; String? get nick; int get role; int get notify; DateTime? get joinedAt; bool get isBot; | ||||
|  DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; String get id; String get chatRoomId; SnChatRoom? get chatRoom; String get accountId; SnAccount get account; String? get nick; int get role; int get notify; DateTime? get joinedAt; DateTime? get breakUntil; DateTime? get timeoutUntil; bool get isBot;// Frontend data | ||||
|  DateTime? get lastTyped; | ||||
| /// Create a copy of SnChatMember | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @JsonKey(includeFromJson: false, includeToJson: false) | ||||
| @@ -679,16 +677,16 @@ $SnChatMemberCopyWith<SnChatMember> get copyWith => _$SnChatMemberCopyWithImpl<S | ||||
|  | ||||
| @override | ||||
| bool operator ==(Object other) { | ||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is SnChatMember&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.id, id) || other.id == id)&&(identical(other.chatRoomId, chatRoomId) || other.chatRoomId == chatRoomId)&&(identical(other.chatRoom, chatRoom) || other.chatRoom == chatRoom)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.account, account) || other.account == account)&&(identical(other.nick, nick) || other.nick == nick)&&(identical(other.role, role) || other.role == role)&&(identical(other.notify, notify) || other.notify == notify)&&(identical(other.joinedAt, joinedAt) || other.joinedAt == joinedAt)&&(identical(other.isBot, isBot) || other.isBot == isBot)); | ||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is SnChatMember&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.id, id) || other.id == id)&&(identical(other.chatRoomId, chatRoomId) || other.chatRoomId == chatRoomId)&&(identical(other.chatRoom, chatRoom) || other.chatRoom == chatRoom)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.account, account) || other.account == account)&&(identical(other.nick, nick) || other.nick == nick)&&(identical(other.role, role) || other.role == role)&&(identical(other.notify, notify) || other.notify == notify)&&(identical(other.joinedAt, joinedAt) || other.joinedAt == joinedAt)&&(identical(other.breakUntil, breakUntil) || other.breakUntil == breakUntil)&&(identical(other.timeoutUntil, timeoutUntil) || other.timeoutUntil == timeoutUntil)&&(identical(other.isBot, isBot) || other.isBot == isBot)&&(identical(other.lastTyped, lastTyped) || other.lastTyped == lastTyped)); | ||||
| } | ||||
|  | ||||
| @JsonKey(includeFromJson: false, includeToJson: false) | ||||
| @override | ||||
| int get hashCode => Object.hash(runtimeType,createdAt,updatedAt,deletedAt,id,chatRoomId,chatRoom,accountId,account,nick,role,notify,joinedAt,isBot); | ||||
| int get hashCode => Object.hash(runtimeType,createdAt,updatedAt,deletedAt,id,chatRoomId,chatRoom,accountId,account,nick,role,notify,joinedAt,breakUntil,timeoutUntil,isBot,lastTyped); | ||||
|  | ||||
| @override | ||||
| String toString() { | ||||
|   return 'SnChatMember(createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, id: $id, chatRoomId: $chatRoomId, chatRoom: $chatRoom, accountId: $accountId, account: $account, nick: $nick, role: $role, notify: $notify, joinedAt: $joinedAt, isBot: $isBot)'; | ||||
|   return 'SnChatMember(createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, id: $id, chatRoomId: $chatRoomId, chatRoom: $chatRoom, accountId: $accountId, account: $account, nick: $nick, role: $role, notify: $notify, joinedAt: $joinedAt, breakUntil: $breakUntil, timeoutUntil: $timeoutUntil, isBot: $isBot, lastTyped: $lastTyped)'; | ||||
| } | ||||
|  | ||||
|  | ||||
| @@ -699,7 +697,7 @@ abstract mixin class $SnChatMemberCopyWith<$Res>  { | ||||
|   factory $SnChatMemberCopyWith(SnChatMember value, $Res Function(SnChatMember) _then) = _$SnChatMemberCopyWithImpl; | ||||
| @useResult | ||||
| $Res call({ | ||||
|  DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, String id, String chatRoomId, SnChatRoom? chatRoom, String accountId, SnAccount account, String? nick, int role, int notify, DateTime? joinedAt, bool isBot | ||||
|  DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, String id, String chatRoomId, SnChatRoom? chatRoom, String accountId, SnAccount account, String? nick, int role, int notify, DateTime? joinedAt, DateTime? breakUntil, DateTime? timeoutUntil, bool isBot, DateTime? lastTyped | ||||
| }); | ||||
|  | ||||
|  | ||||
| @@ -716,7 +714,7 @@ class _$SnChatMemberCopyWithImpl<$Res> | ||||
|  | ||||
| /// Create a copy of SnChatMember | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @pragma('vm:prefer-inline') @override $Res call({Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? id = null,Object? chatRoomId = null,Object? chatRoom = freezed,Object? accountId = null,Object? account = null,Object? nick = freezed,Object? role = null,Object? notify = null,Object? joinedAt = freezed,Object? isBot = null,}) { | ||||
| @pragma('vm:prefer-inline') @override $Res call({Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? id = null,Object? chatRoomId = null,Object? chatRoom = freezed,Object? accountId = null,Object? account = null,Object? nick = freezed,Object? role = null,Object? notify = null,Object? joinedAt = freezed,Object? breakUntil = freezed,Object? timeoutUntil = freezed,Object? isBot = null,Object? lastTyped = freezed,}) { | ||||
|   return _then(_self.copyWith( | ||||
| 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 | ||||
| @@ -730,8 +728,11 @@ as SnAccount,nick: freezed == nick ? _self.nick : nick // ignore: cast_nullable_ | ||||
| as String?,role: null == role ? _self.role : role // ignore: cast_nullable_to_non_nullable | ||||
| as int,notify: null == notify ? _self.notify : notify // ignore: cast_nullable_to_non_nullable | ||||
| as int,joinedAt: freezed == joinedAt ? _self.joinedAt : joinedAt // ignore: cast_nullable_to_non_nullable | ||||
| as DateTime?,breakUntil: freezed == breakUntil ? _self.breakUntil : breakUntil // ignore: cast_nullable_to_non_nullable | ||||
| as DateTime?,timeoutUntil: freezed == timeoutUntil ? _self.timeoutUntil : timeoutUntil // ignore: cast_nullable_to_non_nullable | ||||
| as DateTime?,isBot: null == isBot ? _self.isBot : isBot // ignore: cast_nullable_to_non_nullable | ||||
| as bool, | ||||
| as bool,lastTyped: freezed == lastTyped ? _self.lastTyped : lastTyped // ignore: cast_nullable_to_non_nullable | ||||
| as DateTime?, | ||||
|   )); | ||||
| } | ||||
| /// Create a copy of SnChatMember | ||||
| @@ -763,7 +764,7 @@ $SnAccountCopyWith<$Res> get account { | ||||
| @JsonSerializable() | ||||
|  | ||||
| class _SnChatMember implements SnChatMember { | ||||
|   const _SnChatMember({required this.createdAt, required this.updatedAt, required this.deletedAt, required this.id, required this.chatRoomId, required this.chatRoom, required this.accountId, required this.account, required this.nick, required this.role, required this.notify, required this.joinedAt, required this.isBot}); | ||||
|   const _SnChatMember({required this.createdAt, required this.updatedAt, required this.deletedAt, required this.id, required this.chatRoomId, required this.chatRoom, required this.accountId, required this.account, required this.nick, required this.role, required this.notify, required this.joinedAt, required this.breakUntil, required this.timeoutUntil, required this.isBot, this.lastTyped}); | ||||
|   factory _SnChatMember.fromJson(Map<String, dynamic> json) => _$SnChatMemberFromJson(json); | ||||
|  | ||||
| @override final  DateTime createdAt; | ||||
| @@ -778,7 +779,11 @@ class _SnChatMember implements SnChatMember { | ||||
| @override final  int role; | ||||
| @override final  int notify; | ||||
| @override final  DateTime? joinedAt; | ||||
| @override final  DateTime? breakUntil; | ||||
| @override final  DateTime? timeoutUntil; | ||||
| @override final  bool isBot; | ||||
| // Frontend data | ||||
| @override final  DateTime? lastTyped; | ||||
|  | ||||
| /// Create a copy of SnChatMember | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @@ -793,16 +798,16 @@ Map<String, dynamic> toJson() { | ||||
|  | ||||
| @override | ||||
| bool operator ==(Object other) { | ||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnChatMember&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.id, id) || other.id == id)&&(identical(other.chatRoomId, chatRoomId) || other.chatRoomId == chatRoomId)&&(identical(other.chatRoom, chatRoom) || other.chatRoom == chatRoom)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.account, account) || other.account == account)&&(identical(other.nick, nick) || other.nick == nick)&&(identical(other.role, role) || other.role == role)&&(identical(other.notify, notify) || other.notify == notify)&&(identical(other.joinedAt, joinedAt) || other.joinedAt == joinedAt)&&(identical(other.isBot, isBot) || other.isBot == isBot)); | ||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnChatMember&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.id, id) || other.id == id)&&(identical(other.chatRoomId, chatRoomId) || other.chatRoomId == chatRoomId)&&(identical(other.chatRoom, chatRoom) || other.chatRoom == chatRoom)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.account, account) || other.account == account)&&(identical(other.nick, nick) || other.nick == nick)&&(identical(other.role, role) || other.role == role)&&(identical(other.notify, notify) || other.notify == notify)&&(identical(other.joinedAt, joinedAt) || other.joinedAt == joinedAt)&&(identical(other.breakUntil, breakUntil) || other.breakUntil == breakUntil)&&(identical(other.timeoutUntil, timeoutUntil) || other.timeoutUntil == timeoutUntil)&&(identical(other.isBot, isBot) || other.isBot == isBot)&&(identical(other.lastTyped, lastTyped) || other.lastTyped == lastTyped)); | ||||
| } | ||||
|  | ||||
| @JsonKey(includeFromJson: false, includeToJson: false) | ||||
| @override | ||||
| int get hashCode => Object.hash(runtimeType,createdAt,updatedAt,deletedAt,id,chatRoomId,chatRoom,accountId,account,nick,role,notify,joinedAt,isBot); | ||||
| int get hashCode => Object.hash(runtimeType,createdAt,updatedAt,deletedAt,id,chatRoomId,chatRoom,accountId,account,nick,role,notify,joinedAt,breakUntil,timeoutUntil,isBot,lastTyped); | ||||
|  | ||||
| @override | ||||
| String toString() { | ||||
|   return 'SnChatMember(createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, id: $id, chatRoomId: $chatRoomId, chatRoom: $chatRoom, accountId: $accountId, account: $account, nick: $nick, role: $role, notify: $notify, joinedAt: $joinedAt, isBot: $isBot)'; | ||||
|   return 'SnChatMember(createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, id: $id, chatRoomId: $chatRoomId, chatRoom: $chatRoom, accountId: $accountId, account: $account, nick: $nick, role: $role, notify: $notify, joinedAt: $joinedAt, breakUntil: $breakUntil, timeoutUntil: $timeoutUntil, isBot: $isBot, lastTyped: $lastTyped)'; | ||||
| } | ||||
|  | ||||
|  | ||||
| @@ -813,7 +818,7 @@ abstract mixin class _$SnChatMemberCopyWith<$Res> implements $SnChatMemberCopyWi | ||||
|   factory _$SnChatMemberCopyWith(_SnChatMember value, $Res Function(_SnChatMember) _then) = __$SnChatMemberCopyWithImpl; | ||||
| @override @useResult | ||||
| $Res call({ | ||||
|  DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, String id, String chatRoomId, SnChatRoom? chatRoom, String accountId, SnAccount account, String? nick, int role, int notify, DateTime? joinedAt, bool isBot | ||||
|  DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, String id, String chatRoomId, SnChatRoom? chatRoom, String accountId, SnAccount account, String? nick, int role, int notify, DateTime? joinedAt, DateTime? breakUntil, DateTime? timeoutUntil, bool isBot, DateTime? lastTyped | ||||
| }); | ||||
|  | ||||
|  | ||||
| @@ -830,7 +835,7 @@ class __$SnChatMemberCopyWithImpl<$Res> | ||||
|  | ||||
| /// Create a copy of SnChatMember | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @override @pragma('vm:prefer-inline') $Res call({Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? id = null,Object? chatRoomId = null,Object? chatRoom = freezed,Object? accountId = null,Object? account = null,Object? nick = freezed,Object? role = null,Object? notify = null,Object? joinedAt = freezed,Object? isBot = null,}) { | ||||
| @override @pragma('vm:prefer-inline') $Res call({Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? id = null,Object? chatRoomId = null,Object? chatRoom = freezed,Object? accountId = null,Object? account = null,Object? nick = freezed,Object? role = null,Object? notify = null,Object? joinedAt = freezed,Object? breakUntil = freezed,Object? timeoutUntil = freezed,Object? isBot = null,Object? lastTyped = freezed,}) { | ||||
|   return _then(_SnChatMember( | ||||
| 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 | ||||
| @@ -844,8 +849,11 @@ as SnAccount,nick: freezed == nick ? _self.nick : nick // ignore: cast_nullable_ | ||||
| as String?,role: null == role ? _self.role : role // ignore: cast_nullable_to_non_nullable | ||||
| as int,notify: null == notify ? _self.notify : notify // ignore: cast_nullable_to_non_nullable | ||||
| as int,joinedAt: freezed == joinedAt ? _self.joinedAt : joinedAt // ignore: cast_nullable_to_non_nullable | ||||
| as DateTime?,breakUntil: freezed == breakUntil ? _self.breakUntil : breakUntil // ignore: cast_nullable_to_non_nullable | ||||
| as DateTime?,timeoutUntil: freezed == timeoutUntil ? _self.timeoutUntil : timeoutUntil // ignore: cast_nullable_to_non_nullable | ||||
| as DateTime?,isBot: null == isBot ? _self.isBot : isBot // ignore: cast_nullable_to_non_nullable | ||||
| as bool, | ||||
| as bool,lastTyped: freezed == lastTyped ? _self.lastTyped : lastTyped // ignore: cast_nullable_to_non_nullable | ||||
| as DateTime?, | ||||
|   )); | ||||
| } | ||||
|  | ||||
| @@ -1339,7 +1347,7 @@ as DateTime, | ||||
| /// @nodoc | ||||
| mixin _$ChatRealtimeJoinResponse { | ||||
|  | ||||
|  String get token; Map<String, dynamic> get config; | ||||
|  String get provider; String get endpoint; String get token; String get callId; String get roomName; bool get isAdmin; List<CallParticipant> get participants; | ||||
| /// Create a copy of ChatRealtimeJoinResponse | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @JsonKey(includeFromJson: false, includeToJson: false) | ||||
| @@ -1352,16 +1360,16 @@ $ChatRealtimeJoinResponseCopyWith<ChatRealtimeJoinResponse> get copyWith => _$Ch | ||||
|  | ||||
| @override | ||||
| bool operator ==(Object other) { | ||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is ChatRealtimeJoinResponse&&(identical(other.token, token) || other.token == token)&&const DeepCollectionEquality().equals(other.config, config)); | ||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is ChatRealtimeJoinResponse&&(identical(other.provider, provider) || other.provider == provider)&&(identical(other.endpoint, endpoint) || other.endpoint == endpoint)&&(identical(other.token, token) || other.token == token)&&(identical(other.callId, callId) || other.callId == callId)&&(identical(other.roomName, roomName) || other.roomName == roomName)&&(identical(other.isAdmin, isAdmin) || other.isAdmin == isAdmin)&&const DeepCollectionEquality().equals(other.participants, participants)); | ||||
| } | ||||
|  | ||||
| @JsonKey(includeFromJson: false, includeToJson: false) | ||||
| @override | ||||
| int get hashCode => Object.hash(runtimeType,token,const DeepCollectionEquality().hash(config)); | ||||
| int get hashCode => Object.hash(runtimeType,provider,endpoint,token,callId,roomName,isAdmin,const DeepCollectionEquality().hash(participants)); | ||||
|  | ||||
| @override | ||||
| String toString() { | ||||
|   return 'ChatRealtimeJoinResponse(token: $token, config: $config)'; | ||||
|   return 'ChatRealtimeJoinResponse(provider: $provider, endpoint: $endpoint, token: $token, callId: $callId, roomName: $roomName, isAdmin: $isAdmin, participants: $participants)'; | ||||
| } | ||||
|  | ||||
|  | ||||
| @@ -1372,7 +1380,7 @@ abstract mixin class $ChatRealtimeJoinResponseCopyWith<$Res>  { | ||||
|   factory $ChatRealtimeJoinResponseCopyWith(ChatRealtimeJoinResponse value, $Res Function(ChatRealtimeJoinResponse) _then) = _$ChatRealtimeJoinResponseCopyWithImpl; | ||||
| @useResult | ||||
| $Res call({ | ||||
|  String token, Map<String, dynamic> config | ||||
|  String provider, String endpoint, String token, String callId, String roomName, bool isAdmin, List<CallParticipant> participants | ||||
| }); | ||||
|  | ||||
|  | ||||
| @@ -1389,11 +1397,16 @@ class _$ChatRealtimeJoinResponseCopyWithImpl<$Res> | ||||
|  | ||||
| /// Create a copy of ChatRealtimeJoinResponse | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @pragma('vm:prefer-inline') @override $Res call({Object? token = null,Object? config = null,}) { | ||||
| @pragma('vm:prefer-inline') @override $Res call({Object? provider = null,Object? endpoint = null,Object? token = null,Object? callId = null,Object? roomName = null,Object? isAdmin = null,Object? participants = null,}) { | ||||
|   return _then(_self.copyWith( | ||||
| token: null == token ? _self.token : token // ignore: cast_nullable_to_non_nullable | ||||
| as String,config: null == config ? _self.config : config // ignore: cast_nullable_to_non_nullable | ||||
| as Map<String, dynamic>, | ||||
| provider: null == provider ? _self.provider : provider // ignore: cast_nullable_to_non_nullable | ||||
| as String,endpoint: null == endpoint ? _self.endpoint : endpoint // ignore: cast_nullable_to_non_nullable | ||||
| as String,token: null == token ? _self.token : token // ignore: cast_nullable_to_non_nullable | ||||
| as String,callId: null == callId ? _self.callId : callId // ignore: cast_nullable_to_non_nullable | ||||
| as String,roomName: null == roomName ? _self.roomName : roomName // ignore: cast_nullable_to_non_nullable | ||||
| as String,isAdmin: null == isAdmin ? _self.isAdmin : isAdmin // ignore: cast_nullable_to_non_nullable | ||||
| as bool,participants: null == participants ? _self.participants : participants // ignore: cast_nullable_to_non_nullable | ||||
| as List<CallParticipant>, | ||||
|   )); | ||||
| } | ||||
|  | ||||
| @@ -1404,15 +1417,20 @@ as Map<String, dynamic>, | ||||
| @JsonSerializable() | ||||
|  | ||||
| class _ChatRealtimeJoinResponse implements ChatRealtimeJoinResponse { | ||||
|   const _ChatRealtimeJoinResponse({required this.token, required final  Map<String, dynamic> config}): _config = config; | ||||
|   const _ChatRealtimeJoinResponse({required this.provider, required this.endpoint, required this.token, required this.callId, required this.roomName, required this.isAdmin, required final  List<CallParticipant> participants}): _participants = participants; | ||||
|   factory _ChatRealtimeJoinResponse.fromJson(Map<String, dynamic> json) => _$ChatRealtimeJoinResponseFromJson(json); | ||||
|  | ||||
| @override final  String provider; | ||||
| @override final  String endpoint; | ||||
| @override final  String token; | ||||
|  final  Map<String, dynamic> _config; | ||||
| @override Map<String, dynamic> get config { | ||||
|   if (_config is EqualUnmodifiableMapView) return _config; | ||||
| @override final  String callId; | ||||
| @override final  String roomName; | ||||
| @override final  bool isAdmin; | ||||
|  final  List<CallParticipant> _participants; | ||||
| @override List<CallParticipant> get participants { | ||||
|   if (_participants is EqualUnmodifiableListView) return _participants; | ||||
|   // ignore: implicit_dynamic_type | ||||
|   return EqualUnmodifiableMapView(_config); | ||||
|   return EqualUnmodifiableListView(_participants); | ||||
| } | ||||
|  | ||||
|  | ||||
| @@ -1429,16 +1447,16 @@ Map<String, dynamic> toJson() { | ||||
|  | ||||
| @override | ||||
| bool operator ==(Object other) { | ||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is _ChatRealtimeJoinResponse&&(identical(other.token, token) || other.token == token)&&const DeepCollectionEquality().equals(other._config, _config)); | ||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is _ChatRealtimeJoinResponse&&(identical(other.provider, provider) || other.provider == provider)&&(identical(other.endpoint, endpoint) || other.endpoint == endpoint)&&(identical(other.token, token) || other.token == token)&&(identical(other.callId, callId) || other.callId == callId)&&(identical(other.roomName, roomName) || other.roomName == roomName)&&(identical(other.isAdmin, isAdmin) || other.isAdmin == isAdmin)&&const DeepCollectionEquality().equals(other._participants, _participants)); | ||||
| } | ||||
|  | ||||
| @JsonKey(includeFromJson: false, includeToJson: false) | ||||
| @override | ||||
| int get hashCode => Object.hash(runtimeType,token,const DeepCollectionEquality().hash(_config)); | ||||
| int get hashCode => Object.hash(runtimeType,provider,endpoint,token,callId,roomName,isAdmin,const DeepCollectionEquality().hash(_participants)); | ||||
|  | ||||
| @override | ||||
| String toString() { | ||||
|   return 'ChatRealtimeJoinResponse(token: $token, config: $config)'; | ||||
|   return 'ChatRealtimeJoinResponse(provider: $provider, endpoint: $endpoint, token: $token, callId: $callId, roomName: $roomName, isAdmin: $isAdmin, participants: $participants)'; | ||||
| } | ||||
|  | ||||
|  | ||||
| @@ -1449,7 +1467,7 @@ abstract mixin class _$ChatRealtimeJoinResponseCopyWith<$Res> implements $ChatRe | ||||
|   factory _$ChatRealtimeJoinResponseCopyWith(_ChatRealtimeJoinResponse value, $Res Function(_ChatRealtimeJoinResponse) _then) = __$ChatRealtimeJoinResponseCopyWithImpl; | ||||
| @override @useResult | ||||
| $Res call({ | ||||
|  String token, Map<String, dynamic> config | ||||
|  String provider, String endpoint, String token, String callId, String roomName, bool isAdmin, List<CallParticipant> participants | ||||
| }); | ||||
|  | ||||
|  | ||||
| @@ -1466,15 +1484,397 @@ class __$ChatRealtimeJoinResponseCopyWithImpl<$Res> | ||||
|  | ||||
| /// Create a copy of ChatRealtimeJoinResponse | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @override @pragma('vm:prefer-inline') $Res call({Object? token = null,Object? config = null,}) { | ||||
| @override @pragma('vm:prefer-inline') $Res call({Object? provider = null,Object? endpoint = null,Object? token = null,Object? callId = null,Object? roomName = null,Object? isAdmin = null,Object? participants = null,}) { | ||||
|   return _then(_ChatRealtimeJoinResponse( | ||||
| token: null == token ? _self.token : token // ignore: cast_nullable_to_non_nullable | ||||
| as String,config: null == config ? _self._config : config // ignore: cast_nullable_to_non_nullable | ||||
| as Map<String, dynamic>, | ||||
| provider: null == provider ? _self.provider : provider // ignore: cast_nullable_to_non_nullable | ||||
| as String,endpoint: null == endpoint ? _self.endpoint : endpoint // ignore: cast_nullable_to_non_nullable | ||||
| as String,token: null == token ? _self.token : token // ignore: cast_nullable_to_non_nullable | ||||
| as String,callId: null == callId ? _self.callId : callId // ignore: cast_nullable_to_non_nullable | ||||
| as String,roomName: null == roomName ? _self.roomName : roomName // ignore: cast_nullable_to_non_nullable | ||||
| as String,isAdmin: null == isAdmin ? _self.isAdmin : isAdmin // ignore: cast_nullable_to_non_nullable | ||||
| as bool,participants: null == participants ? _self._participants : participants // ignore: cast_nullable_to_non_nullable | ||||
| as List<CallParticipant>, | ||||
|   )); | ||||
| } | ||||
|  | ||||
|  | ||||
| } | ||||
|  | ||||
|  | ||||
| /// @nodoc | ||||
| mixin _$CallParticipant { | ||||
|  | ||||
|  String get identity; String get name; DateTime get joinedAt; String? get accountId; SnChatMember? get profile; | ||||
| /// Create a copy of CallParticipant | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @JsonKey(includeFromJson: false, includeToJson: false) | ||||
| @pragma('vm:prefer-inline') | ||||
| $CallParticipantCopyWith<CallParticipant> get copyWith => _$CallParticipantCopyWithImpl<CallParticipant>(this as CallParticipant, _$identity); | ||||
|  | ||||
|   /// Serializes this CallParticipant to a JSON map. | ||||
|   Map<String, dynamic> toJson(); | ||||
|  | ||||
|  | ||||
| @override | ||||
| bool operator ==(Object other) { | ||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is CallParticipant&&(identical(other.identity, identity) || other.identity == identity)&&(identical(other.name, name) || other.name == name)&&(identical(other.joinedAt, joinedAt) || other.joinedAt == joinedAt)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.profile, profile) || other.profile == profile)); | ||||
| } | ||||
|  | ||||
| @JsonKey(includeFromJson: false, includeToJson: false) | ||||
| @override | ||||
| int get hashCode => Object.hash(runtimeType,identity,name,joinedAt,accountId,profile); | ||||
|  | ||||
| @override | ||||
| String toString() { | ||||
|   return 'CallParticipant(identity: $identity, name: $name, joinedAt: $joinedAt, accountId: $accountId, profile: $profile)'; | ||||
| } | ||||
|  | ||||
|  | ||||
| } | ||||
|  | ||||
| /// @nodoc | ||||
| abstract mixin class $CallParticipantCopyWith<$Res>  { | ||||
|   factory $CallParticipantCopyWith(CallParticipant value, $Res Function(CallParticipant) _then) = _$CallParticipantCopyWithImpl; | ||||
| @useResult | ||||
| $Res call({ | ||||
|  String identity, String name, DateTime joinedAt, String? accountId, SnChatMember? profile | ||||
| }); | ||||
|  | ||||
|  | ||||
| $SnChatMemberCopyWith<$Res>? get profile; | ||||
|  | ||||
| } | ||||
| /// @nodoc | ||||
| class _$CallParticipantCopyWithImpl<$Res> | ||||
|     implements $CallParticipantCopyWith<$Res> { | ||||
|   _$CallParticipantCopyWithImpl(this._self, this._then); | ||||
|  | ||||
|   final CallParticipant _self; | ||||
|   final $Res Function(CallParticipant) _then; | ||||
|  | ||||
| /// Create a copy of CallParticipant | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @pragma('vm:prefer-inline') @override $Res call({Object? identity = null,Object? name = null,Object? joinedAt = null,Object? accountId = freezed,Object? profile = freezed,}) { | ||||
|   return _then(_self.copyWith( | ||||
| identity: null == identity ? _self.identity : identity // ignore: cast_nullable_to_non_nullable | ||||
| as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable | ||||
| as String,joinedAt: null == joinedAt ? _self.joinedAt : joinedAt // ignore: cast_nullable_to_non_nullable | ||||
| as DateTime,accountId: freezed == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable | ||||
| as String?,profile: freezed == profile ? _self.profile : profile // ignore: cast_nullable_to_non_nullable | ||||
| as SnChatMember?, | ||||
|   )); | ||||
| } | ||||
| /// Create a copy of CallParticipant | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @override | ||||
| @pragma('vm:prefer-inline') | ||||
| $SnChatMemberCopyWith<$Res>? get profile { | ||||
|     if (_self.profile == null) { | ||||
|     return null; | ||||
|   } | ||||
|  | ||||
|   return $SnChatMemberCopyWith<$Res>(_self.profile!, (value) { | ||||
|     return _then(_self.copyWith(profile: value)); | ||||
|   }); | ||||
| } | ||||
| } | ||||
|  | ||||
|  | ||||
| /// @nodoc | ||||
| @JsonSerializable() | ||||
|  | ||||
| class _CallParticipant implements CallParticipant { | ||||
|   const _CallParticipant({required this.identity, required this.name, required this.joinedAt, required this.accountId, required this.profile}); | ||||
|   factory _CallParticipant.fromJson(Map<String, dynamic> json) => _$CallParticipantFromJson(json); | ||||
|  | ||||
| @override final  String identity; | ||||
| @override final  String name; | ||||
| @override final  DateTime joinedAt; | ||||
| @override final  String? accountId; | ||||
| @override final  SnChatMember? profile; | ||||
|  | ||||
| /// Create a copy of CallParticipant | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @override @JsonKey(includeFromJson: false, includeToJson: false) | ||||
| @pragma('vm:prefer-inline') | ||||
| _$CallParticipantCopyWith<_CallParticipant> get copyWith => __$CallParticipantCopyWithImpl<_CallParticipant>(this, _$identity); | ||||
|  | ||||
| @override | ||||
| Map<String, dynamic> toJson() { | ||||
|   return _$CallParticipantToJson(this, ); | ||||
| } | ||||
|  | ||||
| @override | ||||
| bool operator ==(Object other) { | ||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is _CallParticipant&&(identical(other.identity, identity) || other.identity == identity)&&(identical(other.name, name) || other.name == name)&&(identical(other.joinedAt, joinedAt) || other.joinedAt == joinedAt)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.profile, profile) || other.profile == profile)); | ||||
| } | ||||
|  | ||||
| @JsonKey(includeFromJson: false, includeToJson: false) | ||||
| @override | ||||
| int get hashCode => Object.hash(runtimeType,identity,name,joinedAt,accountId,profile); | ||||
|  | ||||
| @override | ||||
| String toString() { | ||||
|   return 'CallParticipant(identity: $identity, name: $name, joinedAt: $joinedAt, accountId: $accountId, profile: $profile)'; | ||||
| } | ||||
|  | ||||
|  | ||||
| } | ||||
|  | ||||
| /// @nodoc | ||||
| abstract mixin class _$CallParticipantCopyWith<$Res> implements $CallParticipantCopyWith<$Res> { | ||||
|   factory _$CallParticipantCopyWith(_CallParticipant value, $Res Function(_CallParticipant) _then) = __$CallParticipantCopyWithImpl; | ||||
| @override @useResult | ||||
| $Res call({ | ||||
|  String identity, String name, DateTime joinedAt, String? accountId, SnChatMember? profile | ||||
| }); | ||||
|  | ||||
|  | ||||
| @override $SnChatMemberCopyWith<$Res>? get profile; | ||||
|  | ||||
| } | ||||
| /// @nodoc | ||||
| class __$CallParticipantCopyWithImpl<$Res> | ||||
|     implements _$CallParticipantCopyWith<$Res> { | ||||
|   __$CallParticipantCopyWithImpl(this._self, this._then); | ||||
|  | ||||
|   final _CallParticipant _self; | ||||
|   final $Res Function(_CallParticipant) _then; | ||||
|  | ||||
| /// Create a copy of CallParticipant | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @override @pragma('vm:prefer-inline') $Res call({Object? identity = null,Object? name = null,Object? joinedAt = null,Object? accountId = freezed,Object? profile = freezed,}) { | ||||
|   return _then(_CallParticipant( | ||||
| identity: null == identity ? _self.identity : identity // ignore: cast_nullable_to_non_nullable | ||||
| as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable | ||||
| as String,joinedAt: null == joinedAt ? _self.joinedAt : joinedAt // ignore: cast_nullable_to_non_nullable | ||||
| as DateTime,accountId: freezed == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable | ||||
| as String?,profile: freezed == profile ? _self.profile : profile // ignore: cast_nullable_to_non_nullable | ||||
| as SnChatMember?, | ||||
|   )); | ||||
| } | ||||
|  | ||||
| /// Create a copy of CallParticipant | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @override | ||||
| @pragma('vm:prefer-inline') | ||||
| $SnChatMemberCopyWith<$Res>? get profile { | ||||
|     if (_self.profile == null) { | ||||
|     return null; | ||||
|   } | ||||
|  | ||||
|   return $SnChatMemberCopyWith<$Res>(_self.profile!, (value) { | ||||
|     return _then(_self.copyWith(profile: value)); | ||||
|   }); | ||||
| } | ||||
| } | ||||
|  | ||||
|  | ||||
| /// @nodoc | ||||
| mixin _$SnRealtimeCall { | ||||
|  | ||||
|  String get id; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; DateTime? get endedAt; String get senderId; SnChatMember get sender; String get roomId; SnChatRoom get room; Map<String, dynamic> get upstreamConfig; String? get providerName; String? get sessionId; | ||||
| /// Create a copy of SnRealtimeCall | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @JsonKey(includeFromJson: false, includeToJson: false) | ||||
| @pragma('vm:prefer-inline') | ||||
| $SnRealtimeCallCopyWith<SnRealtimeCall> get copyWith => _$SnRealtimeCallCopyWithImpl<SnRealtimeCall>(this as SnRealtimeCall, _$identity); | ||||
|  | ||||
|   /// Serializes this SnRealtimeCall to a JSON map. | ||||
|   Map<String, dynamic> toJson(); | ||||
|  | ||||
|  | ||||
| @override | ||||
| bool operator ==(Object other) { | ||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is SnRealtimeCall&&(identical(other.id, id) || other.id == id)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.endedAt, endedAt) || other.endedAt == endedAt)&&(identical(other.senderId, senderId) || other.senderId == senderId)&&(identical(other.sender, sender) || other.sender == sender)&&(identical(other.roomId, roomId) || other.roomId == roomId)&&(identical(other.room, room) || other.room == room)&&const DeepCollectionEquality().equals(other.upstreamConfig, upstreamConfig)&&(identical(other.providerName, providerName) || other.providerName == providerName)&&(identical(other.sessionId, sessionId) || other.sessionId == sessionId)); | ||||
| } | ||||
|  | ||||
| @JsonKey(includeFromJson: false, includeToJson: false) | ||||
| @override | ||||
| int get hashCode => Object.hash(runtimeType,id,createdAt,updatedAt,deletedAt,endedAt,senderId,sender,roomId,room,const DeepCollectionEquality().hash(upstreamConfig),providerName,sessionId); | ||||
|  | ||||
| @override | ||||
| String toString() { | ||||
|   return 'SnRealtimeCall(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, endedAt: $endedAt, senderId: $senderId, sender: $sender, roomId: $roomId, room: $room, upstreamConfig: $upstreamConfig, providerName: $providerName, sessionId: $sessionId)'; | ||||
| } | ||||
|  | ||||
|  | ||||
| } | ||||
|  | ||||
| /// @nodoc | ||||
| abstract mixin class $SnRealtimeCallCopyWith<$Res>  { | ||||
|   factory $SnRealtimeCallCopyWith(SnRealtimeCall value, $Res Function(SnRealtimeCall) _then) = _$SnRealtimeCallCopyWithImpl; | ||||
| @useResult | ||||
| $Res call({ | ||||
|  String id, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, DateTime? endedAt, String senderId, SnChatMember sender, String roomId, SnChatRoom room, Map<String, dynamic> upstreamConfig, String? providerName, String? sessionId | ||||
| }); | ||||
|  | ||||
|  | ||||
| $SnChatMemberCopyWith<$Res> get sender;$SnChatRoomCopyWith<$Res> get room; | ||||
|  | ||||
| } | ||||
| /// @nodoc | ||||
| class _$SnRealtimeCallCopyWithImpl<$Res> | ||||
|     implements $SnRealtimeCallCopyWith<$Res> { | ||||
|   _$SnRealtimeCallCopyWithImpl(this._self, this._then); | ||||
|  | ||||
|   final SnRealtimeCall _self; | ||||
|   final $Res Function(SnRealtimeCall) _then; | ||||
|  | ||||
| /// Create a copy of SnRealtimeCall | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? endedAt = freezed,Object? senderId = null,Object? sender = null,Object? roomId = null,Object? room = null,Object? upstreamConfig = null,Object? providerName = freezed,Object? sessionId = freezed,}) { | ||||
|   return _then(_self.copyWith( | ||||
| id: null == id ? _self.id : id // 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?,endedAt: freezed == endedAt ? _self.endedAt : endedAt // ignore: cast_nullable_to_non_nullable | ||||
| as DateTime?,senderId: null == senderId ? _self.senderId : senderId // ignore: cast_nullable_to_non_nullable | ||||
| as String,sender: null == sender ? _self.sender : sender // ignore: cast_nullable_to_non_nullable | ||||
| as SnChatMember,roomId: null == roomId ? _self.roomId : roomId // ignore: cast_nullable_to_non_nullable | ||||
| as String,room: null == room ? _self.room : room // ignore: cast_nullable_to_non_nullable | ||||
| as SnChatRoom,upstreamConfig: null == upstreamConfig ? _self.upstreamConfig : upstreamConfig // ignore: cast_nullable_to_non_nullable | ||||
| as Map<String, dynamic>,providerName: freezed == providerName ? _self.providerName : providerName // ignore: cast_nullable_to_non_nullable | ||||
| as String?,sessionId: freezed == sessionId ? _self.sessionId : sessionId // ignore: cast_nullable_to_non_nullable | ||||
| as String?, | ||||
|   )); | ||||
| } | ||||
| /// Create a copy of SnRealtimeCall | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @override | ||||
| @pragma('vm:prefer-inline') | ||||
| $SnChatMemberCopyWith<$Res> get sender { | ||||
|    | ||||
|   return $SnChatMemberCopyWith<$Res>(_self.sender, (value) { | ||||
|     return _then(_self.copyWith(sender: value)); | ||||
|   }); | ||||
| }/// Create a copy of SnRealtimeCall | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @override | ||||
| @pragma('vm:prefer-inline') | ||||
| $SnChatRoomCopyWith<$Res> get room { | ||||
|    | ||||
|   return $SnChatRoomCopyWith<$Res>(_self.room, (value) { | ||||
|     return _then(_self.copyWith(room: value)); | ||||
|   }); | ||||
| } | ||||
| } | ||||
|  | ||||
|  | ||||
| /// @nodoc | ||||
| @JsonSerializable() | ||||
|  | ||||
| class _SnRealtimeCall implements SnRealtimeCall { | ||||
|   const _SnRealtimeCall({required this.id, required this.createdAt, required this.updatedAt, required this.deletedAt, required this.endedAt, required this.senderId, required this.sender, required this.roomId, required this.room, required final  Map<String, dynamic> upstreamConfig, this.providerName, this.sessionId}): _upstreamConfig = upstreamConfig; | ||||
|   factory _SnRealtimeCall.fromJson(Map<String, dynamic> json) => _$SnRealtimeCallFromJson(json); | ||||
|  | ||||
| @override final  String id; | ||||
| @override final  DateTime createdAt; | ||||
| @override final  DateTime updatedAt; | ||||
| @override final  DateTime? deletedAt; | ||||
| @override final  DateTime? endedAt; | ||||
| @override final  String senderId; | ||||
| @override final  SnChatMember sender; | ||||
| @override final  String roomId; | ||||
| @override final  SnChatRoom room; | ||||
|  final  Map<String, dynamic> _upstreamConfig; | ||||
| @override Map<String, dynamic> get upstreamConfig { | ||||
|   if (_upstreamConfig is EqualUnmodifiableMapView) return _upstreamConfig; | ||||
|   // ignore: implicit_dynamic_type | ||||
|   return EqualUnmodifiableMapView(_upstreamConfig); | ||||
| } | ||||
|  | ||||
| @override final  String? providerName; | ||||
| @override final  String? sessionId; | ||||
|  | ||||
| /// Create a copy of SnRealtimeCall | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @override @JsonKey(includeFromJson: false, includeToJson: false) | ||||
| @pragma('vm:prefer-inline') | ||||
| _$SnRealtimeCallCopyWith<_SnRealtimeCall> get copyWith => __$SnRealtimeCallCopyWithImpl<_SnRealtimeCall>(this, _$identity); | ||||
|  | ||||
| @override | ||||
| Map<String, dynamic> toJson() { | ||||
|   return _$SnRealtimeCallToJson(this, ); | ||||
| } | ||||
|  | ||||
| @override | ||||
| bool operator ==(Object other) { | ||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnRealtimeCall&&(identical(other.id, id) || other.id == id)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.endedAt, endedAt) || other.endedAt == endedAt)&&(identical(other.senderId, senderId) || other.senderId == senderId)&&(identical(other.sender, sender) || other.sender == sender)&&(identical(other.roomId, roomId) || other.roomId == roomId)&&(identical(other.room, room) || other.room == room)&&const DeepCollectionEquality().equals(other._upstreamConfig, _upstreamConfig)&&(identical(other.providerName, providerName) || other.providerName == providerName)&&(identical(other.sessionId, sessionId) || other.sessionId == sessionId)); | ||||
| } | ||||
|  | ||||
| @JsonKey(includeFromJson: false, includeToJson: false) | ||||
| @override | ||||
| int get hashCode => Object.hash(runtimeType,id,createdAt,updatedAt,deletedAt,endedAt,senderId,sender,roomId,room,const DeepCollectionEquality().hash(_upstreamConfig),providerName,sessionId); | ||||
|  | ||||
| @override | ||||
| String toString() { | ||||
|   return 'SnRealtimeCall(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, endedAt: $endedAt, senderId: $senderId, sender: $sender, roomId: $roomId, room: $room, upstreamConfig: $upstreamConfig, providerName: $providerName, sessionId: $sessionId)'; | ||||
| } | ||||
|  | ||||
|  | ||||
| } | ||||
|  | ||||
| /// @nodoc | ||||
| abstract mixin class _$SnRealtimeCallCopyWith<$Res> implements $SnRealtimeCallCopyWith<$Res> { | ||||
|   factory _$SnRealtimeCallCopyWith(_SnRealtimeCall value, $Res Function(_SnRealtimeCall) _then) = __$SnRealtimeCallCopyWithImpl; | ||||
| @override @useResult | ||||
| $Res call({ | ||||
|  String id, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, DateTime? endedAt, String senderId, SnChatMember sender, String roomId, SnChatRoom room, Map<String, dynamic> upstreamConfig, String? providerName, String? sessionId | ||||
| }); | ||||
|  | ||||
|  | ||||
| @override $SnChatMemberCopyWith<$Res> get sender;@override $SnChatRoomCopyWith<$Res> get room; | ||||
|  | ||||
| } | ||||
| /// @nodoc | ||||
| class __$SnRealtimeCallCopyWithImpl<$Res> | ||||
|     implements _$SnRealtimeCallCopyWith<$Res> { | ||||
|   __$SnRealtimeCallCopyWithImpl(this._self, this._then); | ||||
|  | ||||
|   final _SnRealtimeCall _self; | ||||
|   final $Res Function(_SnRealtimeCall) _then; | ||||
|  | ||||
| /// Create a copy of SnRealtimeCall | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? endedAt = freezed,Object? senderId = null,Object? sender = null,Object? roomId = null,Object? room = null,Object? upstreamConfig = null,Object? providerName = freezed,Object? sessionId = freezed,}) { | ||||
|   return _then(_SnRealtimeCall( | ||||
| id: null == id ? _self.id : id // 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?,endedAt: freezed == endedAt ? _self.endedAt : endedAt // ignore: cast_nullable_to_non_nullable | ||||
| as DateTime?,senderId: null == senderId ? _self.senderId : senderId // ignore: cast_nullable_to_non_nullable | ||||
| as String,sender: null == sender ? _self.sender : sender // ignore: cast_nullable_to_non_nullable | ||||
| as SnChatMember,roomId: null == roomId ? _self.roomId : roomId // ignore: cast_nullable_to_non_nullable | ||||
| as String,room: null == room ? _self.room : room // ignore: cast_nullable_to_non_nullable | ||||
| as SnChatRoom,upstreamConfig: null == upstreamConfig ? _self._upstreamConfig : upstreamConfig // ignore: cast_nullable_to_non_nullable | ||||
| as Map<String, dynamic>,providerName: freezed == providerName ? _self.providerName : providerName // ignore: cast_nullable_to_non_nullable | ||||
| as String?,sessionId: freezed == sessionId ? _self.sessionId : sessionId // ignore: cast_nullable_to_non_nullable | ||||
| as String?, | ||||
|   )); | ||||
| } | ||||
|  | ||||
| /// Create a copy of SnRealtimeCall | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @override | ||||
| @pragma('vm:prefer-inline') | ||||
| $SnChatMemberCopyWith<$Res> get sender { | ||||
|    | ||||
|   return $SnChatMemberCopyWith<$Res>(_self.sender, (value) { | ||||
|     return _then(_self.copyWith(sender: value)); | ||||
|   }); | ||||
| }/// Create a copy of SnRealtimeCall | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @override | ||||
| @pragma('vm:prefer-inline') | ||||
| $SnChatRoomCopyWith<$Res> get room { | ||||
|    | ||||
|   return $SnChatRoomCopyWith<$Res>(_self.room, (value) { | ||||
|     return _then(_self.copyWith(room: value)); | ||||
|   }); | ||||
| } | ||||
| } | ||||
|  | ||||
| // dart format on | ||||
|   | ||||
| @@ -12,12 +12,10 @@ _SnChatRoom _$SnChatRoomFromJson(Map<String, dynamic> json) => _SnChatRoom( | ||||
|   description: json['description'] as String?, | ||||
|   type: (json['type'] as num).toInt(), | ||||
|   isPublic: json['is_public'] as bool, | ||||
|   pictureId: json['picture_id'] as String?, | ||||
|   picture: | ||||
|       json['picture'] == null | ||||
|           ? null | ||||
|           : SnCloudFile.fromJson(json['picture'] as Map<String, dynamic>), | ||||
|   backgroundId: json['background_id'] as String?, | ||||
|   background: | ||||
|       json['background'] == null | ||||
|           ? null | ||||
| @@ -46,9 +44,7 @@ Map<String, dynamic> _$SnChatRoomToJson(_SnChatRoom instance) => | ||||
|       'description': instance.description, | ||||
|       'type': instance.type, | ||||
|       'is_public': instance.isPublic, | ||||
|       'picture_id': instance.pictureId, | ||||
|       'picture': instance.picture?.toJson(), | ||||
|       'background_id': instance.backgroundId, | ||||
|       'background': instance.background?.toJson(), | ||||
|       'realm_id': instance.realmId, | ||||
|       'realm': instance.realm?.toJson(), | ||||
| @@ -67,6 +63,7 @@ _SnChatMessage _$SnChatMessageFromJson(Map<String, dynamic> json) => | ||||
|               ? null | ||||
|               : DateTime.parse(json['deleted_at'] as String), | ||||
|       id: json['id'] as String, | ||||
|       type: json['type'] as String? ?? 'text', | ||||
|       content: json['content'] as String?, | ||||
|       nonce: json['nonce'] as String?, | ||||
|       meta: json['meta'] as Map<String, dynamic>? ?? const {}, | ||||
| @@ -102,6 +99,7 @@ Map<String, dynamic> _$SnChatMessageToJson(_SnChatMessage instance) => | ||||
|       'updated_at': instance.updatedAt.toIso8601String(), | ||||
|       'deleted_at': instance.deletedAt?.toIso8601String(), | ||||
|       'id': instance.id, | ||||
|       'type': instance.type, | ||||
|       'content': instance.content, | ||||
|       'nonce': instance.nonce, | ||||
|       'meta': instance.meta, | ||||
| @@ -168,7 +166,19 @@ _SnChatMember _$SnChatMemberFromJson(Map<String, dynamic> json) => | ||||
|           json['joined_at'] == null | ||||
|               ? null | ||||
|               : DateTime.parse(json['joined_at'] as String), | ||||
|       breakUntil: | ||||
|           json['break_until'] == null | ||||
|               ? null | ||||
|               : DateTime.parse(json['break_until'] as String), | ||||
|       timeoutUntil: | ||||
|           json['timeout_until'] == null | ||||
|               ? null | ||||
|               : DateTime.parse(json['timeout_until'] as String), | ||||
|       isBot: json['is_bot'] as bool, | ||||
|       lastTyped: | ||||
|           json['last_typed'] == null | ||||
|               ? null | ||||
|               : DateTime.parse(json['last_typed'] as String), | ||||
|     ); | ||||
|  | ||||
| Map<String, dynamic> _$SnChatMemberToJson(_SnChatMember instance) => | ||||
| @@ -185,7 +195,10 @@ Map<String, dynamic> _$SnChatMemberToJson(_SnChatMember instance) => | ||||
|       'role': instance.role, | ||||
|       'notify': instance.notify, | ||||
|       'joined_at': instance.joinedAt?.toIso8601String(), | ||||
|       'break_until': instance.breakUntil?.toIso8601String(), | ||||
|       'timeout_until': instance.timeoutUntil?.toIso8601String(), | ||||
|       'is_bot': instance.isBot, | ||||
|       'last_typed': instance.lastTyped?.toIso8601String(), | ||||
|     }; | ||||
|  | ||||
| _SnChatSummary _$SnChatSummaryFromJson(Map<String, dynamic> json) => | ||||
| @@ -241,10 +254,85 @@ Map<String, dynamic> _$MessageSyncResponseToJson( | ||||
| _ChatRealtimeJoinResponse _$ChatRealtimeJoinResponseFromJson( | ||||
|   Map<String, dynamic> json, | ||||
| ) => _ChatRealtimeJoinResponse( | ||||
|   provider: json['provider'] as String, | ||||
|   endpoint: json['endpoint'] as String, | ||||
|   token: json['token'] as String, | ||||
|   config: json['config'] as Map<String, dynamic>, | ||||
|   callId: json['call_id'] as String, | ||||
|   roomName: json['room_name'] as String, | ||||
|   isAdmin: json['is_admin'] as bool, | ||||
|   participants: | ||||
|       (json['participants'] as List<dynamic>) | ||||
|           .map((e) => CallParticipant.fromJson(e as Map<String, dynamic>)) | ||||
|           .toList(), | ||||
| ); | ||||
|  | ||||
| Map<String, dynamic> _$ChatRealtimeJoinResponseToJson( | ||||
|   _ChatRealtimeJoinResponse instance, | ||||
| ) => <String, dynamic>{'token': instance.token, 'config': instance.config}; | ||||
| ) => <String, dynamic>{ | ||||
|   'provider': instance.provider, | ||||
|   'endpoint': instance.endpoint, | ||||
|   'token': instance.token, | ||||
|   'call_id': instance.callId, | ||||
|   'room_name': instance.roomName, | ||||
|   'is_admin': instance.isAdmin, | ||||
|   'participants': instance.participants.map((e) => e.toJson()).toList(), | ||||
| }; | ||||
|  | ||||
| _CallParticipant _$CallParticipantFromJson(Map<String, dynamic> json) => | ||||
|     _CallParticipant( | ||||
|       identity: json['identity'] as String, | ||||
|       name: json['name'] as String, | ||||
|       joinedAt: DateTime.parse(json['joined_at'] as String), | ||||
|       accountId: json['account_id'] as String?, | ||||
|       profile: | ||||
|           json['profile'] == null | ||||
|               ? null | ||||
|               : SnChatMember.fromJson(json['profile'] as Map<String, dynamic>), | ||||
|     ); | ||||
|  | ||||
| Map<String, dynamic> _$CallParticipantToJson(_CallParticipant instance) => | ||||
|     <String, dynamic>{ | ||||
|       'identity': instance.identity, | ||||
|       'name': instance.name, | ||||
|       'joined_at': instance.joinedAt.toIso8601String(), | ||||
|       'account_id': instance.accountId, | ||||
|       'profile': instance.profile?.toJson(), | ||||
|     }; | ||||
|  | ||||
| _SnRealtimeCall _$SnRealtimeCallFromJson(Map<String, dynamic> json) => | ||||
|     _SnRealtimeCall( | ||||
|       id: json['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), | ||||
|       endedAt: | ||||
|           json['ended_at'] == null | ||||
|               ? null | ||||
|               : DateTime.parse(json['ended_at'] as String), | ||||
|       senderId: json['sender_id'] as String, | ||||
|       sender: SnChatMember.fromJson(json['sender'] as Map<String, dynamic>), | ||||
|       roomId: json['room_id'] as String, | ||||
|       room: SnChatRoom.fromJson(json['room'] as Map<String, dynamic>), | ||||
|       upstreamConfig: json['upstream_config'] as Map<String, dynamic>, | ||||
|       providerName: json['provider_name'] as String?, | ||||
|       sessionId: json['session_id'] as String?, | ||||
|     ); | ||||
|  | ||||
| Map<String, dynamic> _$SnRealtimeCallToJson(_SnRealtimeCall instance) => | ||||
|     <String, dynamic>{ | ||||
|       'id': instance.id, | ||||
|       'created_at': instance.createdAt.toIso8601String(), | ||||
|       'updated_at': instance.updatedAt.toIso8601String(), | ||||
|       'deleted_at': instance.deletedAt?.toIso8601String(), | ||||
|       'ended_at': instance.endedAt?.toIso8601String(), | ||||
|       'sender_id': instance.senderId, | ||||
|       'sender': instance.sender.toJson(), | ||||
|       'room_id': instance.roomId, | ||||
|       'room': instance.room.toJson(), | ||||
|       'upstream_config': instance.upstreamConfig, | ||||
|       'provider_name': instance.providerName, | ||||
|       'session_id': instance.sessionId, | ||||
|     }; | ||||
|   | ||||
| @@ -6,7 +6,7 @@ part 'file.g.dart'; | ||||
| enum UniversalFileType { image, video, audio, file } | ||||
|  | ||||
| @freezed | ||||
| abstract class UniversalFile with _$UniversalFile { | ||||
| sealed class UniversalFile with _$UniversalFile { | ||||
|   const UniversalFile._(); | ||||
|  | ||||
|   const factory UniversalFile({ | ||||
| @@ -31,7 +31,7 @@ abstract class UniversalFile with _$UniversalFile { | ||||
| } | ||||
|  | ||||
| @freezed | ||||
| abstract class SnCloudFile with _$SnCloudFile { | ||||
| sealed class SnCloudFile with _$SnCloudFile { | ||||
|   const factory SnCloudFile({ | ||||
|     required String id, | ||||
|     required String name, | ||||
| @@ -43,7 +43,6 @@ abstract class SnCloudFile with _$SnCloudFile { | ||||
|     required int size, | ||||
|     required DateTime? uploadedAt, | ||||
|     required String? uploadedTo, | ||||
|     required int usedCount, | ||||
|     required DateTime createdAt, | ||||
|     required DateTime updatedAt, | ||||
|     required DateTime? deletedAt, | ||||
|   | ||||
| @@ -146,7 +146,7 @@ as UniversalFileType, | ||||
| /// @nodoc | ||||
| mixin _$SnCloudFile { | ||||
|  | ||||
|  String get id; String get name; String? get description; Map<String, dynamic>? get fileMeta; Map<String, dynamic>? get userMeta; String? get mimeType; String? get hash; int get size; DateTime? get uploadedAt; String? get uploadedTo; int get usedCount; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; | ||||
|  String get id; String get name; String? get description; Map<String, dynamic>? get fileMeta; Map<String, dynamic>? get userMeta; String? get mimeType; String? get hash; int get size; DateTime? get uploadedAt; String? get uploadedTo; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; | ||||
| /// Create a copy of SnCloudFile | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @JsonKey(includeFromJson: false, includeToJson: false) | ||||
| @@ -159,16 +159,16 @@ $SnCloudFileCopyWith<SnCloudFile> get copyWith => _$SnCloudFileCopyWithImpl<SnCl | ||||
|  | ||||
| @override | ||||
| bool operator ==(Object other) { | ||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is SnCloudFile&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&const DeepCollectionEquality().equals(other.fileMeta, fileMeta)&&const DeepCollectionEquality().equals(other.userMeta, userMeta)&&(identical(other.mimeType, mimeType) || other.mimeType == mimeType)&&(identical(other.hash, hash) || other.hash == hash)&&(identical(other.size, size) || other.size == size)&&(identical(other.uploadedAt, uploadedAt) || other.uploadedAt == uploadedAt)&&(identical(other.uploadedTo, uploadedTo) || other.uploadedTo == uploadedTo)&&(identical(other.usedCount, usedCount) || other.usedCount == usedCount)&&(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 SnCloudFile&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&const DeepCollectionEquality().equals(other.fileMeta, fileMeta)&&const DeepCollectionEquality().equals(other.userMeta, userMeta)&&(identical(other.mimeType, mimeType) || other.mimeType == mimeType)&&(identical(other.hash, hash) || other.hash == hash)&&(identical(other.size, size) || other.size == size)&&(identical(other.uploadedAt, uploadedAt) || other.uploadedAt == uploadedAt)&&(identical(other.uploadedTo, uploadedTo) || other.uploadedTo == uploadedTo)&&(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,name,description,const DeepCollectionEquality().hash(fileMeta),const DeepCollectionEquality().hash(userMeta),mimeType,hash,size,uploadedAt,uploadedTo,usedCount,createdAt,updatedAt,deletedAt); | ||||
| int get hashCode => Object.hash(runtimeType,id,name,description,const DeepCollectionEquality().hash(fileMeta),const DeepCollectionEquality().hash(userMeta),mimeType,hash,size,uploadedAt,uploadedTo,createdAt,updatedAt,deletedAt); | ||||
|  | ||||
| @override | ||||
| String toString() { | ||||
|   return 'SnCloudFile(id: $id, name: $name, description: $description, fileMeta: $fileMeta, userMeta: $userMeta, mimeType: $mimeType, hash: $hash, size: $size, uploadedAt: $uploadedAt, uploadedTo: $uploadedTo, usedCount: $usedCount, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; | ||||
|   return 'SnCloudFile(id: $id, name: $name, description: $description, fileMeta: $fileMeta, userMeta: $userMeta, mimeType: $mimeType, hash: $hash, size: $size, uploadedAt: $uploadedAt, uploadedTo: $uploadedTo, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; | ||||
| } | ||||
|  | ||||
|  | ||||
| @@ -179,7 +179,7 @@ abstract mixin class $SnCloudFileCopyWith<$Res>  { | ||||
|   factory $SnCloudFileCopyWith(SnCloudFile value, $Res Function(SnCloudFile) _then) = _$SnCloudFileCopyWithImpl; | ||||
| @useResult | ||||
| $Res call({ | ||||
|  String id, String name, String? description, Map<String, dynamic>? fileMeta, Map<String, dynamic>? userMeta, String? mimeType, String? hash, int size, DateTime? uploadedAt, String? uploadedTo, int usedCount, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt | ||||
|  String id, String name, String? description, Map<String, dynamic>? fileMeta, Map<String, dynamic>? userMeta, String? mimeType, String? hash, int size, DateTime? uploadedAt, String? uploadedTo, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt | ||||
| }); | ||||
|  | ||||
|  | ||||
| @@ -196,7 +196,7 @@ class _$SnCloudFileCopyWithImpl<$Res> | ||||
|  | ||||
| /// Create a copy of SnCloudFile | ||||
| /// 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 = freezed,Object? fileMeta = freezed,Object? userMeta = freezed,Object? mimeType = freezed,Object? hash = freezed,Object? size = null,Object? uploadedAt = freezed,Object? uploadedTo = freezed,Object? usedCount = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) { | ||||
| @pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? name = null,Object? description = freezed,Object? fileMeta = freezed,Object? userMeta = freezed,Object? mimeType = freezed,Object? hash = freezed,Object? size = null,Object? uploadedAt = freezed,Object? uploadedTo = freezed,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,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable | ||||
| @@ -208,8 +208,7 @@ as String?,hash: freezed == hash ? _self.hash : hash // ignore: cast_nullable_to | ||||
| as String?,size: null == size ? _self.size : size // ignore: cast_nullable_to_non_nullable | ||||
| as int,uploadedAt: freezed == uploadedAt ? _self.uploadedAt : uploadedAt // ignore: cast_nullable_to_non_nullable | ||||
| as DateTime?,uploadedTo: freezed == uploadedTo ? _self.uploadedTo : uploadedTo // ignore: cast_nullable_to_non_nullable | ||||
| as String?,usedCount: null == usedCount ? _self.usedCount : usedCount // ignore: cast_nullable_to_non_nullable | ||||
| as int,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable | ||||
| as String?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable | ||||
| as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable | ||||
| as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable | ||||
| as DateTime?, | ||||
| @@ -223,7 +222,7 @@ as DateTime?, | ||||
| @JsonSerializable() | ||||
|  | ||||
| class _SnCloudFile implements SnCloudFile { | ||||
|   const _SnCloudFile({required this.id, required this.name, required this.description, required final  Map<String, dynamic>? fileMeta, required final  Map<String, dynamic>? userMeta, required this.mimeType, required this.hash, required this.size, required this.uploadedAt, required this.uploadedTo, required this.usedCount, required this.createdAt, required this.updatedAt, required this.deletedAt}): _fileMeta = fileMeta,_userMeta = userMeta; | ||||
|   const _SnCloudFile({required this.id, required this.name, required this.description, required final  Map<String, dynamic>? fileMeta, required final  Map<String, dynamic>? userMeta, required this.mimeType, required this.hash, required this.size, required this.uploadedAt, required this.uploadedTo, required this.createdAt, required this.updatedAt, required this.deletedAt}): _fileMeta = fileMeta,_userMeta = userMeta; | ||||
|   factory _SnCloudFile.fromJson(Map<String, dynamic> json) => _$SnCloudFileFromJson(json); | ||||
|  | ||||
| @override final  String id; | ||||
| @@ -252,7 +251,6 @@ class _SnCloudFile implements SnCloudFile { | ||||
| @override final  int size; | ||||
| @override final  DateTime? uploadedAt; | ||||
| @override final  String? uploadedTo; | ||||
| @override final  int usedCount; | ||||
| @override final  DateTime createdAt; | ||||
| @override final  DateTime updatedAt; | ||||
| @override final  DateTime? deletedAt; | ||||
| @@ -270,16 +268,16 @@ Map<String, dynamic> toJson() { | ||||
|  | ||||
| @override | ||||
| bool operator ==(Object other) { | ||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnCloudFile&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&const DeepCollectionEquality().equals(other._fileMeta, _fileMeta)&&const DeepCollectionEquality().equals(other._userMeta, _userMeta)&&(identical(other.mimeType, mimeType) || other.mimeType == mimeType)&&(identical(other.hash, hash) || other.hash == hash)&&(identical(other.size, size) || other.size == size)&&(identical(other.uploadedAt, uploadedAt) || other.uploadedAt == uploadedAt)&&(identical(other.uploadedTo, uploadedTo) || other.uploadedTo == uploadedTo)&&(identical(other.usedCount, usedCount) || other.usedCount == usedCount)&&(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 _SnCloudFile&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&const DeepCollectionEquality().equals(other._fileMeta, _fileMeta)&&const DeepCollectionEquality().equals(other._userMeta, _userMeta)&&(identical(other.mimeType, mimeType) || other.mimeType == mimeType)&&(identical(other.hash, hash) || other.hash == hash)&&(identical(other.size, size) || other.size == size)&&(identical(other.uploadedAt, uploadedAt) || other.uploadedAt == uploadedAt)&&(identical(other.uploadedTo, uploadedTo) || other.uploadedTo == uploadedTo)&&(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,name,description,const DeepCollectionEquality().hash(_fileMeta),const DeepCollectionEquality().hash(_userMeta),mimeType,hash,size,uploadedAt,uploadedTo,usedCount,createdAt,updatedAt,deletedAt); | ||||
| int get hashCode => Object.hash(runtimeType,id,name,description,const DeepCollectionEquality().hash(_fileMeta),const DeepCollectionEquality().hash(_userMeta),mimeType,hash,size,uploadedAt,uploadedTo,createdAt,updatedAt,deletedAt); | ||||
|  | ||||
| @override | ||||
| String toString() { | ||||
|   return 'SnCloudFile(id: $id, name: $name, description: $description, fileMeta: $fileMeta, userMeta: $userMeta, mimeType: $mimeType, hash: $hash, size: $size, uploadedAt: $uploadedAt, uploadedTo: $uploadedTo, usedCount: $usedCount, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; | ||||
|   return 'SnCloudFile(id: $id, name: $name, description: $description, fileMeta: $fileMeta, userMeta: $userMeta, mimeType: $mimeType, hash: $hash, size: $size, uploadedAt: $uploadedAt, uploadedTo: $uploadedTo, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; | ||||
| } | ||||
|  | ||||
|  | ||||
| @@ -290,7 +288,7 @@ abstract mixin class _$SnCloudFileCopyWith<$Res> implements $SnCloudFileCopyWith | ||||
|   factory _$SnCloudFileCopyWith(_SnCloudFile value, $Res Function(_SnCloudFile) _then) = __$SnCloudFileCopyWithImpl; | ||||
| @override @useResult | ||||
| $Res call({ | ||||
|  String id, String name, String? description, Map<String, dynamic>? fileMeta, Map<String, dynamic>? userMeta, String? mimeType, String? hash, int size, DateTime? uploadedAt, String? uploadedTo, int usedCount, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt | ||||
|  String id, String name, String? description, Map<String, dynamic>? fileMeta, Map<String, dynamic>? userMeta, String? mimeType, String? hash, int size, DateTime? uploadedAt, String? uploadedTo, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt | ||||
| }); | ||||
|  | ||||
|  | ||||
| @@ -307,7 +305,7 @@ class __$SnCloudFileCopyWithImpl<$Res> | ||||
|  | ||||
| /// Create a copy of SnCloudFile | ||||
| /// 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 = freezed,Object? fileMeta = freezed,Object? userMeta = freezed,Object? mimeType = freezed,Object? hash = freezed,Object? size = null,Object? uploadedAt = freezed,Object? uploadedTo = freezed,Object? usedCount = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) { | ||||
| @override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? name = null,Object? description = freezed,Object? fileMeta = freezed,Object? userMeta = freezed,Object? mimeType = freezed,Object? hash = freezed,Object? size = null,Object? uploadedAt = freezed,Object? uploadedTo = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) { | ||||
|   return _then(_SnCloudFile( | ||||
| 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 | ||||
| @@ -319,8 +317,7 @@ as String?,hash: freezed == hash ? _self.hash : hash // ignore: cast_nullable_to | ||||
| as String?,size: null == size ? _self.size : size // ignore: cast_nullable_to_non_nullable | ||||
| as int,uploadedAt: freezed == uploadedAt ? _self.uploadedAt : uploadedAt // ignore: cast_nullable_to_non_nullable | ||||
| as DateTime?,uploadedTo: freezed == uploadedTo ? _self.uploadedTo : uploadedTo // ignore: cast_nullable_to_non_nullable | ||||
| as String?,usedCount: null == usedCount ? _self.usedCount : usedCount // ignore: cast_nullable_to_non_nullable | ||||
| as int,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable | ||||
| as String?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable | ||||
| as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable | ||||
| as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable | ||||
| as DateTime?, | ||||
|   | ||||
| @@ -20,7 +20,6 @@ _SnCloudFile _$SnCloudFileFromJson(Map<String, dynamic> json) => _SnCloudFile( | ||||
|           ? null | ||||
|           : DateTime.parse(json['uploaded_at'] as String), | ||||
|   uploadedTo: json['uploaded_to'] as String?, | ||||
|   usedCount: (json['used_count'] as num).toInt(), | ||||
|   createdAt: DateTime.parse(json['created_at'] as String), | ||||
|   updatedAt: DateTime.parse(json['updated_at'] as String), | ||||
|   deletedAt: | ||||
| @@ -41,7 +40,6 @@ Map<String, dynamic> _$SnCloudFileToJson(_SnCloudFile instance) => | ||||
|       'size': instance.size, | ||||
|       'uploaded_at': instance.uploadedAt?.toIso8601String(), | ||||
|       'uploaded_to': instance.uploadedTo, | ||||
|       'used_count': instance.usedCount, | ||||
|       'created_at': instance.createdAt.toIso8601String(), | ||||
|       'updated_at': instance.updatedAt.toIso8601String(), | ||||
|       'deleted_at': instance.deletedAt?.toIso8601String(), | ||||
|   | ||||
| @@ -1,11 +1,12 @@ | ||||
| import 'package:freezed_annotation/freezed_annotation.dart'; | ||||
| import 'package:island/models/file.dart'; | ||||
| import 'package:island/models/user.dart'; | ||||
|  | ||||
| part 'post.freezed.dart'; | ||||
| part 'post.g.dart'; | ||||
|  | ||||
| @freezed | ||||
| abstract class SnPost with _$SnPost { | ||||
| sealed class SnPost with _$SnPost { | ||||
|   const factory SnPost({ | ||||
|     required String id, | ||||
|     required String? title, | ||||
| @@ -21,6 +22,7 @@ abstract class SnPost with _$SnPost { | ||||
|     required int viewsTotal, | ||||
|     required int upvotes, | ||||
|     required int downvotes, | ||||
|     required int repliesCount, | ||||
|     required String? threadedPostId, | ||||
|     required SnPost? threadedPost, | ||||
|     required String? repliedPostId, | ||||
| @@ -43,22 +45,22 @@ abstract class SnPost with _$SnPost { | ||||
| } | ||||
|  | ||||
| @freezed | ||||
| abstract class SnPublisher with _$SnPublisher { | ||||
| sealed class SnPublisher with _$SnPublisher { | ||||
|   const factory SnPublisher({ | ||||
|     required String id, | ||||
|     required int type, | ||||
|     required String name, | ||||
|     required String nick, | ||||
|     @Default('') String bio, | ||||
|     required String? pictureId, | ||||
|     required SnCloudFile? picture, | ||||
|     required String? backgroundId, | ||||
|     required SnCloudFile? background, | ||||
|     required SnAccount? account, | ||||
|     required String? accountId, | ||||
|     required DateTime createdAt, | ||||
|     required DateTime updatedAt, | ||||
|     required DateTime? deletedAt, | ||||
|     required String? realmId, | ||||
|     required SnVerificationMark? verification, | ||||
|   }) = _SnPublisher; | ||||
|  | ||||
|   factory SnPublisher.fromJson(Map<String, dynamic> json) => | ||||
| @@ -66,7 +68,7 @@ abstract class SnPublisher with _$SnPublisher { | ||||
| } | ||||
|  | ||||
| @freezed | ||||
| abstract class SnPublisherStats with _$SnPublisherStats { | ||||
| sealed class SnPublisherStats with _$SnPublisherStats { | ||||
|   const factory SnPublisherStats({ | ||||
|     required int postsCreated, | ||||
|     required int stickerPacksCreated, | ||||
| @@ -80,7 +82,7 @@ abstract class SnPublisherStats with _$SnPublisherStats { | ||||
| } | ||||
|  | ||||
| @freezed | ||||
| abstract class SnSubscriptionStatus with _$SnSubscriptionStatus { | ||||
| sealed class SnSubscriptionStatus with _$SnSubscriptionStatus { | ||||
|   const factory SnSubscriptionStatus({ | ||||
|     required bool isSubscribed, | ||||
|     required int publisherId, | ||||
| @@ -92,7 +94,7 @@ abstract class SnSubscriptionStatus with _$SnSubscriptionStatus { | ||||
| } | ||||
|  | ||||
| @freezed | ||||
| abstract class ReactInfo with _$ReactInfo { | ||||
| sealed class ReactInfo with _$ReactInfo { | ||||
|   const factory ReactInfo({required String icon, required int attitude}) = | ||||
|       _ReactInfo; | ||||
| } | ||||
|   | ||||
| @@ -16,7 +16,7 @@ T _$identity<T>(T value) => value; | ||||
| /// @nodoc | ||||
| 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; 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; List<dynamic> get reactions; List<dynamic> get tags; List<dynamic> get categories; List<dynamic> get collections; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; | ||||
|  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; List<dynamic> get reactions; List<dynamic> get tags; List<dynamic> get categories; List<dynamic> get collections; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; | ||||
| /// Create a copy of SnPost | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @JsonKey(includeFromJson: false, includeToJson: false) | ||||
| @@ -29,16 +29,16 @@ $SnPostCopyWith<SnPost> get copyWith => _$SnPostCopyWithImpl<SnPost>(this as SnP | ||||
|  | ||||
| @override | ||||
| bool operator ==(Object other) { | ||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is SnPost&&(identical(other.id, id) || other.id == id)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)&&(identical(other.language, language) || other.language == language)&&(identical(other.editedAt, editedAt) || other.editedAt == editedAt)&&(identical(other.publishedAt, publishedAt) || other.publishedAt == publishedAt)&&(identical(other.visibility, visibility) || other.visibility == visibility)&&(identical(other.content, content) || other.content == content)&&(identical(other.type, type) || other.type == type)&&const DeepCollectionEquality().equals(other.meta, meta)&&(identical(other.viewsUnique, viewsUnique) || other.viewsUnique == viewsUnique)&&(identical(other.viewsTotal, viewsTotal) || other.viewsTotal == viewsTotal)&&(identical(other.upvotes, upvotes) || other.upvotes == upvotes)&&(identical(other.downvotes, downvotes) || other.downvotes == downvotes)&&(identical(other.threadedPostId, threadedPostId) || other.threadedPostId == threadedPostId)&&(identical(other.threadedPost, threadedPost) || other.threadedPost == threadedPost)&&(identical(other.repliedPostId, repliedPostId) || other.repliedPostId == repliedPostId)&&(identical(other.repliedPost, repliedPost) || other.repliedPost == repliedPost)&&(identical(other.forwardedPostId, forwardedPostId) || other.forwardedPostId == forwardedPostId)&&(identical(other.forwardedPost, forwardedPost) || other.forwardedPost == forwardedPost)&&const DeepCollectionEquality().equals(other.attachments, attachments)&&(identical(other.publisher, publisher) || other.publisher == publisher)&&const DeepCollectionEquality().equals(other.reactionsCount, reactionsCount)&&const DeepCollectionEquality().equals(other.reactions, reactions)&&const DeepCollectionEquality().equals(other.tags, tags)&&const DeepCollectionEquality().equals(other.categories, categories)&&const DeepCollectionEquality().equals(other.collections, collections)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)); | ||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is SnPost&&(identical(other.id, id) || other.id == id)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)&&(identical(other.language, language) || other.language == language)&&(identical(other.editedAt, editedAt) || other.editedAt == editedAt)&&(identical(other.publishedAt, publishedAt) || other.publishedAt == publishedAt)&&(identical(other.visibility, visibility) || other.visibility == visibility)&&(identical(other.content, content) || other.content == content)&&(identical(other.type, type) || other.type == type)&&const DeepCollectionEquality().equals(other.meta, meta)&&(identical(other.viewsUnique, viewsUnique) || other.viewsUnique == viewsUnique)&&(identical(other.viewsTotal, viewsTotal) || other.viewsTotal == viewsTotal)&&(identical(other.upvotes, upvotes) || other.upvotes == upvotes)&&(identical(other.downvotes, downvotes) || other.downvotes == downvotes)&&(identical(other.repliesCount, repliesCount) || other.repliesCount == repliesCount)&&(identical(other.threadedPostId, threadedPostId) || other.threadedPostId == threadedPostId)&&(identical(other.threadedPost, threadedPost) || other.threadedPost == threadedPost)&&(identical(other.repliedPostId, repliedPostId) || other.repliedPostId == repliedPostId)&&(identical(other.repliedPost, repliedPost) || other.repliedPost == repliedPost)&&(identical(other.forwardedPostId, forwardedPostId) || other.forwardedPostId == forwardedPostId)&&(identical(other.forwardedPost, forwardedPost) || other.forwardedPost == forwardedPost)&&const DeepCollectionEquality().equals(other.attachments, attachments)&&(identical(other.publisher, publisher) || other.publisher == publisher)&&const DeepCollectionEquality().equals(other.reactionsCount, reactionsCount)&&const DeepCollectionEquality().equals(other.reactions, reactions)&&const DeepCollectionEquality().equals(other.tags, tags)&&const DeepCollectionEquality().equals(other.categories, categories)&&const DeepCollectionEquality().equals(other.collections, collections)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)); | ||||
| } | ||||
|  | ||||
| @JsonKey(includeFromJson: false, includeToJson: false) | ||||
| @override | ||||
| int get hashCode => Object.hashAll([runtimeType,id,title,description,language,editedAt,publishedAt,visibility,content,type,const DeepCollectionEquality().hash(meta),viewsUnique,viewsTotal,upvotes,downvotes,threadedPostId,threadedPost,repliedPostId,repliedPost,forwardedPostId,forwardedPost,const DeepCollectionEquality().hash(attachments),publisher,const DeepCollectionEquality().hash(reactionsCount),const DeepCollectionEquality().hash(reactions),const DeepCollectionEquality().hash(tags),const DeepCollectionEquality().hash(categories),const DeepCollectionEquality().hash(collections),createdAt,updatedAt,deletedAt]); | ||||
| int get hashCode => Object.hashAll([runtimeType,id,title,description,language,editedAt,publishedAt,visibility,content,type,const DeepCollectionEquality().hash(meta),viewsUnique,viewsTotal,upvotes,downvotes,repliesCount,threadedPostId,threadedPost,repliedPostId,repliedPost,forwardedPostId,forwardedPost,const DeepCollectionEquality().hash(attachments),publisher,const DeepCollectionEquality().hash(reactionsCount),const DeepCollectionEquality().hash(reactions),const DeepCollectionEquality().hash(tags),const DeepCollectionEquality().hash(categories),const DeepCollectionEquality().hash(collections),createdAt,updatedAt,deletedAt]); | ||||
|  | ||||
| @override | ||||
| String toString() { | ||||
|   return 'SnPost(id: $id, title: $title, description: $description, language: $language, editedAt: $editedAt, publishedAt: $publishedAt, visibility: $visibility, content: $content, type: $type, meta: $meta, viewsUnique: $viewsUnique, viewsTotal: $viewsTotal, upvotes: $upvotes, downvotes: $downvotes, threadedPostId: $threadedPostId, threadedPost: $threadedPost, repliedPostId: $repliedPostId, repliedPost: $repliedPost, forwardedPostId: $forwardedPostId, forwardedPost: $forwardedPost, attachments: $attachments, publisher: $publisher, reactionsCount: $reactionsCount, reactions: $reactions, tags: $tags, categories: $categories, collections: $collections, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; | ||||
|   return 'SnPost(id: $id, title: $title, description: $description, language: $language, editedAt: $editedAt, publishedAt: $publishedAt, visibility: $visibility, content: $content, type: $type, meta: $meta, viewsUnique: $viewsUnique, viewsTotal: $viewsTotal, upvotes: $upvotes, downvotes: $downvotes, repliesCount: $repliesCount, threadedPostId: $threadedPostId, threadedPost: $threadedPost, repliedPostId: $repliedPostId, repliedPost: $repliedPost, forwardedPostId: $forwardedPostId, forwardedPost: $forwardedPost, attachments: $attachments, publisher: $publisher, reactionsCount: $reactionsCount, reactions: $reactions, tags: $tags, categories: $categories, collections: $collections, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; | ||||
| } | ||||
|  | ||||
|  | ||||
| @@ -49,7 +49,7 @@ abstract mixin class $SnPostCopyWith<$Res>  { | ||||
|   factory $SnPostCopyWith(SnPost value, $Res Function(SnPost) _then) = _$SnPostCopyWithImpl; | ||||
| @useResult | ||||
| $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, String? threadedPostId, SnPost? threadedPost, String? repliedPostId, SnPost? repliedPost, String? forwardedPostId, SnPost? forwardedPost, List<SnCloudFile> attachments, SnPublisher publisher, Map<String, int> reactionsCount, List<dynamic> reactions, List<dynamic> tags, List<dynamic> categories, List<dynamic> collections, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt | ||||
|  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, List<dynamic> reactions, List<dynamic> tags, List<dynamic> categories, List<dynamic> collections, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt | ||||
| }); | ||||
|  | ||||
|  | ||||
| @@ -66,7 +66,7 @@ class _$SnPostCopyWithImpl<$Res> | ||||
|  | ||||
| /// Create a copy of SnPost | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? title = freezed,Object? description = freezed,Object? language = freezed,Object? editedAt = freezed,Object? publishedAt = null,Object? visibility = null,Object? content = freezed,Object? type = null,Object? meta = freezed,Object? viewsUnique = null,Object? viewsTotal = null,Object? upvotes = null,Object? downvotes = null,Object? threadedPostId = freezed,Object? threadedPost = freezed,Object? repliedPostId = freezed,Object? repliedPost = freezed,Object? forwardedPostId = freezed,Object? forwardedPost = freezed,Object? attachments = null,Object? publisher = null,Object? reactionsCount = null,Object? reactions = null,Object? tags = null,Object? categories = null,Object? collections = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) { | ||||
| @pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? title = freezed,Object? description = freezed,Object? language = freezed,Object? editedAt = freezed,Object? publishedAt = null,Object? visibility = null,Object? content = freezed,Object? type = null,Object? meta = freezed,Object? viewsUnique = null,Object? viewsTotal = null,Object? upvotes = null,Object? downvotes = null,Object? repliesCount = null,Object? threadedPostId = freezed,Object? threadedPost = freezed,Object? repliedPostId = freezed,Object? repliedPost = freezed,Object? forwardedPostId = freezed,Object? forwardedPost = freezed,Object? attachments = null,Object? publisher = null,Object? reactionsCount = null,Object? reactions = null,Object? tags = null,Object? categories = null,Object? collections = 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,title: freezed == title ? _self.title : title // ignore: cast_nullable_to_non_nullable | ||||
| @@ -82,6 +82,7 @@ as Map<String, dynamic>?,viewsUnique: null == viewsUnique ? _self.viewsUnique : | ||||
| as int,viewsTotal: null == viewsTotal ? _self.viewsTotal : viewsTotal // ignore: cast_nullable_to_non_nullable | ||||
| as int,upvotes: null == upvotes ? _self.upvotes : upvotes // ignore: cast_nullable_to_non_nullable | ||||
| as int,downvotes: null == downvotes ? _self.downvotes : downvotes // ignore: cast_nullable_to_non_nullable | ||||
| as int,repliesCount: null == repliesCount ? _self.repliesCount : repliesCount // ignore: cast_nullable_to_non_nullable | ||||
| as int,threadedPostId: freezed == threadedPostId ? _self.threadedPostId : threadedPostId // ignore: cast_nullable_to_non_nullable | ||||
| as String?,threadedPost: freezed == threadedPost ? _self.threadedPost : threadedPost // ignore: cast_nullable_to_non_nullable | ||||
| as SnPost?,repliedPostId: freezed == repliedPostId ? _self.repliedPostId : repliedPostId // ignore: cast_nullable_to_non_nullable | ||||
| @@ -154,7 +155,7 @@ $SnPublisherCopyWith<$Res> get publisher { | ||||
| @JsonSerializable() | ||||
|  | ||||
| class _SnPost implements SnPost { | ||||
|   const _SnPost({required this.id, required this.title, required this.description, required this.language, required this.editedAt, required this.publishedAt, required this.visibility, required this.content, required this.type, required final  Map<String, dynamic>? meta, required this.viewsUnique, required this.viewsTotal, required this.upvotes, required this.downvotes, required this.threadedPostId, required this.threadedPost, required this.repliedPostId, required this.repliedPost, required this.forwardedPostId, required this.forwardedPost, required final  List<SnCloudFile> attachments, required this.publisher, final  Map<String, int> reactionsCount = const {}, required final  List<dynamic> reactions, required final  List<dynamic> tags, required final  List<dynamic> categories, required final  List<dynamic> collections, required this.createdAt, required this.updatedAt, required this.deletedAt}): _meta = meta,_attachments = attachments,_reactionsCount = reactionsCount,_reactions = reactions,_tags = tags,_categories = categories,_collections = collections; | ||||
|   const _SnPost({required this.id, required this.title, required this.description, required this.language, required this.editedAt, required this.publishedAt, required this.visibility, required this.content, required this.type, required final  Map<String, dynamic>? meta, required this.viewsUnique, required this.viewsTotal, required this.upvotes, required this.downvotes, required this.repliesCount, required this.threadedPostId, required this.threadedPost, required this.repliedPostId, required this.repliedPost, required this.forwardedPostId, required this.forwardedPost, required final  List<SnCloudFile> attachments, required this.publisher, final  Map<String, int> reactionsCount = const {}, required final  List<dynamic> reactions, required final  List<dynamic> tags, required final  List<dynamic> categories, required final  List<dynamic> collections, required this.createdAt, required this.updatedAt, required this.deletedAt}): _meta = meta,_attachments = attachments,_reactionsCount = reactionsCount,_reactions = reactions,_tags = tags,_categories = categories,_collections = collections; | ||||
|   factory _SnPost.fromJson(Map<String, dynamic> json) => _$SnPostFromJson(json); | ||||
|  | ||||
| @override final  String id; | ||||
| @@ -179,6 +180,7 @@ class _SnPost implements SnPost { | ||||
| @override final  int viewsTotal; | ||||
| @override final  int upvotes; | ||||
| @override final  int downvotes; | ||||
| @override final  int repliesCount; | ||||
| @override final  String? threadedPostId; | ||||
| @override final  SnPost? threadedPost; | ||||
| @override final  String? repliedPostId; | ||||
| @@ -245,16 +247,16 @@ Map<String, dynamic> toJson() { | ||||
|  | ||||
| @override | ||||
| bool operator ==(Object other) { | ||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnPost&&(identical(other.id, id) || other.id == id)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)&&(identical(other.language, language) || other.language == language)&&(identical(other.editedAt, editedAt) || other.editedAt == editedAt)&&(identical(other.publishedAt, publishedAt) || other.publishedAt == publishedAt)&&(identical(other.visibility, visibility) || other.visibility == visibility)&&(identical(other.content, content) || other.content == content)&&(identical(other.type, type) || other.type == type)&&const DeepCollectionEquality().equals(other._meta, _meta)&&(identical(other.viewsUnique, viewsUnique) || other.viewsUnique == viewsUnique)&&(identical(other.viewsTotal, viewsTotal) || other.viewsTotal == viewsTotal)&&(identical(other.upvotes, upvotes) || other.upvotes == upvotes)&&(identical(other.downvotes, downvotes) || other.downvotes == downvotes)&&(identical(other.threadedPostId, threadedPostId) || other.threadedPostId == threadedPostId)&&(identical(other.threadedPost, threadedPost) || other.threadedPost == threadedPost)&&(identical(other.repliedPostId, repliedPostId) || other.repliedPostId == repliedPostId)&&(identical(other.repliedPost, repliedPost) || other.repliedPost == repliedPost)&&(identical(other.forwardedPostId, forwardedPostId) || other.forwardedPostId == forwardedPostId)&&(identical(other.forwardedPost, forwardedPost) || other.forwardedPost == forwardedPost)&&const DeepCollectionEquality().equals(other._attachments, _attachments)&&(identical(other.publisher, publisher) || other.publisher == publisher)&&const DeepCollectionEquality().equals(other._reactionsCount, _reactionsCount)&&const DeepCollectionEquality().equals(other._reactions, _reactions)&&const DeepCollectionEquality().equals(other._tags, _tags)&&const DeepCollectionEquality().equals(other._categories, _categories)&&const DeepCollectionEquality().equals(other._collections, _collections)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)); | ||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnPost&&(identical(other.id, id) || other.id == id)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)&&(identical(other.language, language) || other.language == language)&&(identical(other.editedAt, editedAt) || other.editedAt == editedAt)&&(identical(other.publishedAt, publishedAt) || other.publishedAt == publishedAt)&&(identical(other.visibility, visibility) || other.visibility == visibility)&&(identical(other.content, content) || other.content == content)&&(identical(other.type, type) || other.type == type)&&const DeepCollectionEquality().equals(other._meta, _meta)&&(identical(other.viewsUnique, viewsUnique) || other.viewsUnique == viewsUnique)&&(identical(other.viewsTotal, viewsTotal) || other.viewsTotal == viewsTotal)&&(identical(other.upvotes, upvotes) || other.upvotes == upvotes)&&(identical(other.downvotes, downvotes) || other.downvotes == downvotes)&&(identical(other.repliesCount, repliesCount) || other.repliesCount == repliesCount)&&(identical(other.threadedPostId, threadedPostId) || other.threadedPostId == threadedPostId)&&(identical(other.threadedPost, threadedPost) || other.threadedPost == threadedPost)&&(identical(other.repliedPostId, repliedPostId) || other.repliedPostId == repliedPostId)&&(identical(other.repliedPost, repliedPost) || other.repliedPost == repliedPost)&&(identical(other.forwardedPostId, forwardedPostId) || other.forwardedPostId == forwardedPostId)&&(identical(other.forwardedPost, forwardedPost) || other.forwardedPost == forwardedPost)&&const DeepCollectionEquality().equals(other._attachments, _attachments)&&(identical(other.publisher, publisher) || other.publisher == publisher)&&const DeepCollectionEquality().equals(other._reactionsCount, _reactionsCount)&&const DeepCollectionEquality().equals(other._reactions, _reactions)&&const DeepCollectionEquality().equals(other._tags, _tags)&&const DeepCollectionEquality().equals(other._categories, _categories)&&const DeepCollectionEquality().equals(other._collections, _collections)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)); | ||||
| } | ||||
|  | ||||
| @JsonKey(includeFromJson: false, includeToJson: false) | ||||
| @override | ||||
| int get hashCode => Object.hashAll([runtimeType,id,title,description,language,editedAt,publishedAt,visibility,content,type,const DeepCollectionEquality().hash(_meta),viewsUnique,viewsTotal,upvotes,downvotes,threadedPostId,threadedPost,repliedPostId,repliedPost,forwardedPostId,forwardedPost,const DeepCollectionEquality().hash(_attachments),publisher,const DeepCollectionEquality().hash(_reactionsCount),const DeepCollectionEquality().hash(_reactions),const DeepCollectionEquality().hash(_tags),const DeepCollectionEquality().hash(_categories),const DeepCollectionEquality().hash(_collections),createdAt,updatedAt,deletedAt]); | ||||
| int get hashCode => Object.hashAll([runtimeType,id,title,description,language,editedAt,publishedAt,visibility,content,type,const DeepCollectionEquality().hash(_meta),viewsUnique,viewsTotal,upvotes,downvotes,repliesCount,threadedPostId,threadedPost,repliedPostId,repliedPost,forwardedPostId,forwardedPost,const DeepCollectionEquality().hash(_attachments),publisher,const DeepCollectionEquality().hash(_reactionsCount),const DeepCollectionEquality().hash(_reactions),const DeepCollectionEquality().hash(_tags),const DeepCollectionEquality().hash(_categories),const DeepCollectionEquality().hash(_collections),createdAt,updatedAt,deletedAt]); | ||||
|  | ||||
| @override | ||||
| String toString() { | ||||
|   return 'SnPost(id: $id, title: $title, description: $description, language: $language, editedAt: $editedAt, publishedAt: $publishedAt, visibility: $visibility, content: $content, type: $type, meta: $meta, viewsUnique: $viewsUnique, viewsTotal: $viewsTotal, upvotes: $upvotes, downvotes: $downvotes, threadedPostId: $threadedPostId, threadedPost: $threadedPost, repliedPostId: $repliedPostId, repliedPost: $repliedPost, forwardedPostId: $forwardedPostId, forwardedPost: $forwardedPost, attachments: $attachments, publisher: $publisher, reactionsCount: $reactionsCount, reactions: $reactions, tags: $tags, categories: $categories, collections: $collections, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; | ||||
|   return 'SnPost(id: $id, title: $title, description: $description, language: $language, editedAt: $editedAt, publishedAt: $publishedAt, visibility: $visibility, content: $content, type: $type, meta: $meta, viewsUnique: $viewsUnique, viewsTotal: $viewsTotal, upvotes: $upvotes, downvotes: $downvotes, repliesCount: $repliesCount, threadedPostId: $threadedPostId, threadedPost: $threadedPost, repliedPostId: $repliedPostId, repliedPost: $repliedPost, forwardedPostId: $forwardedPostId, forwardedPost: $forwardedPost, attachments: $attachments, publisher: $publisher, reactionsCount: $reactionsCount, reactions: $reactions, tags: $tags, categories: $categories, collections: $collections, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; | ||||
| } | ||||
|  | ||||
|  | ||||
| @@ -265,7 +267,7 @@ abstract mixin class _$SnPostCopyWith<$Res> implements $SnPostCopyWith<$Res> { | ||||
|   factory _$SnPostCopyWith(_SnPost value, $Res Function(_SnPost) _then) = __$SnPostCopyWithImpl; | ||||
| @override @useResult | ||||
| $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, String? threadedPostId, SnPost? threadedPost, String? repliedPostId, SnPost? repliedPost, String? forwardedPostId, SnPost? forwardedPost, List<SnCloudFile> attachments, SnPublisher publisher, Map<String, int> reactionsCount, List<dynamic> reactions, List<dynamic> tags, List<dynamic> categories, List<dynamic> collections, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt | ||||
|  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, List<dynamic> reactions, List<dynamic> tags, List<dynamic> categories, List<dynamic> collections, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt | ||||
| }); | ||||
|  | ||||
|  | ||||
| @@ -282,7 +284,7 @@ class __$SnPostCopyWithImpl<$Res> | ||||
|  | ||||
| /// Create a copy of SnPost | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? title = freezed,Object? description = freezed,Object? language = freezed,Object? editedAt = freezed,Object? publishedAt = null,Object? visibility = null,Object? content = freezed,Object? type = null,Object? meta = freezed,Object? viewsUnique = null,Object? viewsTotal = null,Object? upvotes = null,Object? downvotes = null,Object? threadedPostId = freezed,Object? threadedPost = freezed,Object? repliedPostId = freezed,Object? repliedPost = freezed,Object? forwardedPostId = freezed,Object? forwardedPost = freezed,Object? attachments = null,Object? publisher = null,Object? reactionsCount = null,Object? reactions = null,Object? tags = null,Object? categories = null,Object? collections = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) { | ||||
| @override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? title = freezed,Object? description = freezed,Object? language = freezed,Object? editedAt = freezed,Object? publishedAt = null,Object? visibility = null,Object? content = freezed,Object? type = null,Object? meta = freezed,Object? viewsUnique = null,Object? viewsTotal = null,Object? upvotes = null,Object? downvotes = null,Object? repliesCount = null,Object? threadedPostId = freezed,Object? threadedPost = freezed,Object? repliedPostId = freezed,Object? repliedPost = freezed,Object? forwardedPostId = freezed,Object? forwardedPost = freezed,Object? attachments = null,Object? publisher = null,Object? reactionsCount = null,Object? reactions = null,Object? tags = null,Object? categories = null,Object? collections = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) { | ||||
|   return _then(_SnPost( | ||||
| id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable | ||||
| as String,title: freezed == title ? _self.title : title // ignore: cast_nullable_to_non_nullable | ||||
| @@ -298,6 +300,7 @@ as Map<String, dynamic>?,viewsUnique: null == viewsUnique ? _self.viewsUnique : | ||||
| as int,viewsTotal: null == viewsTotal ? _self.viewsTotal : viewsTotal // ignore: cast_nullable_to_non_nullable | ||||
| as int,upvotes: null == upvotes ? _self.upvotes : upvotes // ignore: cast_nullable_to_non_nullable | ||||
| as int,downvotes: null == downvotes ? _self.downvotes : downvotes // ignore: cast_nullable_to_non_nullable | ||||
| as int,repliesCount: null == repliesCount ? _self.repliesCount : repliesCount // ignore: cast_nullable_to_non_nullable | ||||
| as int,threadedPostId: freezed == threadedPostId ? _self.threadedPostId : threadedPostId // ignore: cast_nullable_to_non_nullable | ||||
| as String?,threadedPost: freezed == threadedPost ? _self.threadedPost : threadedPost // ignore: cast_nullable_to_non_nullable | ||||
| as SnPost?,repliedPostId: freezed == repliedPostId ? _self.repliedPostId : repliedPostId // ignore: cast_nullable_to_non_nullable | ||||
| @@ -370,7 +373,7 @@ $SnPublisherCopyWith<$Res> get publisher { | ||||
| /// @nodoc | ||||
| mixin _$SnPublisher { | ||||
|  | ||||
|  String get id; int get type; String get name; String get nick; String get bio; String? get pictureId; SnCloudFile? get picture; String? get backgroundId; SnCloudFile? get background; String? get accountId; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; String? get realmId; | ||||
|  String get id; int get type; String get name; String get nick; String get bio; SnCloudFile? get picture; SnCloudFile? get background; SnAccount? get account; String? get accountId; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; String? get realmId; SnVerificationMark? get verification; | ||||
| /// Create a copy of SnPublisher | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @JsonKey(includeFromJson: false, includeToJson: false) | ||||
| @@ -383,16 +386,16 @@ $SnPublisherCopyWith<SnPublisher> get copyWith => _$SnPublisherCopyWithImpl<SnPu | ||||
|  | ||||
| @override | ||||
| bool operator ==(Object other) { | ||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is SnPublisher&&(identical(other.id, id) || other.id == id)&&(identical(other.type, type) || other.type == type)&&(identical(other.name, name) || other.name == name)&&(identical(other.nick, nick) || other.nick == nick)&&(identical(other.bio, bio) || other.bio == bio)&&(identical(other.pictureId, pictureId) || other.pictureId == pictureId)&&(identical(other.picture, picture) || other.picture == picture)&&(identical(other.backgroundId, backgroundId) || other.backgroundId == backgroundId)&&(identical(other.background, background) || other.background == background)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.realmId, realmId) || other.realmId == realmId)); | ||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is SnPublisher&&(identical(other.id, id) || other.id == id)&&(identical(other.type, type) || other.type == type)&&(identical(other.name, name) || other.name == name)&&(identical(other.nick, nick) || other.nick == nick)&&(identical(other.bio, bio) || other.bio == bio)&&(identical(other.picture, picture) || other.picture == picture)&&(identical(other.background, background) || other.background == background)&&(identical(other.account, account) || other.account == account)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.realmId, realmId) || other.realmId == realmId)&&(identical(other.verification, verification) || other.verification == verification)); | ||||
| } | ||||
|  | ||||
| @JsonKey(includeFromJson: false, includeToJson: false) | ||||
| @override | ||||
| int get hashCode => Object.hash(runtimeType,id,type,name,nick,bio,pictureId,picture,backgroundId,background,accountId,createdAt,updatedAt,deletedAt,realmId); | ||||
| int get hashCode => Object.hash(runtimeType,id,type,name,nick,bio,picture,background,account,accountId,createdAt,updatedAt,deletedAt,realmId,verification); | ||||
|  | ||||
| @override | ||||
| String toString() { | ||||
|   return 'SnPublisher(id: $id, type: $type, name: $name, nick: $nick, bio: $bio, pictureId: $pictureId, picture: $picture, backgroundId: $backgroundId, background: $background, accountId: $accountId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, realmId: $realmId)'; | ||||
|   return 'SnPublisher(id: $id, type: $type, name: $name, nick: $nick, bio: $bio, picture: $picture, background: $background, account: $account, accountId: $accountId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, realmId: $realmId, verification: $verification)'; | ||||
| } | ||||
|  | ||||
|  | ||||
| @@ -403,11 +406,11 @@ abstract mixin class $SnPublisherCopyWith<$Res>  { | ||||
|   factory $SnPublisherCopyWith(SnPublisher value, $Res Function(SnPublisher) _then) = _$SnPublisherCopyWithImpl; | ||||
| @useResult | ||||
| $Res call({ | ||||
|  String id, int type, String name, String nick, String bio, String? pictureId, SnCloudFile? picture, String? backgroundId, SnCloudFile? background, String? accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, String? realmId | ||||
|  String id, int type, String name, String nick, String bio, SnCloudFile? picture, SnCloudFile? background, SnAccount? account, String? accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, String? realmId, SnVerificationMark? verification | ||||
| }); | ||||
|  | ||||
|  | ||||
| $SnCloudFileCopyWith<$Res>? get picture;$SnCloudFileCopyWith<$Res>? get background; | ||||
| $SnCloudFileCopyWith<$Res>? get picture;$SnCloudFileCopyWith<$Res>? get background;$SnAccountCopyWith<$Res>? get account;$SnVerificationMarkCopyWith<$Res>? get verification; | ||||
|  | ||||
| } | ||||
| /// @nodoc | ||||
| @@ -420,23 +423,23 @@ class _$SnPublisherCopyWithImpl<$Res> | ||||
|  | ||||
| /// Create a copy of SnPublisher | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? type = null,Object? name = null,Object? nick = null,Object? bio = null,Object? pictureId = freezed,Object? picture = freezed,Object? backgroundId = freezed,Object? background = freezed,Object? accountId = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? realmId = freezed,}) { | ||||
| @pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? type = null,Object? name = null,Object? nick = null,Object? bio = null,Object? picture = freezed,Object? background = freezed,Object? account = freezed,Object? accountId = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? realmId = freezed,Object? verification = freezed,}) { | ||||
|   return _then(_self.copyWith( | ||||
| id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable | ||||
| as String,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable | ||||
| as int,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable | ||||
| as String,nick: null == nick ? _self.nick : nick // ignore: cast_nullable_to_non_nullable | ||||
| as String,bio: null == bio ? _self.bio : bio // ignore: cast_nullable_to_non_nullable | ||||
| as String,pictureId: freezed == pictureId ? _self.pictureId : pictureId // ignore: cast_nullable_to_non_nullable | ||||
| as String?,picture: freezed == picture ? _self.picture : picture // ignore: cast_nullable_to_non_nullable | ||||
| as SnCloudFile?,backgroundId: freezed == backgroundId ? _self.backgroundId : backgroundId // ignore: cast_nullable_to_non_nullable | ||||
| as String?,background: freezed == background ? _self.background : background // ignore: cast_nullable_to_non_nullable | ||||
| as SnCloudFile?,accountId: freezed == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable | ||||
| as String,picture: freezed == picture ? _self.picture : picture // ignore: cast_nullable_to_non_nullable | ||||
| as SnCloudFile?,background: freezed == background ? _self.background : background // ignore: cast_nullable_to_non_nullable | ||||
| as SnCloudFile?,account: freezed == account ? _self.account : account // ignore: cast_nullable_to_non_nullable | ||||
| as SnAccount?,accountId: freezed == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable | ||||
| as String?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable | ||||
| as 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?,realmId: freezed == realmId ? _self.realmId : realmId // ignore: cast_nullable_to_non_nullable | ||||
| as String?, | ||||
| as String?,verification: freezed == verification ? _self.verification : verification // ignore: cast_nullable_to_non_nullable | ||||
| as SnVerificationMark?, | ||||
|   )); | ||||
| } | ||||
| /// Create a copy of SnPublisher | ||||
| @@ -463,6 +466,30 @@ $SnCloudFileCopyWith<$Res>? get background { | ||||
|   return $SnCloudFileCopyWith<$Res>(_self.background!, (value) { | ||||
|     return _then(_self.copyWith(background: value)); | ||||
|   }); | ||||
| }/// Create a copy of SnPublisher | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @override | ||||
| @pragma('vm:prefer-inline') | ||||
| $SnAccountCopyWith<$Res>? get account { | ||||
|     if (_self.account == null) { | ||||
|     return null; | ||||
|   } | ||||
|  | ||||
|   return $SnAccountCopyWith<$Res>(_self.account!, (value) { | ||||
|     return _then(_self.copyWith(account: value)); | ||||
|   }); | ||||
| }/// Create a copy of SnPublisher | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @override | ||||
| @pragma('vm:prefer-inline') | ||||
| $SnVerificationMarkCopyWith<$Res>? get verification { | ||||
|     if (_self.verification == null) { | ||||
|     return null; | ||||
|   } | ||||
|  | ||||
|   return $SnVerificationMarkCopyWith<$Res>(_self.verification!, (value) { | ||||
|     return _then(_self.copyWith(verification: value)); | ||||
|   }); | ||||
| } | ||||
| } | ||||
|  | ||||
| @@ -471,7 +498,7 @@ $SnCloudFileCopyWith<$Res>? get background { | ||||
| @JsonSerializable() | ||||
|  | ||||
| class _SnPublisher implements SnPublisher { | ||||
|   const _SnPublisher({required this.id, required this.type, required this.name, required this.nick, this.bio = '', required this.pictureId, required this.picture, required this.backgroundId, required this.background, required this.accountId, required this.createdAt, required this.updatedAt, required this.deletedAt, required this.realmId}); | ||||
|   const _SnPublisher({required this.id, required this.type, required this.name, required this.nick, this.bio = '', required this.picture, required this.background, required this.account, required this.accountId, required this.createdAt, required this.updatedAt, required this.deletedAt, required this.realmId, required this.verification}); | ||||
|   factory _SnPublisher.fromJson(Map<String, dynamic> json) => _$SnPublisherFromJson(json); | ||||
|  | ||||
| @override final  String id; | ||||
| @@ -479,15 +506,15 @@ class _SnPublisher implements SnPublisher { | ||||
| @override final  String name; | ||||
| @override final  String nick; | ||||
| @override@JsonKey() final  String bio; | ||||
| @override final  String? pictureId; | ||||
| @override final  SnCloudFile? picture; | ||||
| @override final  String? backgroundId; | ||||
| @override final  SnCloudFile? background; | ||||
| @override final  SnAccount? account; | ||||
| @override final  String? accountId; | ||||
| @override final  DateTime createdAt; | ||||
| @override final  DateTime updatedAt; | ||||
| @override final  DateTime? deletedAt; | ||||
| @override final  String? realmId; | ||||
| @override final  SnVerificationMark? verification; | ||||
|  | ||||
| /// Create a copy of SnPublisher | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @@ -502,16 +529,16 @@ Map<String, dynamic> toJson() { | ||||
|  | ||||
| @override | ||||
| bool operator ==(Object other) { | ||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnPublisher&&(identical(other.id, id) || other.id == id)&&(identical(other.type, type) || other.type == type)&&(identical(other.name, name) || other.name == name)&&(identical(other.nick, nick) || other.nick == nick)&&(identical(other.bio, bio) || other.bio == bio)&&(identical(other.pictureId, pictureId) || other.pictureId == pictureId)&&(identical(other.picture, picture) || other.picture == picture)&&(identical(other.backgroundId, backgroundId) || other.backgroundId == backgroundId)&&(identical(other.background, background) || other.background == background)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.realmId, realmId) || other.realmId == realmId)); | ||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnPublisher&&(identical(other.id, id) || other.id == id)&&(identical(other.type, type) || other.type == type)&&(identical(other.name, name) || other.name == name)&&(identical(other.nick, nick) || other.nick == nick)&&(identical(other.bio, bio) || other.bio == bio)&&(identical(other.picture, picture) || other.picture == picture)&&(identical(other.background, background) || other.background == background)&&(identical(other.account, account) || other.account == account)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.realmId, realmId) || other.realmId == realmId)&&(identical(other.verification, verification) || other.verification == verification)); | ||||
| } | ||||
|  | ||||
| @JsonKey(includeFromJson: false, includeToJson: false) | ||||
| @override | ||||
| int get hashCode => Object.hash(runtimeType,id,type,name,nick,bio,pictureId,picture,backgroundId,background,accountId,createdAt,updatedAt,deletedAt,realmId); | ||||
| int get hashCode => Object.hash(runtimeType,id,type,name,nick,bio,picture,background,account,accountId,createdAt,updatedAt,deletedAt,realmId,verification); | ||||
|  | ||||
| @override | ||||
| String toString() { | ||||
|   return 'SnPublisher(id: $id, type: $type, name: $name, nick: $nick, bio: $bio, pictureId: $pictureId, picture: $picture, backgroundId: $backgroundId, background: $background, accountId: $accountId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, realmId: $realmId)'; | ||||
|   return 'SnPublisher(id: $id, type: $type, name: $name, nick: $nick, bio: $bio, picture: $picture, background: $background, account: $account, accountId: $accountId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, realmId: $realmId, verification: $verification)'; | ||||
| } | ||||
|  | ||||
|  | ||||
| @@ -522,11 +549,11 @@ abstract mixin class _$SnPublisherCopyWith<$Res> implements $SnPublisherCopyWith | ||||
|   factory _$SnPublisherCopyWith(_SnPublisher value, $Res Function(_SnPublisher) _then) = __$SnPublisherCopyWithImpl; | ||||
| @override @useResult | ||||
| $Res call({ | ||||
|  String id, int type, String name, String nick, String bio, String? pictureId, SnCloudFile? picture, String? backgroundId, SnCloudFile? background, String? accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, String? realmId | ||||
|  String id, int type, String name, String nick, String bio, SnCloudFile? picture, SnCloudFile? background, SnAccount? account, String? accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, String? realmId, SnVerificationMark? verification | ||||
| }); | ||||
|  | ||||
|  | ||||
| @override $SnCloudFileCopyWith<$Res>? get picture;@override $SnCloudFileCopyWith<$Res>? get background; | ||||
| @override $SnCloudFileCopyWith<$Res>? get picture;@override $SnCloudFileCopyWith<$Res>? get background;@override $SnAccountCopyWith<$Res>? get account;@override $SnVerificationMarkCopyWith<$Res>? get verification; | ||||
|  | ||||
| } | ||||
| /// @nodoc | ||||
| @@ -539,23 +566,23 @@ class __$SnPublisherCopyWithImpl<$Res> | ||||
|  | ||||
| /// Create a copy of SnPublisher | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? type = null,Object? name = null,Object? nick = null,Object? bio = null,Object? pictureId = freezed,Object? picture = freezed,Object? backgroundId = freezed,Object? background = freezed,Object? accountId = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? realmId = freezed,}) { | ||||
| @override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? type = null,Object? name = null,Object? nick = null,Object? bio = null,Object? picture = freezed,Object? background = freezed,Object? account = freezed,Object? accountId = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? realmId = freezed,Object? verification = freezed,}) { | ||||
|   return _then(_SnPublisher( | ||||
| id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable | ||||
| as String,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable | ||||
| as int,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable | ||||
| as String,nick: null == nick ? _self.nick : nick // ignore: cast_nullable_to_non_nullable | ||||
| as String,bio: null == bio ? _self.bio : bio // ignore: cast_nullable_to_non_nullable | ||||
| as String,pictureId: freezed == pictureId ? _self.pictureId : pictureId // ignore: cast_nullable_to_non_nullable | ||||
| as String?,picture: freezed == picture ? _self.picture : picture // ignore: cast_nullable_to_non_nullable | ||||
| as SnCloudFile?,backgroundId: freezed == backgroundId ? _self.backgroundId : backgroundId // ignore: cast_nullable_to_non_nullable | ||||
| as String?,background: freezed == background ? _self.background : background // ignore: cast_nullable_to_non_nullable | ||||
| as SnCloudFile?,accountId: freezed == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable | ||||
| as String,picture: freezed == picture ? _self.picture : picture // ignore: cast_nullable_to_non_nullable | ||||
| as SnCloudFile?,background: freezed == background ? _self.background : background // ignore: cast_nullable_to_non_nullable | ||||
| as SnCloudFile?,account: freezed == account ? _self.account : account // ignore: cast_nullable_to_non_nullable | ||||
| as SnAccount?,accountId: freezed == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable | ||||
| as String?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable | ||||
| as 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?,realmId: freezed == realmId ? _self.realmId : realmId // ignore: cast_nullable_to_non_nullable | ||||
| as String?, | ||||
| as String?,verification: freezed == verification ? _self.verification : verification // ignore: cast_nullable_to_non_nullable | ||||
| as SnVerificationMark?, | ||||
|   )); | ||||
| } | ||||
|  | ||||
| @@ -583,6 +610,30 @@ $SnCloudFileCopyWith<$Res>? get background { | ||||
|   return $SnCloudFileCopyWith<$Res>(_self.background!, (value) { | ||||
|     return _then(_self.copyWith(background: value)); | ||||
|   }); | ||||
| }/// Create a copy of SnPublisher | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @override | ||||
| @pragma('vm:prefer-inline') | ||||
| $SnAccountCopyWith<$Res>? get account { | ||||
|     if (_self.account == null) { | ||||
|     return null; | ||||
|   } | ||||
|  | ||||
|   return $SnAccountCopyWith<$Res>(_self.account!, (value) { | ||||
|     return _then(_self.copyWith(account: value)); | ||||
|   }); | ||||
| }/// Create a copy of SnPublisher | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @override | ||||
| @pragma('vm:prefer-inline') | ||||
| $SnVerificationMarkCopyWith<$Res>? get verification { | ||||
|     if (_self.verification == null) { | ||||
|     return null; | ||||
|   } | ||||
|  | ||||
|   return $SnVerificationMarkCopyWith<$Res>(_self.verification!, (value) { | ||||
|     return _then(_self.copyWith(verification: value)); | ||||
|   }); | ||||
| } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -24,6 +24,7 @@ _SnPost _$SnPostFromJson(Map<String, dynamic> json) => _SnPost( | ||||
|   viewsTotal: (json['views_total'] as num).toInt(), | ||||
|   upvotes: (json['upvotes'] as num).toInt(), | ||||
|   downvotes: (json['downvotes'] as num).toInt(), | ||||
|   repliesCount: (json['replies_count'] as num).toInt(), | ||||
|   threadedPostId: json['threaded_post_id'] as String?, | ||||
|   threadedPost: | ||||
|       json['threaded_post'] == null | ||||
| @@ -76,6 +77,7 @@ Map<String, dynamic> _$SnPostToJson(_SnPost instance) => <String, dynamic>{ | ||||
|   'views_total': instance.viewsTotal, | ||||
|   'upvotes': instance.upvotes, | ||||
|   'downvotes': instance.downvotes, | ||||
|   'replies_count': instance.repliesCount, | ||||
|   'threaded_post_id': instance.threadedPostId, | ||||
|   'threaded_post': instance.threadedPost?.toJson(), | ||||
|   'replied_post_id': instance.repliedPostId, | ||||
| @@ -100,16 +102,18 @@ _SnPublisher _$SnPublisherFromJson(Map<String, dynamic> json) => _SnPublisher( | ||||
|   name: json['name'] as String, | ||||
|   nick: json['nick'] as String, | ||||
|   bio: json['bio'] as String? ?? '', | ||||
|   pictureId: json['picture_id'] as String?, | ||||
|   picture: | ||||
|       json['picture'] == null | ||||
|           ? null | ||||
|           : SnCloudFile.fromJson(json['picture'] as Map<String, dynamic>), | ||||
|   backgroundId: json['background_id'] as String?, | ||||
|   background: | ||||
|       json['background'] == null | ||||
|           ? null | ||||
|           : SnCloudFile.fromJson(json['background'] as Map<String, dynamic>), | ||||
|   account: | ||||
|       json['account'] == null | ||||
|           ? null | ||||
|           : SnAccount.fromJson(json['account'] as Map<String, dynamic>), | ||||
|   accountId: json['account_id'] as String?, | ||||
|   createdAt: DateTime.parse(json['created_at'] as String), | ||||
|   updatedAt: DateTime.parse(json['updated_at'] as String), | ||||
| @@ -118,6 +122,12 @@ _SnPublisher _$SnPublisherFromJson(Map<String, dynamic> json) => _SnPublisher( | ||||
|           ? null | ||||
|           : DateTime.parse(json['deleted_at'] as String), | ||||
|   realmId: json['realm_id'] as String?, | ||||
|   verification: | ||||
|       json['verification'] == null | ||||
|           ? null | ||||
|           : SnVerificationMark.fromJson( | ||||
|             json['verification'] as Map<String, dynamic>, | ||||
|           ), | ||||
| ); | ||||
|  | ||||
| Map<String, dynamic> _$SnPublisherToJson(_SnPublisher instance) => | ||||
| @@ -127,15 +137,15 @@ Map<String, dynamic> _$SnPublisherToJson(_SnPublisher instance) => | ||||
|       'name': instance.name, | ||||
|       'nick': instance.nick, | ||||
|       'bio': instance.bio, | ||||
|       'picture_id': instance.pictureId, | ||||
|       'picture': instance.picture?.toJson(), | ||||
|       'background_id': instance.backgroundId, | ||||
|       'background': instance.background?.toJson(), | ||||
|       'account': instance.account?.toJson(), | ||||
|       'account_id': instance.accountId, | ||||
|       'created_at': instance.createdAt.toIso8601String(), | ||||
|       'updated_at': instance.updatedAt.toIso8601String(), | ||||
|       'deleted_at': instance.deletedAt?.toIso8601String(), | ||||
|       'realm_id': instance.realmId, | ||||
|       'verification': instance.verification?.toJson(), | ||||
|     }; | ||||
|  | ||||
| _SnPublisherStats _$SnPublisherStatsFromJson(Map<String, dynamic> json) => | ||||
|   | ||||
| @@ -6,7 +6,7 @@ part 'realm.freezed.dart'; | ||||
| part 'realm.g.dart'; | ||||
|  | ||||
| @freezed | ||||
| abstract class SnRealm with _$SnRealm { | ||||
| sealed class SnRealm with _$SnRealm { | ||||
|   const factory SnRealm({ | ||||
|     required String id, | ||||
|     required String slug, | ||||
| @@ -16,9 +16,7 @@ abstract class SnRealm with _$SnRealm { | ||||
|     required DateTime? verifiedAt, | ||||
|     required bool isCommunity, | ||||
|     required bool isPublic, | ||||
|     required String? pictureId, | ||||
|     required SnCloudFile? picture, | ||||
|     required String? backgroundId, | ||||
|     required SnCloudFile? background, | ||||
|     required String accountId, | ||||
|     required DateTime createdAt, | ||||
| @@ -31,7 +29,7 @@ abstract class SnRealm with _$SnRealm { | ||||
| } | ||||
|  | ||||
| @freezed | ||||
| abstract class SnRealmMember with _$SnRealmMember { | ||||
| sealed class SnRealmMember with _$SnRealmMember { | ||||
|   const factory SnRealmMember({ | ||||
|     required String realmId, | ||||
|     required SnRealm? realm, | ||||
|   | ||||
| @@ -16,7 +16,7 @@ T _$identity<T>(T value) => value; | ||||
| /// @nodoc | ||||
| mixin _$SnRealm { | ||||
|  | ||||
|  String get id; String get slug; String get name; String get description; String? get verifiedAs; DateTime? get verifiedAt; bool get isCommunity; bool get isPublic; String? get pictureId; SnCloudFile? get picture; String? get backgroundId; SnCloudFile? get background; String get accountId; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; | ||||
|  String get id; String get slug; String get name; String get description; String? get verifiedAs; DateTime? get verifiedAt; bool get isCommunity; bool get isPublic; SnCloudFile? get picture; SnCloudFile? get background; String get accountId; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; | ||||
| /// Create a copy of SnRealm | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @JsonKey(includeFromJson: false, includeToJson: false) | ||||
| @@ -29,16 +29,16 @@ $SnRealmCopyWith<SnRealm> get copyWith => _$SnRealmCopyWithImpl<SnRealm>(this as | ||||
|  | ||||
| @override | ||||
| bool operator ==(Object other) { | ||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is SnRealm&&(identical(other.id, id) || other.id == id)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&(identical(other.verifiedAs, verifiedAs) || other.verifiedAs == verifiedAs)&&(identical(other.verifiedAt, verifiedAt) || other.verifiedAt == verifiedAt)&&(identical(other.isCommunity, isCommunity) || other.isCommunity == isCommunity)&&(identical(other.isPublic, isPublic) || other.isPublic == isPublic)&&(identical(other.pictureId, pictureId) || other.pictureId == pictureId)&&(identical(other.picture, picture) || other.picture == picture)&&(identical(other.backgroundId, backgroundId) || other.backgroundId == backgroundId)&&(identical(other.background, background) || other.background == background)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)); | ||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is SnRealm&&(identical(other.id, id) || other.id == id)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&(identical(other.verifiedAs, verifiedAs) || other.verifiedAs == verifiedAs)&&(identical(other.verifiedAt, verifiedAt) || other.verifiedAt == verifiedAt)&&(identical(other.isCommunity, isCommunity) || other.isCommunity == isCommunity)&&(identical(other.isPublic, isPublic) || other.isPublic == isPublic)&&(identical(other.picture, picture) || other.picture == picture)&&(identical(other.background, background) || other.background == background)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)); | ||||
| } | ||||
|  | ||||
| @JsonKey(includeFromJson: false, includeToJson: false) | ||||
| @override | ||||
| int get hashCode => Object.hash(runtimeType,id,slug,name,description,verifiedAs,verifiedAt,isCommunity,isPublic,pictureId,picture,backgroundId,background,accountId,createdAt,updatedAt,deletedAt); | ||||
| int get hashCode => Object.hash(runtimeType,id,slug,name,description,verifiedAs,verifiedAt,isCommunity,isPublic,picture,background,accountId,createdAt,updatedAt,deletedAt); | ||||
|  | ||||
| @override | ||||
| String toString() { | ||||
|   return 'SnRealm(id: $id, slug: $slug, name: $name, description: $description, verifiedAs: $verifiedAs, verifiedAt: $verifiedAt, isCommunity: $isCommunity, isPublic: $isPublic, pictureId: $pictureId, picture: $picture, backgroundId: $backgroundId, background: $background, accountId: $accountId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; | ||||
|   return 'SnRealm(id: $id, slug: $slug, name: $name, description: $description, verifiedAs: $verifiedAs, verifiedAt: $verifiedAt, isCommunity: $isCommunity, isPublic: $isPublic, picture: $picture, background: $background, accountId: $accountId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; | ||||
| } | ||||
|  | ||||
|  | ||||
| @@ -49,7 +49,7 @@ abstract mixin class $SnRealmCopyWith<$Res>  { | ||||
|   factory $SnRealmCopyWith(SnRealm value, $Res Function(SnRealm) _then) = _$SnRealmCopyWithImpl; | ||||
| @useResult | ||||
| $Res call({ | ||||
|  String id, String slug, String name, String description, String? verifiedAs, DateTime? verifiedAt, bool isCommunity, bool isPublic, String? pictureId, SnCloudFile? picture, String? backgroundId, SnCloudFile? background, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt | ||||
|  String id, String slug, String name, String description, String? verifiedAs, DateTime? verifiedAt, bool isCommunity, bool isPublic, SnCloudFile? picture, SnCloudFile? background, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt | ||||
| }); | ||||
|  | ||||
|  | ||||
| @@ -66,7 +66,7 @@ class _$SnRealmCopyWithImpl<$Res> | ||||
|  | ||||
| /// Create a copy of SnRealm | ||||
| /// 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 = null,Object? description = null,Object? verifiedAs = freezed,Object? verifiedAt = freezed,Object? isCommunity = null,Object? isPublic = null,Object? pictureId = freezed,Object? picture = freezed,Object? backgroundId = freezed,Object? background = freezed,Object? accountId = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) { | ||||
| @pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? slug = null,Object? name = null,Object? description = null,Object? verifiedAs = freezed,Object? verifiedAt = freezed,Object? isCommunity = null,Object? isPublic = null,Object? picture = freezed,Object? background = freezed,Object? accountId = 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,slug: null == slug ? _self.slug : slug // ignore: cast_nullable_to_non_nullable | ||||
| @@ -76,10 +76,8 @@ as String,verifiedAs: freezed == verifiedAs ? _self.verifiedAs : verifiedAs // i | ||||
| as String?,verifiedAt: freezed == verifiedAt ? _self.verifiedAt : verifiedAt // ignore: cast_nullable_to_non_nullable | ||||
| as DateTime?,isCommunity: null == isCommunity ? _self.isCommunity : isCommunity // ignore: cast_nullable_to_non_nullable | ||||
| as bool,isPublic: null == isPublic ? _self.isPublic : isPublic // ignore: cast_nullable_to_non_nullable | ||||
| as bool,pictureId: freezed == pictureId ? _self.pictureId : pictureId // ignore: cast_nullable_to_non_nullable | ||||
| as String?,picture: freezed == picture ? _self.picture : picture // ignore: cast_nullable_to_non_nullable | ||||
| as SnCloudFile?,backgroundId: freezed == backgroundId ? _self.backgroundId : backgroundId // ignore: cast_nullable_to_non_nullable | ||||
| as String?,background: freezed == background ? _self.background : background // ignore: cast_nullable_to_non_nullable | ||||
| as bool,picture: freezed == picture ? _self.picture : picture // ignore: cast_nullable_to_non_nullable | ||||
| as SnCloudFile?,background: freezed == background ? _self.background : background // ignore: cast_nullable_to_non_nullable | ||||
| as SnCloudFile?,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable | ||||
| as String,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable | ||||
| as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable | ||||
| @@ -119,7 +117,7 @@ $SnCloudFileCopyWith<$Res>? get background { | ||||
| @JsonSerializable() | ||||
|  | ||||
| class _SnRealm implements SnRealm { | ||||
|   const _SnRealm({required this.id, required this.slug, required this.name, required this.description, required this.verifiedAs, required this.verifiedAt, required this.isCommunity, required this.isPublic, required this.pictureId, required this.picture, required this.backgroundId, required this.background, required this.accountId, required this.createdAt, required this.updatedAt, required this.deletedAt}); | ||||
|   const _SnRealm({required this.id, required this.slug, required this.name, required this.description, required this.verifiedAs, required this.verifiedAt, required this.isCommunity, required this.isPublic, required this.picture, required this.background, required this.accountId, required this.createdAt, required this.updatedAt, required this.deletedAt}); | ||||
|   factory _SnRealm.fromJson(Map<String, dynamic> json) => _$SnRealmFromJson(json); | ||||
|  | ||||
| @override final  String id; | ||||
| @@ -130,9 +128,7 @@ class _SnRealm implements SnRealm { | ||||
| @override final  DateTime? verifiedAt; | ||||
| @override final  bool isCommunity; | ||||
| @override final  bool isPublic; | ||||
| @override final  String? pictureId; | ||||
| @override final  SnCloudFile? picture; | ||||
| @override final  String? backgroundId; | ||||
| @override final  SnCloudFile? background; | ||||
| @override final  String accountId; | ||||
| @override final  DateTime createdAt; | ||||
| @@ -152,16 +148,16 @@ Map<String, dynamic> toJson() { | ||||
|  | ||||
| @override | ||||
| bool operator ==(Object other) { | ||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnRealm&&(identical(other.id, id) || other.id == id)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&(identical(other.verifiedAs, verifiedAs) || other.verifiedAs == verifiedAs)&&(identical(other.verifiedAt, verifiedAt) || other.verifiedAt == verifiedAt)&&(identical(other.isCommunity, isCommunity) || other.isCommunity == isCommunity)&&(identical(other.isPublic, isPublic) || other.isPublic == isPublic)&&(identical(other.pictureId, pictureId) || other.pictureId == pictureId)&&(identical(other.picture, picture) || other.picture == picture)&&(identical(other.backgroundId, backgroundId) || other.backgroundId == backgroundId)&&(identical(other.background, background) || other.background == background)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)); | ||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnRealm&&(identical(other.id, id) || other.id == id)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&(identical(other.verifiedAs, verifiedAs) || other.verifiedAs == verifiedAs)&&(identical(other.verifiedAt, verifiedAt) || other.verifiedAt == verifiedAt)&&(identical(other.isCommunity, isCommunity) || other.isCommunity == isCommunity)&&(identical(other.isPublic, isPublic) || other.isPublic == isPublic)&&(identical(other.picture, picture) || other.picture == picture)&&(identical(other.background, background) || other.background == background)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)); | ||||
| } | ||||
|  | ||||
| @JsonKey(includeFromJson: false, includeToJson: false) | ||||
| @override | ||||
| int get hashCode => Object.hash(runtimeType,id,slug,name,description,verifiedAs,verifiedAt,isCommunity,isPublic,pictureId,picture,backgroundId,background,accountId,createdAt,updatedAt,deletedAt); | ||||
| int get hashCode => Object.hash(runtimeType,id,slug,name,description,verifiedAs,verifiedAt,isCommunity,isPublic,picture,background,accountId,createdAt,updatedAt,deletedAt); | ||||
|  | ||||
| @override | ||||
| String toString() { | ||||
|   return 'SnRealm(id: $id, slug: $slug, name: $name, description: $description, verifiedAs: $verifiedAs, verifiedAt: $verifiedAt, isCommunity: $isCommunity, isPublic: $isPublic, pictureId: $pictureId, picture: $picture, backgroundId: $backgroundId, background: $background, accountId: $accountId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; | ||||
|   return 'SnRealm(id: $id, slug: $slug, name: $name, description: $description, verifiedAs: $verifiedAs, verifiedAt: $verifiedAt, isCommunity: $isCommunity, isPublic: $isPublic, picture: $picture, background: $background, accountId: $accountId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; | ||||
| } | ||||
|  | ||||
|  | ||||
| @@ -172,7 +168,7 @@ abstract mixin class _$SnRealmCopyWith<$Res> implements $SnRealmCopyWith<$Res> { | ||||
|   factory _$SnRealmCopyWith(_SnRealm value, $Res Function(_SnRealm) _then) = __$SnRealmCopyWithImpl; | ||||
| @override @useResult | ||||
| $Res call({ | ||||
|  String id, String slug, String name, String description, String? verifiedAs, DateTime? verifiedAt, bool isCommunity, bool isPublic, String? pictureId, SnCloudFile? picture, String? backgroundId, SnCloudFile? background, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt | ||||
|  String id, String slug, String name, String description, String? verifiedAs, DateTime? verifiedAt, bool isCommunity, bool isPublic, SnCloudFile? picture, SnCloudFile? background, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt | ||||
| }); | ||||
|  | ||||
|  | ||||
| @@ -189,7 +185,7 @@ class __$SnRealmCopyWithImpl<$Res> | ||||
|  | ||||
| /// Create a copy of SnRealm | ||||
| /// 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 = null,Object? description = null,Object? verifiedAs = freezed,Object? verifiedAt = freezed,Object? isCommunity = null,Object? isPublic = null,Object? pictureId = freezed,Object? picture = freezed,Object? backgroundId = freezed,Object? background = freezed,Object? accountId = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) { | ||||
| @override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? slug = null,Object? name = null,Object? description = null,Object? verifiedAs = freezed,Object? verifiedAt = freezed,Object? isCommunity = null,Object? isPublic = null,Object? picture = freezed,Object? background = freezed,Object? accountId = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) { | ||||
|   return _then(_SnRealm( | ||||
| 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 | ||||
| @@ -199,10 +195,8 @@ as String,verifiedAs: freezed == verifiedAs ? _self.verifiedAs : verifiedAs // i | ||||
| as String?,verifiedAt: freezed == verifiedAt ? _self.verifiedAt : verifiedAt // ignore: cast_nullable_to_non_nullable | ||||
| as DateTime?,isCommunity: null == isCommunity ? _self.isCommunity : isCommunity // ignore: cast_nullable_to_non_nullable | ||||
| as bool,isPublic: null == isPublic ? _self.isPublic : isPublic // ignore: cast_nullable_to_non_nullable | ||||
| as bool,pictureId: freezed == pictureId ? _self.pictureId : pictureId // ignore: cast_nullable_to_non_nullable | ||||
| as String?,picture: freezed == picture ? _self.picture : picture // ignore: cast_nullable_to_non_nullable | ||||
| as SnCloudFile?,backgroundId: freezed == backgroundId ? _self.backgroundId : backgroundId // ignore: cast_nullable_to_non_nullable | ||||
| as String?,background: freezed == background ? _self.background : background // ignore: cast_nullable_to_non_nullable | ||||
| as bool,picture: freezed == picture ? _self.picture : picture // ignore: cast_nullable_to_non_nullable | ||||
| as SnCloudFile?,background: freezed == background ? _self.background : background // ignore: cast_nullable_to_non_nullable | ||||
| as SnCloudFile?,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable | ||||
| as String,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable | ||||
| as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable | ||||
|   | ||||
| @@ -18,12 +18,10 @@ _SnRealm _$SnRealmFromJson(Map<String, dynamic> json) => _SnRealm( | ||||
|           : DateTime.parse(json['verified_at'] as String), | ||||
|   isCommunity: json['is_community'] as bool, | ||||
|   isPublic: json['is_public'] as bool, | ||||
|   pictureId: json['picture_id'] as String?, | ||||
|   picture: | ||||
|       json['picture'] == null | ||||
|           ? null | ||||
|           : SnCloudFile.fromJson(json['picture'] as Map<String, dynamic>), | ||||
|   backgroundId: json['background_id'] as String?, | ||||
|   background: | ||||
|       json['background'] == null | ||||
|           ? null | ||||
| @@ -46,9 +44,7 @@ Map<String, dynamic> _$SnRealmToJson(_SnRealm instance) => <String, dynamic>{ | ||||
|   'verified_at': instance.verifiedAt?.toIso8601String(), | ||||
|   'is_community': instance.isCommunity, | ||||
|   'is_public': instance.isPublic, | ||||
|   'picture_id': instance.pictureId, | ||||
|   'picture': instance.picture?.toJson(), | ||||
|   'background_id': instance.backgroundId, | ||||
|   'background': instance.background?.toJson(), | ||||
|   'account_id': instance.accountId, | ||||
|   'created_at': instance.createdAt.toIso8601String(), | ||||
|   | ||||
| @@ -6,7 +6,7 @@ part 'relationship.freezed.dart'; | ||||
| part 'relationship.g.dart'; | ||||
|  | ||||
| @freezed | ||||
| abstract class SnRelationship with _$SnRelationship { | ||||
| sealed class SnRelationship with _$SnRelationship { | ||||
|   const factory SnRelationship({ | ||||
|     required DateTime? createdAt, | ||||
|     required DateTime? updatedAt, | ||||
|   | ||||
| @@ -6,7 +6,7 @@ part 'sticker.freezed.dart'; | ||||
| part 'sticker.g.dart'; | ||||
|  | ||||
| @freezed | ||||
| abstract class SnSticker with _$SnSticker { | ||||
| sealed class SnSticker with _$SnSticker { | ||||
|   const factory SnSticker({ | ||||
|     required String id, | ||||
|     required String slug, | ||||
| @@ -24,7 +24,7 @@ abstract class SnSticker with _$SnSticker { | ||||
| } | ||||
|  | ||||
| @freezed | ||||
| abstract class SnStickerPack with _$SnStickerPack { | ||||
| sealed class SnStickerPack with _$SnStickerPack { | ||||
|   const factory SnStickerPack({ | ||||
|     required String id, | ||||
|     required String name, | ||||
|   | ||||
| @@ -5,7 +5,7 @@ part 'user.freezed.dart'; | ||||
| part 'user.g.dart'; | ||||
|  | ||||
| @freezed | ||||
| abstract class SnAccount with _$SnAccount { | ||||
| sealed class SnAccount with _$SnAccount { | ||||
|   const factory SnAccount({ | ||||
|     required String id, | ||||
|     required String name, | ||||
| @@ -24,20 +24,26 @@ abstract class SnAccount with _$SnAccount { | ||||
| } | ||||
|  | ||||
| @freezed | ||||
| abstract class SnAccountProfile with _$SnAccountProfile { | ||||
| sealed class SnAccountProfile with _$SnAccountProfile { | ||||
|   const factory SnAccountProfile({ | ||||
|     required String id, | ||||
|     required String? firstName, | ||||
|     required String? middleName, | ||||
|     required String? lastName, | ||||
|     @Default('') String firstName, | ||||
|     @Default('') String middleName, | ||||
|     @Default('') String lastName, | ||||
|     @Default('') String bio, | ||||
|     required String? pictureId, | ||||
|     @Default('') String gender, | ||||
|     @Default('') String pronouns, | ||||
|     @Default('') String location, | ||||
|     @Default('') String timeZone, | ||||
|     DateTime? birthday, | ||||
|     DateTime? lastSeenAt, | ||||
|     SnAccountBadge? activeBadge, | ||||
|     required int experience, | ||||
|     required int level, | ||||
|     required double levelingProgress, | ||||
|     required SnCloudFile? picture, | ||||
|     required String? backgroundId, | ||||
|     required SnCloudFile? background, | ||||
|     required SnVerificationMark? verification, | ||||
|     required DateTime createdAt, | ||||
|     required DateTime updatedAt, | ||||
|     required DateTime? deletedAt, | ||||
| @@ -48,7 +54,7 @@ abstract class SnAccountProfile with _$SnAccountProfile { | ||||
| } | ||||
|  | ||||
| @freezed | ||||
| abstract class SnAccountStatus with _$SnAccountStatus { | ||||
| sealed class SnAccountStatus with _$SnAccountStatus { | ||||
|   const factory SnAccountStatus({ | ||||
|     required String id, | ||||
|     required int attitude, | ||||
| @@ -69,7 +75,7 @@ abstract class SnAccountStatus with _$SnAccountStatus { | ||||
| } | ||||
|  | ||||
| @freezed | ||||
| abstract class SnAccountBadge with _$SnAccountBadge { | ||||
| sealed class SnAccountBadge with _$SnAccountBadge { | ||||
|   const factory SnAccountBadge({ | ||||
|     required String id, | ||||
|     required String type, | ||||
| @@ -80,6 +86,7 @@ abstract class SnAccountBadge with _$SnAccountBadge { | ||||
|     required String accountId, | ||||
|     required DateTime createdAt, | ||||
|     required DateTime updatedAt, | ||||
|     required DateTime? activatedAt, | ||||
|     required DateTime? deletedAt, | ||||
|   }) = _SnAccountBadge; | ||||
|  | ||||
| @@ -88,7 +95,25 @@ abstract class SnAccountBadge with _$SnAccountBadge { | ||||
| } | ||||
|  | ||||
| @freezed | ||||
| abstract class SnNotification with _$SnNotification { | ||||
| sealed class SnContactMethod with _$SnContactMethod { | ||||
|   const factory SnContactMethod({ | ||||
|     required String id, | ||||
|     required int type, | ||||
|     required DateTime? verifiedAt, | ||||
|     required bool isPrimary, | ||||
|     required String content, | ||||
|     required String accountId, | ||||
|     required DateTime createdAt, | ||||
|     required DateTime updatedAt, | ||||
|     required DateTime? deletedAt, | ||||
|   }) = _SnContactMethod; | ||||
|  | ||||
|   factory SnContactMethod.fromJson(Map<String, dynamic> json) => | ||||
|       _$SnContactMethodFromJson(json); | ||||
| } | ||||
|  | ||||
| @freezed | ||||
| sealed class SnNotification with _$SnNotification { | ||||
|   const factory SnNotification({ | ||||
|     required DateTime createdAt, | ||||
|     required DateTime updatedAt, | ||||
| @@ -107,3 +132,16 @@ abstract class SnNotification with _$SnNotification { | ||||
|   factory SnNotification.fromJson(Map<String, dynamic> json) => | ||||
|       _$SnNotificationFromJson(json); | ||||
| } | ||||
|  | ||||
| @freezed | ||||
| sealed class SnVerificationMark with _$SnVerificationMark { | ||||
|   const factory SnVerificationMark({ | ||||
|     required int type, | ||||
|     required String? title, | ||||
|     required String? description, | ||||
|     required String? verifiedBy, | ||||
|   }) = _SnVerificationMark; | ||||
|  | ||||
|   factory SnVerificationMark.fromJson(Map<String, dynamic> json) => | ||||
|       _$SnVerificationMarkFromJson(json); | ||||
| } | ||||
|   | ||||
| @@ -200,7 +200,7 @@ $SnAccountProfileCopyWith<$Res> get profile { | ||||
| /// @nodoc | ||||
| mixin _$SnAccountProfile { | ||||
|  | ||||
|  String get id; String? get firstName; String? get middleName; String? get lastName; String get bio; String? get pictureId; int get experience; int get level; double get levelingProgress; SnCloudFile? get picture; String? get backgroundId; SnCloudFile? get background; 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; 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 | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @JsonKey(includeFromJson: false, includeToJson: false) | ||||
| @@ -213,16 +213,16 @@ $SnAccountProfileCopyWith<SnAccountProfile> get copyWith => _$SnAccountProfileCo | ||||
|  | ||||
| @override | ||||
| 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.pictureId, pictureId) || other.pictureId == pictureId)&&(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.backgroundId, backgroundId) || other.backgroundId == backgroundId)&&(identical(other.background, background) || other.background == background)&&(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)&&(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) | ||||
| @override | ||||
| int get hashCode => Object.hash(runtimeType,id,firstName,middleName,lastName,bio,pictureId,experience,level,levelingProgress,picture,backgroundId,background,createdAt,updatedAt,deletedAt); | ||||
| 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]); | ||||
|  | ||||
| @override | ||||
| String toString() { | ||||
|   return 'SnAccountProfile(id: $id, firstName: $firstName, middleName: $middleName, lastName: $lastName, bio: $bio, pictureId: $pictureId, experience: $experience, level: $level, levelingProgress: $levelingProgress, picture: $picture, backgroundId: $backgroundId, background: $background, 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, lastSeenAt: $lastSeenAt, activeBadge: $activeBadge, experience: $experience, level: $level, levelingProgress: $levelingProgress, picture: $picture, background: $background, verification: $verification, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; | ||||
| } | ||||
|  | ||||
|  | ||||
| @@ -233,11 +233,11 @@ abstract mixin class $SnAccountProfileCopyWith<$Res>  { | ||||
|   factory $SnAccountProfileCopyWith(SnAccountProfile value, $Res Function(SnAccountProfile) _then) = _$SnAccountProfileCopyWithImpl; | ||||
| @useResult | ||||
| $Res call({ | ||||
|  String id, String? firstName, String? middleName, String? lastName, String bio, String? pictureId, int experience, int level, double levelingProgress, SnCloudFile? picture, String? backgroundId, SnCloudFile? background, 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, DateTime? lastSeenAt, SnAccountBadge? activeBadge, int experience, int level, double levelingProgress, SnCloudFile? picture, SnCloudFile? background, SnVerificationMark? verification, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt | ||||
| }); | ||||
|  | ||||
|  | ||||
| $SnCloudFileCopyWith<$Res>? get picture;$SnCloudFileCopyWith<$Res>? get background; | ||||
| $SnAccountBadgeCopyWith<$Res>? get activeBadge;$SnCloudFileCopyWith<$Res>? get picture;$SnCloudFileCopyWith<$Res>? get background;$SnVerificationMarkCopyWith<$Res>? get verification; | ||||
|  | ||||
| } | ||||
| /// @nodoc | ||||
| @@ -250,21 +250,27 @@ class _$SnAccountProfileCopyWithImpl<$Res> | ||||
|  | ||||
| /// Create a copy of SnAccountProfile | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? firstName = freezed,Object? middleName = freezed,Object? lastName = freezed,Object? bio = null,Object? pictureId = freezed,Object? experience = null,Object? level = null,Object? levelingProgress = null,Object? picture = freezed,Object? backgroundId = freezed,Object? background = 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? 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( | ||||
| id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable | ||||
| as String,firstName: freezed == firstName ? _self.firstName : firstName // ignore: cast_nullable_to_non_nullable | ||||
| as String?,middleName: freezed == middleName ? _self.middleName : middleName // ignore: cast_nullable_to_non_nullable | ||||
| as String?,lastName: freezed == lastName ? _self.lastName : lastName // ignore: cast_nullable_to_non_nullable | ||||
| as String?,bio: null == bio ? _self.bio : bio // ignore: cast_nullable_to_non_nullable | ||||
| as String,pictureId: freezed == pictureId ? _self.pictureId : pictureId // ignore: cast_nullable_to_non_nullable | ||||
| as String?,experience: null == experience ? _self.experience : experience // ignore: cast_nullable_to_non_nullable | ||||
| as String,firstName: null == firstName ? _self.firstName : firstName // ignore: cast_nullable_to_non_nullable | ||||
| as String,middleName: null == middleName ? _self.middleName : middleName // ignore: cast_nullable_to_non_nullable | ||||
| as String,lastName: null == lastName ? _self.lastName : lastName // ignore: cast_nullable_to_non_nullable | ||||
| as String,bio: null == bio ? _self.bio : bio // ignore: cast_nullable_to_non_nullable | ||||
| as String,gender: null == gender ? _self.gender : gender // ignore: cast_nullable_to_non_nullable | ||||
| as String,pronouns: null == pronouns ? _self.pronouns : pronouns // 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,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?,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 int,level: null == level ? _self.level : level // ignore: cast_nullable_to_non_nullable | ||||
| as int,levelingProgress: null == levelingProgress ? _self.levelingProgress : levelingProgress // ignore: cast_nullable_to_non_nullable | ||||
| as double,picture: freezed == picture ? _self.picture : picture // ignore: cast_nullable_to_non_nullable | ||||
| as SnCloudFile?,backgroundId: freezed == backgroundId ? _self.backgroundId : backgroundId // ignore: cast_nullable_to_non_nullable | ||||
| as String?,background: freezed == background ? _self.background : background // ignore: cast_nullable_to_non_nullable | ||||
| as SnCloudFile?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable | ||||
| as SnCloudFile?,background: freezed == background ? _self.background : background // ignore: cast_nullable_to_non_nullable | ||||
| as SnCloudFile?,verification: freezed == verification ? _self.verification : verification // ignore: cast_nullable_to_non_nullable | ||||
| as SnVerificationMark?,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?, | ||||
| @@ -274,6 +280,18 @@ as DateTime?, | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @override | ||||
| @pragma('vm:prefer-inline') | ||||
| $SnAccountBadgeCopyWith<$Res>? get activeBadge { | ||||
|     if (_self.activeBadge == null) { | ||||
|     return null; | ||||
|   } | ||||
|  | ||||
|   return $SnAccountBadgeCopyWith<$Res>(_self.activeBadge!, (value) { | ||||
|     return _then(_self.copyWith(activeBadge: value)); | ||||
|   }); | ||||
| }/// Create a copy of SnAccountProfile | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @override | ||||
| @pragma('vm:prefer-inline') | ||||
| $SnCloudFileCopyWith<$Res>? get picture { | ||||
|     if (_self.picture == null) { | ||||
|     return null; | ||||
| @@ -294,6 +312,18 @@ $SnCloudFileCopyWith<$Res>? get background { | ||||
|   return $SnCloudFileCopyWith<$Res>(_self.background!, (value) { | ||||
|     return _then(_self.copyWith(background: value)); | ||||
|   }); | ||||
| }/// Create a copy of SnAccountProfile | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @override | ||||
| @pragma('vm:prefer-inline') | ||||
| $SnVerificationMarkCopyWith<$Res>? get verification { | ||||
|     if (_self.verification == null) { | ||||
|     return null; | ||||
|   } | ||||
|  | ||||
|   return $SnVerificationMarkCopyWith<$Res>(_self.verification!, (value) { | ||||
|     return _then(_self.copyWith(verification: value)); | ||||
|   }); | ||||
| } | ||||
| } | ||||
|  | ||||
| @@ -302,21 +332,27 @@ $SnCloudFileCopyWith<$Res>? get background { | ||||
| @JsonSerializable() | ||||
|  | ||||
| class _SnAccountProfile implements SnAccountProfile { | ||||
|   const _SnAccountProfile({required this.id, required this.firstName, required this.middleName, required this.lastName, this.bio = '', required this.pictureId, required this.experience, required this.level, required this.levelingProgress, required this.picture, required this.backgroundId, required this.background, 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, 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}); | ||||
|   factory _SnAccountProfile.fromJson(Map<String, dynamic> json) => _$SnAccountProfileFromJson(json); | ||||
|  | ||||
| @override final  String id; | ||||
| @override final  String? firstName; | ||||
| @override final  String? middleName; | ||||
| @override final  String? lastName; | ||||
| @override@JsonKey() final  String firstName; | ||||
| @override@JsonKey() final  String middleName; | ||||
| @override@JsonKey() final  String lastName; | ||||
| @override@JsonKey() final  String bio; | ||||
| @override final  String? pictureId; | ||||
| @override@JsonKey() final  String gender; | ||||
| @override@JsonKey() final  String pronouns; | ||||
| @override@JsonKey() final  String location; | ||||
| @override@JsonKey() final  String timeZone; | ||||
| @override final  DateTime? birthday; | ||||
| @override final  DateTime? lastSeenAt; | ||||
| @override final  SnAccountBadge? activeBadge; | ||||
| @override final  int experience; | ||||
| @override final  int level; | ||||
| @override final  double levelingProgress; | ||||
| @override final  SnCloudFile? picture; | ||||
| @override final  String? backgroundId; | ||||
| @override final  SnCloudFile? background; | ||||
| @override final  SnVerificationMark? verification; | ||||
| @override final  DateTime createdAt; | ||||
| @override final  DateTime updatedAt; | ||||
| @override final  DateTime? deletedAt; | ||||
| @@ -334,16 +370,16 @@ Map<String, dynamic> toJson() { | ||||
|  | ||||
| @override | ||||
| 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.pictureId, pictureId) || other.pictureId == pictureId)&&(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.backgroundId, backgroundId) || other.backgroundId == backgroundId)&&(identical(other.background, background) || other.background == background)&&(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)&&(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) | ||||
| @override | ||||
| int get hashCode => Object.hash(runtimeType,id,firstName,middleName,lastName,bio,pictureId,experience,level,levelingProgress,picture,backgroundId,background,createdAt,updatedAt,deletedAt); | ||||
| 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]); | ||||
|  | ||||
| @override | ||||
| String toString() { | ||||
|   return 'SnAccountProfile(id: $id, firstName: $firstName, middleName: $middleName, lastName: $lastName, bio: $bio, pictureId: $pictureId, experience: $experience, level: $level, levelingProgress: $levelingProgress, picture: $picture, backgroundId: $backgroundId, background: $background, 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, lastSeenAt: $lastSeenAt, activeBadge: $activeBadge, experience: $experience, level: $level, levelingProgress: $levelingProgress, picture: $picture, background: $background, verification: $verification, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; | ||||
| } | ||||
|  | ||||
|  | ||||
| @@ -354,11 +390,11 @@ abstract mixin class _$SnAccountProfileCopyWith<$Res> implements $SnAccountProfi | ||||
|   factory _$SnAccountProfileCopyWith(_SnAccountProfile value, $Res Function(_SnAccountProfile) _then) = __$SnAccountProfileCopyWithImpl; | ||||
| @override @useResult | ||||
| $Res call({ | ||||
|  String id, String? firstName, String? middleName, String? lastName, String bio, String? pictureId, int experience, int level, double levelingProgress, SnCloudFile? picture, String? backgroundId, SnCloudFile? background, 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, DateTime? lastSeenAt, SnAccountBadge? activeBadge, int experience, int level, double levelingProgress, SnCloudFile? picture, SnCloudFile? background, SnVerificationMark? verification, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt | ||||
| }); | ||||
|  | ||||
|  | ||||
| @override $SnCloudFileCopyWith<$Res>? get picture;@override $SnCloudFileCopyWith<$Res>? get background; | ||||
| @override $SnAccountBadgeCopyWith<$Res>? get activeBadge;@override $SnCloudFileCopyWith<$Res>? get picture;@override $SnCloudFileCopyWith<$Res>? get background;@override $SnVerificationMarkCopyWith<$Res>? get verification; | ||||
|  | ||||
| } | ||||
| /// @nodoc | ||||
| @@ -371,21 +407,27 @@ class __$SnAccountProfileCopyWithImpl<$Res> | ||||
|  | ||||
| /// Create a copy of SnAccountProfile | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? firstName = freezed,Object? middleName = freezed,Object? lastName = freezed,Object? bio = null,Object? pictureId = freezed,Object? experience = null,Object? level = null,Object? levelingProgress = null,Object? picture = freezed,Object? backgroundId = freezed,Object? background = 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? 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( | ||||
| id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable | ||||
| as String,firstName: freezed == firstName ? _self.firstName : firstName // ignore: cast_nullable_to_non_nullable | ||||
| as String?,middleName: freezed == middleName ? _self.middleName : middleName // ignore: cast_nullable_to_non_nullable | ||||
| as String?,lastName: freezed == lastName ? _self.lastName : lastName // ignore: cast_nullable_to_non_nullable | ||||
| as String?,bio: null == bio ? _self.bio : bio // ignore: cast_nullable_to_non_nullable | ||||
| as String,pictureId: freezed == pictureId ? _self.pictureId : pictureId // ignore: cast_nullable_to_non_nullable | ||||
| as String?,experience: null == experience ? _self.experience : experience // ignore: cast_nullable_to_non_nullable | ||||
| as String,firstName: null == firstName ? _self.firstName : firstName // ignore: cast_nullable_to_non_nullable | ||||
| as String,middleName: null == middleName ? _self.middleName : middleName // ignore: cast_nullable_to_non_nullable | ||||
| as String,lastName: null == lastName ? _self.lastName : lastName // ignore: cast_nullable_to_non_nullable | ||||
| as String,bio: null == bio ? _self.bio : bio // ignore: cast_nullable_to_non_nullable | ||||
| as String,gender: null == gender ? _self.gender : gender // ignore: cast_nullable_to_non_nullable | ||||
| as String,pronouns: null == pronouns ? _self.pronouns : pronouns // 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,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?,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 int,level: null == level ? _self.level : level // ignore: cast_nullable_to_non_nullable | ||||
| as int,levelingProgress: null == levelingProgress ? _self.levelingProgress : levelingProgress // ignore: cast_nullable_to_non_nullable | ||||
| as double,picture: freezed == picture ? _self.picture : picture // ignore: cast_nullable_to_non_nullable | ||||
| as SnCloudFile?,backgroundId: freezed == backgroundId ? _self.backgroundId : backgroundId // ignore: cast_nullable_to_non_nullable | ||||
| as String?,background: freezed == background ? _self.background : background // ignore: cast_nullable_to_non_nullable | ||||
| as SnCloudFile?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable | ||||
| as SnCloudFile?,background: freezed == background ? _self.background : background // ignore: cast_nullable_to_non_nullable | ||||
| as SnCloudFile?,verification: freezed == verification ? _self.verification : verification // ignore: cast_nullable_to_non_nullable | ||||
| as SnVerificationMark?,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?, | ||||
| @@ -396,6 +438,18 @@ as DateTime?, | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @override | ||||
| @pragma('vm:prefer-inline') | ||||
| $SnAccountBadgeCopyWith<$Res>? get activeBadge { | ||||
|     if (_self.activeBadge == null) { | ||||
|     return null; | ||||
|   } | ||||
|  | ||||
|   return $SnAccountBadgeCopyWith<$Res>(_self.activeBadge!, (value) { | ||||
|     return _then(_self.copyWith(activeBadge: value)); | ||||
|   }); | ||||
| }/// Create a copy of SnAccountProfile | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @override | ||||
| @pragma('vm:prefer-inline') | ||||
| $SnCloudFileCopyWith<$Res>? get picture { | ||||
|     if (_self.picture == null) { | ||||
|     return null; | ||||
| @@ -416,6 +470,18 @@ $SnCloudFileCopyWith<$Res>? get background { | ||||
|   return $SnCloudFileCopyWith<$Res>(_self.background!, (value) { | ||||
|     return _then(_self.copyWith(background: value)); | ||||
|   }); | ||||
| }/// Create a copy of SnAccountProfile | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @override | ||||
| @pragma('vm:prefer-inline') | ||||
| $SnVerificationMarkCopyWith<$Res>? get verification { | ||||
|     if (_self.verification == null) { | ||||
|     return null; | ||||
|   } | ||||
|  | ||||
|   return $SnVerificationMarkCopyWith<$Res>(_self.verification!, (value) { | ||||
|     return _then(_self.copyWith(verification: value)); | ||||
|   }); | ||||
| } | ||||
| } | ||||
|  | ||||
| @@ -589,7 +655,7 @@ as DateTime?, | ||||
| /// @nodoc | ||||
| mixin _$SnAccountBadge { | ||||
|  | ||||
|  String get id; String get type; String? get label; String? get caption; Map<String, dynamic> get meta; DateTime? get expiredAt; String get accountId; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; | ||||
|  String get id; String get type; String? get label; String? get caption; Map<String, dynamic> get meta; DateTime? get expiredAt; String get accountId; DateTime get createdAt; DateTime get updatedAt; DateTime? get activatedAt; DateTime? get deletedAt; | ||||
| /// Create a copy of SnAccountBadge | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @JsonKey(includeFromJson: false, includeToJson: false) | ||||
| @@ -602,16 +668,16 @@ $SnAccountBadgeCopyWith<SnAccountBadge> get copyWith => _$SnAccountBadgeCopyWith | ||||
|  | ||||
| @override | ||||
| bool operator ==(Object other) { | ||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is SnAccountBadge&&(identical(other.id, id) || other.id == id)&&(identical(other.type, type) || other.type == type)&&(identical(other.label, label) || other.label == label)&&(identical(other.caption, caption) || other.caption == caption)&&const DeepCollectionEquality().equals(other.meta, meta)&&(identical(other.expiredAt, expiredAt) || other.expiredAt == expiredAt)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)); | ||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is SnAccountBadge&&(identical(other.id, id) || other.id == id)&&(identical(other.type, type) || other.type == type)&&(identical(other.label, label) || other.label == label)&&(identical(other.caption, caption) || other.caption == caption)&&const DeepCollectionEquality().equals(other.meta, meta)&&(identical(other.expiredAt, expiredAt) || other.expiredAt == expiredAt)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.activatedAt, activatedAt) || other.activatedAt == activatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)); | ||||
| } | ||||
|  | ||||
| @JsonKey(includeFromJson: false, includeToJson: false) | ||||
| @override | ||||
| int get hashCode => Object.hash(runtimeType,id,type,label,caption,const DeepCollectionEquality().hash(meta),expiredAt,accountId,createdAt,updatedAt,deletedAt); | ||||
| int get hashCode => Object.hash(runtimeType,id,type,label,caption,const DeepCollectionEquality().hash(meta),expiredAt,accountId,createdAt,updatedAt,activatedAt,deletedAt); | ||||
|  | ||||
| @override | ||||
| String toString() { | ||||
|   return 'SnAccountBadge(id: $id, type: $type, label: $label, caption: $caption, meta: $meta, expiredAt: $expiredAt, accountId: $accountId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; | ||||
|   return 'SnAccountBadge(id: $id, type: $type, label: $label, caption: $caption, meta: $meta, expiredAt: $expiredAt, accountId: $accountId, createdAt: $createdAt, updatedAt: $updatedAt, activatedAt: $activatedAt, deletedAt: $deletedAt)'; | ||||
| } | ||||
|  | ||||
|  | ||||
| @@ -622,7 +688,7 @@ abstract mixin class $SnAccountBadgeCopyWith<$Res>  { | ||||
|   factory $SnAccountBadgeCopyWith(SnAccountBadge value, $Res Function(SnAccountBadge) _then) = _$SnAccountBadgeCopyWithImpl; | ||||
| @useResult | ||||
| $Res call({ | ||||
|  String id, String type, String? label, String? caption, Map<String, dynamic> meta, DateTime? expiredAt, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt | ||||
|  String id, String type, String? label, String? caption, Map<String, dynamic> meta, DateTime? expiredAt, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? activatedAt, DateTime? deletedAt | ||||
| }); | ||||
|  | ||||
|  | ||||
| @@ -639,7 +705,7 @@ class _$SnAccountBadgeCopyWithImpl<$Res> | ||||
|  | ||||
| /// Create a copy of SnAccountBadge | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? type = null,Object? label = freezed,Object? caption = freezed,Object? meta = null,Object? expiredAt = freezed,Object? accountId = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) { | ||||
| @pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? type = null,Object? label = freezed,Object? caption = freezed,Object? meta = null,Object? expiredAt = freezed,Object? accountId = null,Object? createdAt = null,Object? updatedAt = null,Object? activatedAt = freezed,Object? deletedAt = freezed,}) { | ||||
|   return _then(_self.copyWith( | ||||
| id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable | ||||
| as String,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable | ||||
| @@ -650,7 +716,8 @@ as Map<String, dynamic>,expiredAt: freezed == expiredAt ? _self.expiredAt : expi | ||||
| as DateTime?,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable | ||||
| as String,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable | ||||
| as 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,activatedAt: freezed == activatedAt ? _self.activatedAt : activatedAt // ignore: cast_nullable_to_non_nullable | ||||
| as DateTime?,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable | ||||
| as DateTime?, | ||||
|   )); | ||||
| } | ||||
| @@ -662,7 +729,7 @@ as DateTime?, | ||||
| @JsonSerializable() | ||||
|  | ||||
| class _SnAccountBadge implements SnAccountBadge { | ||||
|   const _SnAccountBadge({required this.id, required this.type, required this.label, required this.caption, required final  Map<String, dynamic> meta, required this.expiredAt, required this.accountId, required this.createdAt, required this.updatedAt, required this.deletedAt}): _meta = meta; | ||||
|   const _SnAccountBadge({required this.id, required this.type, required this.label, required this.caption, required final  Map<String, dynamic> meta, required this.expiredAt, required this.accountId, required this.createdAt, required this.updatedAt, required this.activatedAt, required this.deletedAt}): _meta = meta; | ||||
|   factory _SnAccountBadge.fromJson(Map<String, dynamic> json) => _$SnAccountBadgeFromJson(json); | ||||
|  | ||||
| @override final  String id; | ||||
| @@ -680,6 +747,7 @@ class _SnAccountBadge implements SnAccountBadge { | ||||
| @override final  String accountId; | ||||
| @override final  DateTime createdAt; | ||||
| @override final  DateTime updatedAt; | ||||
| @override final  DateTime? activatedAt; | ||||
| @override final  DateTime? deletedAt; | ||||
|  | ||||
| /// Create a copy of SnAccountBadge | ||||
| @@ -695,16 +763,16 @@ Map<String, dynamic> toJson() { | ||||
|  | ||||
| @override | ||||
| bool operator ==(Object other) { | ||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnAccountBadge&&(identical(other.id, id) || other.id == id)&&(identical(other.type, type) || other.type == type)&&(identical(other.label, label) || other.label == label)&&(identical(other.caption, caption) || other.caption == caption)&&const DeepCollectionEquality().equals(other._meta, _meta)&&(identical(other.expiredAt, expiredAt) || other.expiredAt == expiredAt)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)); | ||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnAccountBadge&&(identical(other.id, id) || other.id == id)&&(identical(other.type, type) || other.type == type)&&(identical(other.label, label) || other.label == label)&&(identical(other.caption, caption) || other.caption == caption)&&const DeepCollectionEquality().equals(other._meta, _meta)&&(identical(other.expiredAt, expiredAt) || other.expiredAt == expiredAt)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.activatedAt, activatedAt) || other.activatedAt == activatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)); | ||||
| } | ||||
|  | ||||
| @JsonKey(includeFromJson: false, includeToJson: false) | ||||
| @override | ||||
| int get hashCode => Object.hash(runtimeType,id,type,label,caption,const DeepCollectionEquality().hash(_meta),expiredAt,accountId,createdAt,updatedAt,deletedAt); | ||||
| int get hashCode => Object.hash(runtimeType,id,type,label,caption,const DeepCollectionEquality().hash(_meta),expiredAt,accountId,createdAt,updatedAt,activatedAt,deletedAt); | ||||
|  | ||||
| @override | ||||
| String toString() { | ||||
|   return 'SnAccountBadge(id: $id, type: $type, label: $label, caption: $caption, meta: $meta, expiredAt: $expiredAt, accountId: $accountId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; | ||||
|   return 'SnAccountBadge(id: $id, type: $type, label: $label, caption: $caption, meta: $meta, expiredAt: $expiredAt, accountId: $accountId, createdAt: $createdAt, updatedAt: $updatedAt, activatedAt: $activatedAt, deletedAt: $deletedAt)'; | ||||
| } | ||||
|  | ||||
|  | ||||
| @@ -715,7 +783,7 @@ abstract mixin class _$SnAccountBadgeCopyWith<$Res> implements $SnAccountBadgeCo | ||||
|   factory _$SnAccountBadgeCopyWith(_SnAccountBadge value, $Res Function(_SnAccountBadge) _then) = __$SnAccountBadgeCopyWithImpl; | ||||
| @override @useResult | ||||
| $Res call({ | ||||
|  String id, String type, String? label, String? caption, Map<String, dynamic> meta, DateTime? expiredAt, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt | ||||
|  String id, String type, String? label, String? caption, Map<String, dynamic> meta, DateTime? expiredAt, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? activatedAt, DateTime? deletedAt | ||||
| }); | ||||
|  | ||||
|  | ||||
| @@ -732,7 +800,7 @@ class __$SnAccountBadgeCopyWithImpl<$Res> | ||||
|  | ||||
| /// Create a copy of SnAccountBadge | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? type = null,Object? label = freezed,Object? caption = freezed,Object? meta = null,Object? expiredAt = freezed,Object? accountId = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) { | ||||
| @override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? type = null,Object? label = freezed,Object? caption = freezed,Object? meta = null,Object? expiredAt = freezed,Object? accountId = null,Object? createdAt = null,Object? updatedAt = null,Object? activatedAt = freezed,Object? deletedAt = freezed,}) { | ||||
|   return _then(_SnAccountBadge( | ||||
| id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable | ||||
| as String,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable | ||||
| @@ -743,6 +811,164 @@ as Map<String, dynamic>,expiredAt: freezed == expiredAt ? _self.expiredAt : expi | ||||
| as DateTime?,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable | ||||
| as String,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable | ||||
| as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable | ||||
| as DateTime,activatedAt: freezed == activatedAt ? _self.activatedAt : activatedAt // ignore: cast_nullable_to_non_nullable | ||||
| as DateTime?,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable | ||||
| as DateTime?, | ||||
|   )); | ||||
| } | ||||
|  | ||||
|  | ||||
| } | ||||
|  | ||||
|  | ||||
| /// @nodoc | ||||
| mixin _$SnContactMethod { | ||||
|  | ||||
|  String get id; int get type; DateTime? get verifiedAt; bool get isPrimary; String get content; String get accountId; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; | ||||
| /// Create a copy of SnContactMethod | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @JsonKey(includeFromJson: false, includeToJson: false) | ||||
| @pragma('vm:prefer-inline') | ||||
| $SnContactMethodCopyWith<SnContactMethod> get copyWith => _$SnContactMethodCopyWithImpl<SnContactMethod>(this as SnContactMethod, _$identity); | ||||
|  | ||||
|   /// Serializes this SnContactMethod to a JSON map. | ||||
|   Map<String, dynamic> toJson(); | ||||
|  | ||||
|  | ||||
| @override | ||||
| bool operator ==(Object other) { | ||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is SnContactMethod&&(identical(other.id, id) || other.id == id)&&(identical(other.type, type) || other.type == type)&&(identical(other.verifiedAt, verifiedAt) || other.verifiedAt == verifiedAt)&&(identical(other.isPrimary, isPrimary) || other.isPrimary == isPrimary)&&(identical(other.content, content) || other.content == content)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)); | ||||
| } | ||||
|  | ||||
| @JsonKey(includeFromJson: false, includeToJson: false) | ||||
| @override | ||||
| int get hashCode => Object.hash(runtimeType,id,type,verifiedAt,isPrimary,content,accountId,createdAt,updatedAt,deletedAt); | ||||
|  | ||||
| @override | ||||
| String toString() { | ||||
|   return 'SnContactMethod(id: $id, type: $type, verifiedAt: $verifiedAt, isPrimary: $isPrimary, content: $content, accountId: $accountId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; | ||||
| } | ||||
|  | ||||
|  | ||||
| } | ||||
|  | ||||
| /// @nodoc | ||||
| abstract mixin class $SnContactMethodCopyWith<$Res>  { | ||||
|   factory $SnContactMethodCopyWith(SnContactMethod value, $Res Function(SnContactMethod) _then) = _$SnContactMethodCopyWithImpl; | ||||
| @useResult | ||||
| $Res call({ | ||||
|  String id, int type, DateTime? verifiedAt, bool isPrimary, String content, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt | ||||
| }); | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| } | ||||
| /// @nodoc | ||||
| class _$SnContactMethodCopyWithImpl<$Res> | ||||
|     implements $SnContactMethodCopyWith<$Res> { | ||||
|   _$SnContactMethodCopyWithImpl(this._self, this._then); | ||||
|  | ||||
|   final SnContactMethod _self; | ||||
|   final $Res Function(SnContactMethod) _then; | ||||
|  | ||||
| /// Create a copy of SnContactMethod | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? type = null,Object? verifiedAt = freezed,Object? isPrimary = null,Object? content = null,Object? accountId = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) { | ||||
|   return _then(_self.copyWith( | ||||
| id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable | ||||
| as String,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable | ||||
| as int,verifiedAt: freezed == verifiedAt ? _self.verifiedAt : verifiedAt // ignore: cast_nullable_to_non_nullable | ||||
| as DateTime?,isPrimary: null == isPrimary ? _self.isPrimary : isPrimary // ignore: cast_nullable_to_non_nullable | ||||
| as bool,content: null == content ? _self.content : content // ignore: cast_nullable_to_non_nullable | ||||
| as String,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable | ||||
| as String,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?, | ||||
|   )); | ||||
| } | ||||
|  | ||||
| } | ||||
|  | ||||
|  | ||||
| /// @nodoc | ||||
| @JsonSerializable() | ||||
|  | ||||
| class _SnContactMethod implements SnContactMethod { | ||||
|   const _SnContactMethod({required this.id, required this.type, required this.verifiedAt, required this.isPrimary, required this.content, required this.accountId, required this.createdAt, required this.updatedAt, required this.deletedAt}); | ||||
|   factory _SnContactMethod.fromJson(Map<String, dynamic> json) => _$SnContactMethodFromJson(json); | ||||
|  | ||||
| @override final  String id; | ||||
| @override final  int type; | ||||
| @override final  DateTime? verifiedAt; | ||||
| @override final  bool isPrimary; | ||||
| @override final  String content; | ||||
| @override final  String accountId; | ||||
| @override final  DateTime createdAt; | ||||
| @override final  DateTime updatedAt; | ||||
| @override final  DateTime? deletedAt; | ||||
|  | ||||
| /// Create a copy of SnContactMethod | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @override @JsonKey(includeFromJson: false, includeToJson: false) | ||||
| @pragma('vm:prefer-inline') | ||||
| _$SnContactMethodCopyWith<_SnContactMethod> get copyWith => __$SnContactMethodCopyWithImpl<_SnContactMethod>(this, _$identity); | ||||
|  | ||||
| @override | ||||
| Map<String, dynamic> toJson() { | ||||
|   return _$SnContactMethodToJson(this, ); | ||||
| } | ||||
|  | ||||
| @override | ||||
| bool operator ==(Object other) { | ||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnContactMethod&&(identical(other.id, id) || other.id == id)&&(identical(other.type, type) || other.type == type)&&(identical(other.verifiedAt, verifiedAt) || other.verifiedAt == verifiedAt)&&(identical(other.isPrimary, isPrimary) || other.isPrimary == isPrimary)&&(identical(other.content, content) || other.content == content)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)); | ||||
| } | ||||
|  | ||||
| @JsonKey(includeFromJson: false, includeToJson: false) | ||||
| @override | ||||
| int get hashCode => Object.hash(runtimeType,id,type,verifiedAt,isPrimary,content,accountId,createdAt,updatedAt,deletedAt); | ||||
|  | ||||
| @override | ||||
| String toString() { | ||||
|   return 'SnContactMethod(id: $id, type: $type, verifiedAt: $verifiedAt, isPrimary: $isPrimary, content: $content, accountId: $accountId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; | ||||
| } | ||||
|  | ||||
|  | ||||
| } | ||||
|  | ||||
| /// @nodoc | ||||
| abstract mixin class _$SnContactMethodCopyWith<$Res> implements $SnContactMethodCopyWith<$Res> { | ||||
|   factory _$SnContactMethodCopyWith(_SnContactMethod value, $Res Function(_SnContactMethod) _then) = __$SnContactMethodCopyWithImpl; | ||||
| @override @useResult | ||||
| $Res call({ | ||||
|  String id, int type, DateTime? verifiedAt, bool isPrimary, String content, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt | ||||
| }); | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| } | ||||
| /// @nodoc | ||||
| class __$SnContactMethodCopyWithImpl<$Res> | ||||
|     implements _$SnContactMethodCopyWith<$Res> { | ||||
|   __$SnContactMethodCopyWithImpl(this._self, this._then); | ||||
|  | ||||
|   final _SnContactMethod _self; | ||||
|   final $Res Function(_SnContactMethod) _then; | ||||
|  | ||||
| /// Create a copy of SnContactMethod | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? type = null,Object? verifiedAt = freezed,Object? isPrimary = null,Object? content = null,Object? accountId = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) { | ||||
|   return _then(_SnContactMethod( | ||||
| id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable | ||||
| as String,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable | ||||
| as int,verifiedAt: freezed == verifiedAt ? _self.verifiedAt : verifiedAt // ignore: cast_nullable_to_non_nullable | ||||
| as DateTime?,isPrimary: null == isPrimary ? _self.isPrimary : isPrimary // ignore: cast_nullable_to_non_nullable | ||||
| as bool,content: null == content ? _self.content : content // ignore: cast_nullable_to_non_nullable | ||||
| as String,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable | ||||
| as String,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?, | ||||
|   )); | ||||
| @@ -921,6 +1147,148 @@ as String, | ||||
| } | ||||
|  | ||||
|  | ||||
| } | ||||
|  | ||||
|  | ||||
| /// @nodoc | ||||
| mixin _$SnVerificationMark { | ||||
|  | ||||
|  int get type; String? get title; String? get description; String? get verifiedBy; | ||||
| /// Create a copy of SnVerificationMark | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @JsonKey(includeFromJson: false, includeToJson: false) | ||||
| @pragma('vm:prefer-inline') | ||||
| $SnVerificationMarkCopyWith<SnVerificationMark> get copyWith => _$SnVerificationMarkCopyWithImpl<SnVerificationMark>(this as SnVerificationMark, _$identity); | ||||
|  | ||||
|   /// Serializes this SnVerificationMark to a JSON map. | ||||
|   Map<String, dynamic> toJson(); | ||||
|  | ||||
|  | ||||
| @override | ||||
| bool operator ==(Object other) { | ||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is SnVerificationMark&&(identical(other.type, type) || other.type == type)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)&&(identical(other.verifiedBy, verifiedBy) || other.verifiedBy == verifiedBy)); | ||||
| } | ||||
|  | ||||
| @JsonKey(includeFromJson: false, includeToJson: false) | ||||
| @override | ||||
| int get hashCode => Object.hash(runtimeType,type,title,description,verifiedBy); | ||||
|  | ||||
| @override | ||||
| String toString() { | ||||
|   return 'SnVerificationMark(type: $type, title: $title, description: $description, verifiedBy: $verifiedBy)'; | ||||
| } | ||||
|  | ||||
|  | ||||
| } | ||||
|  | ||||
| /// @nodoc | ||||
| abstract mixin class $SnVerificationMarkCopyWith<$Res>  { | ||||
|   factory $SnVerificationMarkCopyWith(SnVerificationMark value, $Res Function(SnVerificationMark) _then) = _$SnVerificationMarkCopyWithImpl; | ||||
| @useResult | ||||
| $Res call({ | ||||
|  int type, String? title, String? description, String? verifiedBy | ||||
| }); | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| } | ||||
| /// @nodoc | ||||
| class _$SnVerificationMarkCopyWithImpl<$Res> | ||||
|     implements $SnVerificationMarkCopyWith<$Res> { | ||||
|   _$SnVerificationMarkCopyWithImpl(this._self, this._then); | ||||
|  | ||||
|   final SnVerificationMark _self; | ||||
|   final $Res Function(SnVerificationMark) _then; | ||||
|  | ||||
| /// Create a copy of SnVerificationMark | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @pragma('vm:prefer-inline') @override $Res call({Object? type = null,Object? title = freezed,Object? description = freezed,Object? verifiedBy = freezed,}) { | ||||
|   return _then(_self.copyWith( | ||||
| type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable | ||||
| as int,title: freezed == 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?,verifiedBy: freezed == verifiedBy ? _self.verifiedBy : verifiedBy // ignore: cast_nullable_to_non_nullable | ||||
| as String?, | ||||
|   )); | ||||
| } | ||||
|  | ||||
| } | ||||
|  | ||||
|  | ||||
| /// @nodoc | ||||
| @JsonSerializable() | ||||
|  | ||||
| class _SnVerificationMark implements SnVerificationMark { | ||||
|   const _SnVerificationMark({required this.type, required this.title, required this.description, required this.verifiedBy}); | ||||
|   factory _SnVerificationMark.fromJson(Map<String, dynamic> json) => _$SnVerificationMarkFromJson(json); | ||||
|  | ||||
| @override final  int type; | ||||
| @override final  String? title; | ||||
| @override final  String? description; | ||||
| @override final  String? verifiedBy; | ||||
|  | ||||
| /// Create a copy of SnVerificationMark | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @override @JsonKey(includeFromJson: false, includeToJson: false) | ||||
| @pragma('vm:prefer-inline') | ||||
| _$SnVerificationMarkCopyWith<_SnVerificationMark> get copyWith => __$SnVerificationMarkCopyWithImpl<_SnVerificationMark>(this, _$identity); | ||||
|  | ||||
| @override | ||||
| Map<String, dynamic> toJson() { | ||||
|   return _$SnVerificationMarkToJson(this, ); | ||||
| } | ||||
|  | ||||
| @override | ||||
| bool operator ==(Object other) { | ||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnVerificationMark&&(identical(other.type, type) || other.type == type)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)&&(identical(other.verifiedBy, verifiedBy) || other.verifiedBy == verifiedBy)); | ||||
| } | ||||
|  | ||||
| @JsonKey(includeFromJson: false, includeToJson: false) | ||||
| @override | ||||
| int get hashCode => Object.hash(runtimeType,type,title,description,verifiedBy); | ||||
|  | ||||
| @override | ||||
| String toString() { | ||||
|   return 'SnVerificationMark(type: $type, title: $title, description: $description, verifiedBy: $verifiedBy)'; | ||||
| } | ||||
|  | ||||
|  | ||||
| } | ||||
|  | ||||
| /// @nodoc | ||||
| abstract mixin class _$SnVerificationMarkCopyWith<$Res> implements $SnVerificationMarkCopyWith<$Res> { | ||||
|   factory _$SnVerificationMarkCopyWith(_SnVerificationMark value, $Res Function(_SnVerificationMark) _then) = __$SnVerificationMarkCopyWithImpl; | ||||
| @override @useResult | ||||
| $Res call({ | ||||
|  int type, String? title, String? description, String? verifiedBy | ||||
| }); | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| } | ||||
| /// @nodoc | ||||
| class __$SnVerificationMarkCopyWithImpl<$Res> | ||||
|     implements _$SnVerificationMarkCopyWith<$Res> { | ||||
|   __$SnVerificationMarkCopyWithImpl(this._self, this._then); | ||||
|  | ||||
|   final _SnVerificationMark _self; | ||||
|   final $Res Function(_SnVerificationMark) _then; | ||||
|  | ||||
| /// Create a copy of SnVerificationMark | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @override @pragma('vm:prefer-inline') $Res call({Object? type = null,Object? title = freezed,Object? description = freezed,Object? verifiedBy = freezed,}) { | ||||
|   return _then(_SnVerificationMark( | ||||
| type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable | ||||
| as int,title: freezed == 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?,verifiedBy: freezed == verifiedBy ? _self.verifiedBy : verifiedBy // ignore: cast_nullable_to_non_nullable | ||||
| as String?, | ||||
|   )); | ||||
| } | ||||
|  | ||||
|  | ||||
| } | ||||
|  | ||||
| // dart format on | ||||
|   | ||||
| @@ -43,11 +43,28 @@ Map<String, dynamic> _$SnAccountToJson(_SnAccount instance) => | ||||
| _SnAccountProfile _$SnAccountProfileFromJson(Map<String, dynamic> json) => | ||||
|     _SnAccountProfile( | ||||
|       id: json['id'] as String, | ||||
|       firstName: json['first_name'] as String?, | ||||
|       middleName: json['middle_name'] as String?, | ||||
|       lastName: json['last_name'] as String?, | ||||
|       firstName: json['first_name'] as String? ?? '', | ||||
|       middleName: json['middle_name'] as String? ?? '', | ||||
|       lastName: json['last_name'] as String? ?? '', | ||||
|       bio: json['bio'] as String? ?? '', | ||||
|       pictureId: json['picture_id'] as String?, | ||||
|       gender: json['gender'] as String? ?? '', | ||||
|       pronouns: json['pronouns'] as String? ?? '', | ||||
|       location: json['location'] as String? ?? '', | ||||
|       timeZone: json['time_zone'] as String? ?? '', | ||||
|       birthday: | ||||
|           json['birthday'] == null | ||||
|               ? null | ||||
|               : DateTime.parse(json['birthday'] as String), | ||||
|       lastSeenAt: | ||||
|           json['last_seen_at'] == null | ||||
|               ? null | ||||
|               : DateTime.parse(json['last_seen_at'] as String), | ||||
|       activeBadge: | ||||
|           json['active_badge'] == null | ||||
|               ? null | ||||
|               : SnAccountBadge.fromJson( | ||||
|                 json['active_badge'] as Map<String, dynamic>, | ||||
|               ), | ||||
|       experience: (json['experience'] as num).toInt(), | ||||
|       level: (json['level'] as num).toInt(), | ||||
|       levelingProgress: (json['leveling_progress'] as num).toDouble(), | ||||
| @@ -55,13 +72,18 @@ _SnAccountProfile _$SnAccountProfileFromJson(Map<String, dynamic> json) => | ||||
|           json['picture'] == null | ||||
|               ? null | ||||
|               : SnCloudFile.fromJson(json['picture'] as Map<String, dynamic>), | ||||
|       backgroundId: json['background_id'] as String?, | ||||
|       background: | ||||
|           json['background'] == null | ||||
|               ? null | ||||
|               : SnCloudFile.fromJson( | ||||
|                 json['background'] as Map<String, dynamic>, | ||||
|               ), | ||||
|       verification: | ||||
|           json['verification'] == null | ||||
|               ? null | ||||
|               : SnVerificationMark.fromJson( | ||||
|                 json['verification'] as Map<String, dynamic>, | ||||
|               ), | ||||
|       createdAt: DateTime.parse(json['created_at'] as String), | ||||
|       updatedAt: DateTime.parse(json['updated_at'] as String), | ||||
|       deletedAt: | ||||
| @@ -77,13 +99,19 @@ Map<String, dynamic> _$SnAccountProfileToJson(_SnAccountProfile instance) => | ||||
|       'middle_name': instance.middleName, | ||||
|       'last_name': instance.lastName, | ||||
|       'bio': instance.bio, | ||||
|       'picture_id': instance.pictureId, | ||||
|       'gender': instance.gender, | ||||
|       'pronouns': instance.pronouns, | ||||
|       'location': instance.location, | ||||
|       'time_zone': instance.timeZone, | ||||
|       'birthday': instance.birthday?.toIso8601String(), | ||||
|       'last_seen_at': instance.lastSeenAt?.toIso8601String(), | ||||
|       'active_badge': instance.activeBadge?.toJson(), | ||||
|       'experience': instance.experience, | ||||
|       'level': instance.level, | ||||
|       'leveling_progress': instance.levelingProgress, | ||||
|       'picture': instance.picture?.toJson(), | ||||
|       'background_id': instance.backgroundId, | ||||
|       'background': instance.background?.toJson(), | ||||
|       'verification': instance.verification?.toJson(), | ||||
|       'created_at': instance.createdAt.toIso8601String(), | ||||
|       'updated_at': instance.updatedAt.toIso8601String(), | ||||
|       'deleted_at': instance.deletedAt?.toIso8601String(), | ||||
| @@ -141,6 +169,10 @@ _SnAccountBadge _$SnAccountBadgeFromJson(Map<String, dynamic> json) => | ||||
|       accountId: json['account_id'] as String, | ||||
|       createdAt: DateTime.parse(json['created_at'] as String), | ||||
|       updatedAt: DateTime.parse(json['updated_at'] as String), | ||||
|       activatedAt: | ||||
|           json['activated_at'] == null | ||||
|               ? null | ||||
|               : DateTime.parse(json['activated_at'] as String), | ||||
|       deletedAt: | ||||
|           json['deleted_at'] == null | ||||
|               ? null | ||||
| @@ -158,6 +190,39 @@ Map<String, dynamic> _$SnAccountBadgeToJson(_SnAccountBadge instance) => | ||||
|       'account_id': instance.accountId, | ||||
|       'created_at': instance.createdAt.toIso8601String(), | ||||
|       'updated_at': instance.updatedAt.toIso8601String(), | ||||
|       'activated_at': instance.activatedAt?.toIso8601String(), | ||||
|       'deleted_at': instance.deletedAt?.toIso8601String(), | ||||
|     }; | ||||
|  | ||||
| _SnContactMethod _$SnContactMethodFromJson(Map<String, dynamic> json) => | ||||
|     _SnContactMethod( | ||||
|       id: json['id'] as String, | ||||
|       type: (json['type'] as num).toInt(), | ||||
|       verifiedAt: | ||||
|           json['verified_at'] == null | ||||
|               ? null | ||||
|               : DateTime.parse(json['verified_at'] as String), | ||||
|       isPrimary: json['is_primary'] as bool, | ||||
|       content: json['content'] as String, | ||||
|       accountId: json['account_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> _$SnContactMethodToJson(_SnContactMethod instance) => | ||||
|     <String, dynamic>{ | ||||
|       'id': instance.id, | ||||
|       'type': instance.type, | ||||
|       'verified_at': instance.verifiedAt?.toIso8601String(), | ||||
|       'is_primary': instance.isPrimary, | ||||
|       'content': instance.content, | ||||
|       'account_id': instance.accountId, | ||||
|       'created_at': instance.createdAt.toIso8601String(), | ||||
|       'updated_at': instance.updatedAt.toIso8601String(), | ||||
|       'deleted_at': instance.deletedAt?.toIso8601String(), | ||||
|     }; | ||||
|  | ||||
| @@ -198,3 +263,19 @@ Map<String, dynamic> _$SnNotificationToJson(_SnNotification instance) => | ||||
|       'viewed_at': instance.viewedAt?.toIso8601String(), | ||||
|       'account_id': instance.accountId, | ||||
|     }; | ||||
|  | ||||
| _SnVerificationMark _$SnVerificationMarkFromJson(Map<String, dynamic> json) => | ||||
|     _SnVerificationMark( | ||||
|       type: (json['type'] as num).toInt(), | ||||
|       title: json['title'] as String?, | ||||
|       description: json['description'] as String?, | ||||
|       verifiedBy: json['verified_by'] as String?, | ||||
|     ); | ||||
|  | ||||
| Map<String, dynamic> _$SnVerificationMarkToJson(_SnVerificationMark instance) => | ||||
|     <String, dynamic>{ | ||||
|       'type': instance.type, | ||||
|       'title': instance.title, | ||||
|       'description': instance.description, | ||||
|       'verified_by': instance.verifiedBy, | ||||
|     }; | ||||
|   | ||||
| @@ -5,7 +5,7 @@ part 'wallet.freezed.dart'; | ||||
| part 'wallet.g.dart'; | ||||
|  | ||||
| @freezed | ||||
| abstract class SnWallet with _$SnWallet { | ||||
| sealed class SnWallet with _$SnWallet { | ||||
|   const factory SnWallet({ | ||||
|     required String id, | ||||
|     required List<SnWalletPocket> pockets, | ||||
| @@ -21,7 +21,7 @@ abstract class SnWallet with _$SnWallet { | ||||
| } | ||||
|  | ||||
| @freezed | ||||
| abstract class SnWalletPocket with _$SnWalletPocket { | ||||
| sealed class SnWalletPocket with _$SnWalletPocket { | ||||
|   const factory SnWalletPocket({ | ||||
|     required String id, | ||||
|     required String currency, | ||||
| @@ -37,7 +37,7 @@ abstract class SnWalletPocket with _$SnWalletPocket { | ||||
| } | ||||
|  | ||||
| @freezed | ||||
| abstract class SnTransaction with _$SnTransaction { | ||||
| sealed class SnTransaction with _$SnTransaction { | ||||
|   const factory SnTransaction({ | ||||
|     required String id, | ||||
|     required String currency, | ||||
|   | ||||
							
								
								
									
										355
									
								
								lib/pods/call.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										355
									
								
								lib/pods/call.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,355 @@ | ||||
| import 'package:island/pods/userinfo.dart'; | ||||
| import 'package:island/screens/chat/chat.dart'; | ||||
| import 'package:island/widgets/chat/call_button.dart'; | ||||
| import 'package:livekit_client/livekit_client.dart'; | ||||
| import 'package:freezed_annotation/freezed_annotation.dart'; | ||||
| import 'dart:async'; | ||||
| import 'package:riverpod_annotation/riverpod_annotation.dart'; | ||||
| import 'package:island/pods/network.dart'; | ||||
| import 'package:island/models/chat.dart'; | ||||
| import 'package:island/pods/websocket.dart'; | ||||
|  | ||||
| part 'call.g.dart'; | ||||
| part 'call.freezed.dart'; | ||||
|  | ||||
| String formatDuration(Duration duration) { | ||||
|   String negativeSign = duration.isNegative ? '-' : ''; | ||||
|   String twoDigits(int n) => n.toString().padLeft(2, "0"); | ||||
|   String twoDigitMinutes = twoDigits(duration.inMinutes.remainder(60).abs()); | ||||
|   String twoDigitSeconds = twoDigits(duration.inSeconds.remainder(60).abs()); | ||||
|   return "$negativeSign${twoDigits(duration.inHours)}:$twoDigitMinutes:$twoDigitSeconds"; | ||||
| } | ||||
|  | ||||
| @freezed | ||||
| sealed class CallState with _$CallState { | ||||
|   const factory CallState({ | ||||
|     required bool isConnected, | ||||
|     required bool isMicrophoneEnabled, | ||||
|     required bool isCameraEnabled, | ||||
|     required bool isScreenSharing, | ||||
|     @Default(Duration(seconds: 0)) Duration duration, | ||||
|     String? error, | ||||
|   }) = _CallState; | ||||
| } | ||||
|  | ||||
| @freezed | ||||
| sealed class CallParticipantLive with _$CallParticipantLive { | ||||
|   const CallParticipantLive._(); | ||||
|  | ||||
|   const factory CallParticipantLive({ | ||||
|     required CallParticipant participant, | ||||
|     required Participant remoteParticipant, | ||||
|   }) = _CallParticipantLive; | ||||
|  | ||||
|   bool get isSpeaking => remoteParticipant.isSpeaking; | ||||
|   bool get isMuted => remoteParticipant.isMuted; | ||||
|   bool get isScreenSharing => remoteParticipant.isScreenShareEnabled(); | ||||
|   bool get isScreenSharingWithAudio => | ||||
|       remoteParticipant.isScreenShareAudioEnabled(); | ||||
|  | ||||
|   bool get hasVideo => remoteParticipant.hasVideo; | ||||
|   bool get hasAudio => remoteParticipant.hasAudio; | ||||
| } | ||||
|  | ||||
| @riverpod | ||||
| class CallNotifier extends _$CallNotifier { | ||||
|   Room? _room; | ||||
|   LocalParticipant? _localParticipant; | ||||
|   List<CallParticipantLive> _participants = []; | ||||
|   final Map<String, CallParticipant> _participantInfoByIdentity = {}; | ||||
|   StreamSubscription? _wsSubscription; | ||||
|   EventsListener? _roomListener; | ||||
|  | ||||
|   List<CallParticipantLive> get participants => | ||||
|       List.unmodifiable(_participants); | ||||
|   LocalParticipant? get localParticipant => _localParticipant; | ||||
|  | ||||
|   Timer? _durationTimer; | ||||
|  | ||||
|   @override | ||||
|   CallState build() { | ||||
|     // Subscribe to websocket updates | ||||
|     _subscribeToParticipantsUpdate(); | ||||
|     return const CallState( | ||||
|       isConnected: false, | ||||
|       isMicrophoneEnabled: true, | ||||
|       isCameraEnabled: false, | ||||
|       isScreenSharing: false, | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   void _subscribeToParticipantsUpdate() { | ||||
|     // Only subscribe once | ||||
|     if (_wsSubscription != null) return; | ||||
|     final ws = ref.read(websocketProvider); | ||||
|     _wsSubscription = ws.dataStream.listen((packet) { | ||||
|       if (packet.type == 'call.participants.update' && packet.data != null) { | ||||
|         final participantsData = packet.data!["participants"]; | ||||
|         if (participantsData is List) { | ||||
|           final parsed = | ||||
|               participantsData | ||||
|                   .map( | ||||
|                     (e) => | ||||
|                         CallParticipant.fromJson(Map<String, dynamic>.from(e)), | ||||
|                   ) | ||||
|                   .toList(); | ||||
|           _updateLiveParticipants(parsed); | ||||
|         } | ||||
|       } | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   void _initRoomListeners() { | ||||
|     if (_room == null) return; | ||||
|     _roomListener?.dispose(); | ||||
|     _roomListener = _room!.createListener(); | ||||
|     _room!.addListener(_onRoomChange); | ||||
|     _roomListener! | ||||
|       ..on<ParticipantConnectedEvent>((e) { | ||||
|         _refreshLiveParticipants(); | ||||
|       }) | ||||
|       ..on<RoomDisconnectedEvent>((e) { | ||||
|         _participants = []; | ||||
|         state = state.copyWith(); | ||||
|       }); | ||||
|   } | ||||
|  | ||||
|   void _onRoomChange() { | ||||
|     _refreshLiveParticipants(); | ||||
|   } | ||||
|  | ||||
|   void _refreshLiveParticipants() { | ||||
|     if (_room == null) return; | ||||
|     final remoteParticipants = _room!.remoteParticipants; | ||||
|     _participants = []; | ||||
|     // Add local participant first if available | ||||
|     if (_localParticipant != null) { | ||||
|       final localInfo = _buildParticipant(); | ||||
|       _participants.add( | ||||
|         CallParticipantLive( | ||||
|           participant: localInfo, | ||||
|           remoteParticipant: _localParticipant!, | ||||
|         ), | ||||
|       ); | ||||
|     } | ||||
|     // Add remote participants | ||||
|     _participants.addAll( | ||||
|       remoteParticipants.values.map((remote) { | ||||
|         final match = | ||||
|             _participantInfoByIdentity[remote.identity] ?? | ||||
|             CallParticipant( | ||||
|               identity: remote.identity, | ||||
|               name: remote.identity, | ||||
|               joinedAt: DateTime.now(), | ||||
|               accountId: null, | ||||
|               profile: null, | ||||
|             ); | ||||
|         return CallParticipantLive( | ||||
|           participant: match, | ||||
|           remoteParticipant: remote, | ||||
|         ); | ||||
|       }), | ||||
|     ); | ||||
|     state = state.copyWith(); | ||||
|   } | ||||
|  | ||||
|   /// Builds the CallParticipant object for the local participant. | ||||
|   /// Optionally, pass [participants] if you want to prioritize info from the latest list. | ||||
|   CallParticipant _buildParticipant({List<CallParticipant>? participants}) { | ||||
|     if (_localParticipant == null) { | ||||
|       throw StateError('No local participant available'); | ||||
|     } | ||||
|     // Prefer info from the latest participants list if available | ||||
|     if (participants != null) { | ||||
|       final idx = participants.indexWhere( | ||||
|         (p) => p.identity == _localParticipant!.identity, | ||||
|       ); | ||||
|       if (idx != -1) return participants[idx]; | ||||
|     } | ||||
|  | ||||
|     final userInfo = ref.read(userInfoProvider); | ||||
|     final roomIdentity = ref.read(chatroomIdentityProvider(_roomId)); | ||||
|     // Otherwise, use info from the identity map or fallback to minimal | ||||
|     return _participantInfoByIdentity[_localParticipant!.identity] ?? | ||||
|         CallParticipant( | ||||
|           identity: _localParticipant!.identity, | ||||
|           name: _localParticipant!.identity, | ||||
|           joinedAt: DateTime.now(), | ||||
|           accountId: userInfo.value?.id, | ||||
|           profile: roomIdentity.value, | ||||
|         ); | ||||
|   } | ||||
|  | ||||
|   void _updateLiveParticipants(List<CallParticipant> participants) { | ||||
|     // Update the info map for lookup | ||||
|     for (final p in participants) { | ||||
|       _participantInfoByIdentity[p.identity] = p; | ||||
|     } | ||||
|     if (_room == null) { | ||||
|       // Can't build live objects, just store empty | ||||
|       _participants = []; | ||||
|       state = state.copyWith(); | ||||
|       return; | ||||
|     } | ||||
|     final remoteParticipants = _room!.remoteParticipants; | ||||
|     final remotes = remoteParticipants.values.toList(); | ||||
|     _participants = []; | ||||
|     // Add local participant if present in the list | ||||
|     if (_localParticipant != null) { | ||||
|       final localInfo = _buildParticipant(participants: participants); | ||||
|       _participants.add( | ||||
|         CallParticipantLive( | ||||
|           participant: localInfo, | ||||
|           remoteParticipant: _localParticipant!, | ||||
|         ), | ||||
|       ); | ||||
|     } | ||||
|     // Add remote participants | ||||
|     _participants.addAll( | ||||
|       participants.map((p) { | ||||
|         RemoteParticipant? remote; | ||||
|         for (final r in remotes) { | ||||
|           if (r.identity == p.identity) { | ||||
|             remote = r; | ||||
|             break; | ||||
|           } | ||||
|         } | ||||
|         if (_localParticipant != null && | ||||
|             p.identity == _localParticipant!.identity) { | ||||
|           return null; // Already added local | ||||
|         } | ||||
|         return remote != null | ||||
|             ? CallParticipantLive(participant: p, remoteParticipant: remote) | ||||
|             : null; | ||||
|       }).whereType<CallParticipantLive>(), | ||||
|     ); | ||||
|     state = state.copyWith(); | ||||
|   } | ||||
|  | ||||
|   String? _roomId; | ||||
|   String? get roomId => _roomId; | ||||
|  | ||||
|   Future<void> joinRoom(String roomId) async { | ||||
|     if (_roomId == roomId && _room != null) { | ||||
|       return; | ||||
|     } | ||||
|     _roomId = roomId; | ||||
|     if (_room != null) { | ||||
|       await _room!.disconnect(); | ||||
|       await _room!.dispose(); | ||||
|       _room = null; | ||||
|       _localParticipant = null; | ||||
|       _participants = []; | ||||
|     } | ||||
|     try { | ||||
|       final apiClient = ref.read(apiClientProvider); | ||||
|       final ongoingCall = await ref.read(ongoingCallProvider(roomId).future); | ||||
|       final response = await apiClient.get('/chat/realtime/$roomId/join'); | ||||
|       if (response.statusCode == 200 && response.data != null) { | ||||
|         final data = response.data; | ||||
|         // Parse join response | ||||
|         final joinResponse = ChatRealtimeJoinResponse.fromJson(data); | ||||
|         final participants = joinResponse.participants; | ||||
|         final String endpoint = joinResponse.endpoint; | ||||
|         final String token = joinResponse.token; | ||||
|  | ||||
|         // Setup duration timer | ||||
|         _durationTimer?.cancel(); | ||||
|         _durationTimer = Timer.periodic(const Duration(seconds: 1), (timer) { | ||||
|           state = state.copyWith( | ||||
|             duration: Duration( | ||||
|               milliseconds: | ||||
|                   (DateTime.now().millisecondsSinceEpoch - | ||||
|                       (ongoingCall?.createdAt.millisecondsSinceEpoch ?? 0)), | ||||
|             ), | ||||
|           ); | ||||
|         }); | ||||
|  | ||||
|         // Connect to LiveKit | ||||
|         _room = Room(); | ||||
|  | ||||
|         await _room!.connect( | ||||
|           endpoint, | ||||
|           token, | ||||
|           connectOptions: ConnectOptions(autoSubscribe: true), | ||||
|           roomOptions: RoomOptions(adaptiveStream: true, dynacast: true), | ||||
|           fastConnectOptions: FastConnectOptions( | ||||
|             microphone: TrackOption(enabled: true), | ||||
|           ), | ||||
|         ); | ||||
|         _localParticipant = _room!.localParticipant; | ||||
|  | ||||
|         _initRoomListeners(); | ||||
|         _updateLiveParticipants(participants); | ||||
|  | ||||
|         // Listen for connection updates | ||||
|         _room!.addListener(() { | ||||
|           state = state.copyWith( | ||||
|             isConnected: _room!.connectionState == ConnectionState.connected, | ||||
|             isMicrophoneEnabled: _localParticipant!.isMicrophoneEnabled(), | ||||
|             isCameraEnabled: _localParticipant!.isCameraEnabled(), | ||||
|             isScreenSharing: _localParticipant!.isScreenShareEnabled(), | ||||
|           ); | ||||
|         }); | ||||
|         state = state.copyWith(isConnected: true); | ||||
|       } else { | ||||
|         state = state.copyWith(error: 'Failed to join room'); | ||||
|       } | ||||
|     } catch (e) { | ||||
|       state = state.copyWith(error: e.toString()); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   Future<void> toggleMicrophone() async { | ||||
|     if (_localParticipant != null) { | ||||
|       const autostop = true; | ||||
|       final target = !_localParticipant!.isMicrophoneEnabled(); | ||||
|       state = state.copyWith(isMicrophoneEnabled: target); | ||||
|       if (target) { | ||||
|         await _localParticipant!.audioTrackPublications.firstOrNull?.unmute( | ||||
|           stopOnMute: autostop, | ||||
|         ); | ||||
|       } else { | ||||
|         await _localParticipant!.audioTrackPublications.firstOrNull?.mute( | ||||
|           stopOnMute: autostop, | ||||
|         ); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   Future<void> toggleCamera() async { | ||||
|     if (_localParticipant != null) { | ||||
|       final target = !_localParticipant!.isCameraEnabled(); | ||||
|       state = state.copyWith(isCameraEnabled: target); | ||||
|       await _localParticipant!.setCameraEnabled(target); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   Future<void> toggleScreenShare() async { | ||||
|     if (_localParticipant != null) { | ||||
|       final target = !_localParticipant!.isScreenShareEnabled(); | ||||
|       state = state.copyWith(isScreenSharing: target); | ||||
|       await _localParticipant!.setScreenShareEnabled(target); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   Future<void> disconnect() async { | ||||
|     if (_room != null) { | ||||
|       await _room!.disconnect(); | ||||
|       state = state.copyWith( | ||||
|         isConnected: false, | ||||
|         isMicrophoneEnabled: false, | ||||
|         isCameraEnabled: false, | ||||
|         isScreenSharing: false, | ||||
|       ); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   void dispose() { | ||||
|     _wsSubscription?.cancel(); | ||||
|     _roomListener?.dispose(); | ||||
|     _room?.removeListener(_onRoomChange); | ||||
|     _room?.dispose(); | ||||
|     _durationTimer?.cancel(); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										305
									
								
								lib/pods/call.freezed.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										305
									
								
								lib/pods/call.freezed.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,305 @@ | ||||
| // dart format width=80 | ||||
| // coverage:ignore-file | ||||
| // GENERATED CODE - DO NOT MODIFY BY HAND | ||||
| // ignore_for_file: type=lint | ||||
| // ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark | ||||
|  | ||||
| part of 'call.dart'; | ||||
|  | ||||
| // ************************************************************************** | ||||
| // FreezedGenerator | ||||
| // ************************************************************************** | ||||
|  | ||||
| // dart format off | ||||
| T _$identity<T>(T value) => value; | ||||
| /// @nodoc | ||||
| mixin _$CallState { | ||||
|  | ||||
|  bool get isConnected; bool get isMicrophoneEnabled; bool get isCameraEnabled; bool get isScreenSharing; Duration get duration; String? get error; | ||||
| /// Create a copy of CallState | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @JsonKey(includeFromJson: false, includeToJson: false) | ||||
| @pragma('vm:prefer-inline') | ||||
| $CallStateCopyWith<CallState> get copyWith => _$CallStateCopyWithImpl<CallState>(this as CallState, _$identity); | ||||
|  | ||||
|  | ||||
|  | ||||
| @override | ||||
| bool operator ==(Object other) { | ||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is CallState&&(identical(other.isConnected, isConnected) || other.isConnected == isConnected)&&(identical(other.isMicrophoneEnabled, isMicrophoneEnabled) || other.isMicrophoneEnabled == isMicrophoneEnabled)&&(identical(other.isCameraEnabled, isCameraEnabled) || other.isCameraEnabled == isCameraEnabled)&&(identical(other.isScreenSharing, isScreenSharing) || other.isScreenSharing == isScreenSharing)&&(identical(other.duration, duration) || other.duration == duration)&&(identical(other.error, error) || other.error == error)); | ||||
| } | ||||
|  | ||||
|  | ||||
| @override | ||||
| int get hashCode => Object.hash(runtimeType,isConnected,isMicrophoneEnabled,isCameraEnabled,isScreenSharing,duration,error); | ||||
|  | ||||
| @override | ||||
| String toString() { | ||||
|   return 'CallState(isConnected: $isConnected, isMicrophoneEnabled: $isMicrophoneEnabled, isCameraEnabled: $isCameraEnabled, isScreenSharing: $isScreenSharing, duration: $duration, error: $error)'; | ||||
| } | ||||
|  | ||||
|  | ||||
| } | ||||
|  | ||||
| /// @nodoc | ||||
| abstract mixin class $CallStateCopyWith<$Res>  { | ||||
|   factory $CallStateCopyWith(CallState value, $Res Function(CallState) _then) = _$CallStateCopyWithImpl; | ||||
| @useResult | ||||
| $Res call({ | ||||
|  bool isConnected, bool isMicrophoneEnabled, bool isCameraEnabled, bool isScreenSharing, Duration duration, String? error | ||||
| }); | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| } | ||||
| /// @nodoc | ||||
| class _$CallStateCopyWithImpl<$Res> | ||||
|     implements $CallStateCopyWith<$Res> { | ||||
|   _$CallStateCopyWithImpl(this._self, this._then); | ||||
|  | ||||
|   final CallState _self; | ||||
|   final $Res Function(CallState) _then; | ||||
|  | ||||
| /// Create a copy of CallState | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @pragma('vm:prefer-inline') @override $Res call({Object? isConnected = null,Object? isMicrophoneEnabled = null,Object? isCameraEnabled = null,Object? isScreenSharing = null,Object? duration = null,Object? error = freezed,}) { | ||||
|   return _then(_self.copyWith( | ||||
| isConnected: null == isConnected ? _self.isConnected : isConnected // ignore: cast_nullable_to_non_nullable | ||||
| as bool,isMicrophoneEnabled: null == isMicrophoneEnabled ? _self.isMicrophoneEnabled : isMicrophoneEnabled // ignore: cast_nullable_to_non_nullable | ||||
| as bool,isCameraEnabled: null == isCameraEnabled ? _self.isCameraEnabled : isCameraEnabled // ignore: cast_nullable_to_non_nullable | ||||
| as bool,isScreenSharing: null == isScreenSharing ? _self.isScreenSharing : isScreenSharing // ignore: cast_nullable_to_non_nullable | ||||
| as bool,duration: null == duration ? _self.duration : duration // ignore: cast_nullable_to_non_nullable | ||||
| as Duration,error: freezed == error ? _self.error : error // ignore: cast_nullable_to_non_nullable | ||||
| as String?, | ||||
|   )); | ||||
| } | ||||
|  | ||||
| } | ||||
|  | ||||
|  | ||||
| /// @nodoc | ||||
|  | ||||
|  | ||||
| class _CallState implements CallState { | ||||
|   const _CallState({required this.isConnected, required this.isMicrophoneEnabled, required this.isCameraEnabled, required this.isScreenSharing, this.duration = const Duration(seconds: 0), this.error}); | ||||
|    | ||||
|  | ||||
| @override final  bool isConnected; | ||||
| @override final  bool isMicrophoneEnabled; | ||||
| @override final  bool isCameraEnabled; | ||||
| @override final  bool isScreenSharing; | ||||
| @override@JsonKey() final  Duration duration; | ||||
| @override final  String? error; | ||||
|  | ||||
| /// Create a copy of CallState | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @override @JsonKey(includeFromJson: false, includeToJson: false) | ||||
| @pragma('vm:prefer-inline') | ||||
| _$CallStateCopyWith<_CallState> get copyWith => __$CallStateCopyWithImpl<_CallState>(this, _$identity); | ||||
|  | ||||
|  | ||||
|  | ||||
| @override | ||||
| bool operator ==(Object other) { | ||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is _CallState&&(identical(other.isConnected, isConnected) || other.isConnected == isConnected)&&(identical(other.isMicrophoneEnabled, isMicrophoneEnabled) || other.isMicrophoneEnabled == isMicrophoneEnabled)&&(identical(other.isCameraEnabled, isCameraEnabled) || other.isCameraEnabled == isCameraEnabled)&&(identical(other.isScreenSharing, isScreenSharing) || other.isScreenSharing == isScreenSharing)&&(identical(other.duration, duration) || other.duration == duration)&&(identical(other.error, error) || other.error == error)); | ||||
| } | ||||
|  | ||||
|  | ||||
| @override | ||||
| int get hashCode => Object.hash(runtimeType,isConnected,isMicrophoneEnabled,isCameraEnabled,isScreenSharing,duration,error); | ||||
|  | ||||
| @override | ||||
| String toString() { | ||||
|   return 'CallState(isConnected: $isConnected, isMicrophoneEnabled: $isMicrophoneEnabled, isCameraEnabled: $isCameraEnabled, isScreenSharing: $isScreenSharing, duration: $duration, error: $error)'; | ||||
| } | ||||
|  | ||||
|  | ||||
| } | ||||
|  | ||||
| /// @nodoc | ||||
| abstract mixin class _$CallStateCopyWith<$Res> implements $CallStateCopyWith<$Res> { | ||||
|   factory _$CallStateCopyWith(_CallState value, $Res Function(_CallState) _then) = __$CallStateCopyWithImpl; | ||||
| @override @useResult | ||||
| $Res call({ | ||||
|  bool isConnected, bool isMicrophoneEnabled, bool isCameraEnabled, bool isScreenSharing, Duration duration, String? error | ||||
| }); | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| } | ||||
| /// @nodoc | ||||
| class __$CallStateCopyWithImpl<$Res> | ||||
|     implements _$CallStateCopyWith<$Res> { | ||||
|   __$CallStateCopyWithImpl(this._self, this._then); | ||||
|  | ||||
|   final _CallState _self; | ||||
|   final $Res Function(_CallState) _then; | ||||
|  | ||||
| /// Create a copy of CallState | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @override @pragma('vm:prefer-inline') $Res call({Object? isConnected = null,Object? isMicrophoneEnabled = null,Object? isCameraEnabled = null,Object? isScreenSharing = null,Object? duration = null,Object? error = freezed,}) { | ||||
|   return _then(_CallState( | ||||
| isConnected: null == isConnected ? _self.isConnected : isConnected // ignore: cast_nullable_to_non_nullable | ||||
| as bool,isMicrophoneEnabled: null == isMicrophoneEnabled ? _self.isMicrophoneEnabled : isMicrophoneEnabled // ignore: cast_nullable_to_non_nullable | ||||
| as bool,isCameraEnabled: null == isCameraEnabled ? _self.isCameraEnabled : isCameraEnabled // ignore: cast_nullable_to_non_nullable | ||||
| as bool,isScreenSharing: null == isScreenSharing ? _self.isScreenSharing : isScreenSharing // ignore: cast_nullable_to_non_nullable | ||||
| as bool,duration: null == duration ? _self.duration : duration // ignore: cast_nullable_to_non_nullable | ||||
| as Duration,error: freezed == error ? _self.error : error // ignore: cast_nullable_to_non_nullable | ||||
| as String?, | ||||
|   )); | ||||
| } | ||||
|  | ||||
|  | ||||
| } | ||||
|  | ||||
| /// @nodoc | ||||
| mixin _$CallParticipantLive { | ||||
|  | ||||
|  CallParticipant get participant; Participant get remoteParticipant; | ||||
| /// Create a copy of CallParticipantLive | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @JsonKey(includeFromJson: false, includeToJson: false) | ||||
| @pragma('vm:prefer-inline') | ||||
| $CallParticipantLiveCopyWith<CallParticipantLive> get copyWith => _$CallParticipantLiveCopyWithImpl<CallParticipantLive>(this as CallParticipantLive, _$identity); | ||||
|  | ||||
|  | ||||
|  | ||||
| @override | ||||
| bool operator ==(Object other) { | ||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is CallParticipantLive&&(identical(other.participant, participant) || other.participant == participant)&&(identical(other.remoteParticipant, remoteParticipant) || other.remoteParticipant == remoteParticipant)); | ||||
| } | ||||
|  | ||||
|  | ||||
| @override | ||||
| int get hashCode => Object.hash(runtimeType,participant,remoteParticipant); | ||||
|  | ||||
| @override | ||||
| String toString() { | ||||
|   return 'CallParticipantLive(participant: $participant, remoteParticipant: $remoteParticipant)'; | ||||
| } | ||||
|  | ||||
|  | ||||
| } | ||||
|  | ||||
| /// @nodoc | ||||
| abstract mixin class $CallParticipantLiveCopyWith<$Res>  { | ||||
|   factory $CallParticipantLiveCopyWith(CallParticipantLive value, $Res Function(CallParticipantLive) _then) = _$CallParticipantLiveCopyWithImpl; | ||||
| @useResult | ||||
| $Res call({ | ||||
|  CallParticipant participant, Participant remoteParticipant | ||||
| }); | ||||
|  | ||||
|  | ||||
| $CallParticipantCopyWith<$Res> get participant; | ||||
|  | ||||
| } | ||||
| /// @nodoc | ||||
| class _$CallParticipantLiveCopyWithImpl<$Res> | ||||
|     implements $CallParticipantLiveCopyWith<$Res> { | ||||
|   _$CallParticipantLiveCopyWithImpl(this._self, this._then); | ||||
|  | ||||
|   final CallParticipantLive _self; | ||||
|   final $Res Function(CallParticipantLive) _then; | ||||
|  | ||||
| /// Create a copy of CallParticipantLive | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @pragma('vm:prefer-inline') @override $Res call({Object? participant = null,Object? remoteParticipant = null,}) { | ||||
|   return _then(_self.copyWith( | ||||
| participant: null == participant ? _self.participant : participant // ignore: cast_nullable_to_non_nullable | ||||
| as CallParticipant,remoteParticipant: null == remoteParticipant ? _self.remoteParticipant : remoteParticipant // ignore: cast_nullable_to_non_nullable | ||||
| as Participant, | ||||
|   )); | ||||
| } | ||||
| /// Create a copy of CallParticipantLive | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @override | ||||
| @pragma('vm:prefer-inline') | ||||
| $CallParticipantCopyWith<$Res> get participant { | ||||
|    | ||||
|   return $CallParticipantCopyWith<$Res>(_self.participant, (value) { | ||||
|     return _then(_self.copyWith(participant: value)); | ||||
|   }); | ||||
| } | ||||
| } | ||||
|  | ||||
|  | ||||
| /// @nodoc | ||||
|  | ||||
|  | ||||
| class _CallParticipantLive extends CallParticipantLive { | ||||
|   const _CallParticipantLive({required this.participant, required this.remoteParticipant}): super._(); | ||||
|    | ||||
|  | ||||
| @override final  CallParticipant participant; | ||||
| @override final  Participant remoteParticipant; | ||||
|  | ||||
| /// Create a copy of CallParticipantLive | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @override @JsonKey(includeFromJson: false, includeToJson: false) | ||||
| @pragma('vm:prefer-inline') | ||||
| _$CallParticipantLiveCopyWith<_CallParticipantLive> get copyWith => __$CallParticipantLiveCopyWithImpl<_CallParticipantLive>(this, _$identity); | ||||
|  | ||||
|  | ||||
|  | ||||
| @override | ||||
| bool operator ==(Object other) { | ||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is _CallParticipantLive&&(identical(other.participant, participant) || other.participant == participant)&&(identical(other.remoteParticipant, remoteParticipant) || other.remoteParticipant == remoteParticipant)); | ||||
| } | ||||
|  | ||||
|  | ||||
| @override | ||||
| int get hashCode => Object.hash(runtimeType,participant,remoteParticipant); | ||||
|  | ||||
| @override | ||||
| String toString() { | ||||
|   return 'CallParticipantLive(participant: $participant, remoteParticipant: $remoteParticipant)'; | ||||
| } | ||||
|  | ||||
|  | ||||
| } | ||||
|  | ||||
| /// @nodoc | ||||
| abstract mixin class _$CallParticipantLiveCopyWith<$Res> implements $CallParticipantLiveCopyWith<$Res> { | ||||
|   factory _$CallParticipantLiveCopyWith(_CallParticipantLive value, $Res Function(_CallParticipantLive) _then) = __$CallParticipantLiveCopyWithImpl; | ||||
| @override @useResult | ||||
| $Res call({ | ||||
|  CallParticipant participant, Participant remoteParticipant | ||||
| }); | ||||
|  | ||||
|  | ||||
| @override $CallParticipantCopyWith<$Res> get participant; | ||||
|  | ||||
| } | ||||
| /// @nodoc | ||||
| class __$CallParticipantLiveCopyWithImpl<$Res> | ||||
|     implements _$CallParticipantLiveCopyWith<$Res> { | ||||
|   __$CallParticipantLiveCopyWithImpl(this._self, this._then); | ||||
|  | ||||
|   final _CallParticipantLive _self; | ||||
|   final $Res Function(_CallParticipantLive) _then; | ||||
|  | ||||
| /// Create a copy of CallParticipantLive | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @override @pragma('vm:prefer-inline') $Res call({Object? participant = null,Object? remoteParticipant = null,}) { | ||||
|   return _then(_CallParticipantLive( | ||||
| participant: null == participant ? _self.participant : participant // ignore: cast_nullable_to_non_nullable | ||||
| as CallParticipant,remoteParticipant: null == remoteParticipant ? _self.remoteParticipant : remoteParticipant // ignore: cast_nullable_to_non_nullable | ||||
| as Participant, | ||||
|   )); | ||||
| } | ||||
|  | ||||
| /// Create a copy of CallParticipantLive | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @override | ||||
| @pragma('vm:prefer-inline') | ||||
| $CallParticipantCopyWith<$Res> get participant { | ||||
|    | ||||
|   return $CallParticipantCopyWith<$Res>(_self.participant, (value) { | ||||
|     return _then(_self.copyWith(participant: value)); | ||||
|   }); | ||||
| } | ||||
| } | ||||
|  | ||||
| // dart format on | ||||
							
								
								
									
										27
									
								
								lib/pods/call.g.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								lib/pods/call.g.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | ||||
| // GENERATED CODE - DO NOT MODIFY BY HAND | ||||
|  | ||||
| part of 'call.dart'; | ||||
|  | ||||
| // ************************************************************************** | ||||
| // RiverpodGenerator | ||||
| // ************************************************************************** | ||||
|  | ||||
| String _$callNotifierHash() => r'e04cea314c823e407d49fd616d90d77491232c12'; | ||||
|  | ||||
| /// See also [CallNotifier]. | ||||
| @ProviderFor(CallNotifier) | ||||
| final callNotifierProvider = | ||||
|     AutoDisposeNotifierProvider<CallNotifier, CallState>.internal( | ||||
|       CallNotifier.new, | ||||
|       name: r'callNotifierProvider', | ||||
|       debugGetCreateSourceHash: | ||||
|           const bool.fromEnvironment('dart.vm.product') | ||||
|               ? null | ||||
|               : _$callNotifierHash, | ||||
|       dependencies: null, | ||||
|       allTransitiveDependencies: null, | ||||
|     ); | ||||
|  | ||||
| typedef _$CallNotifier = AutoDisposeNotifier<CallState>; | ||||
| // 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 | ||||
| @@ -1,9 +1,12 @@ | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter_riverpod/flutter_riverpod.dart'; | ||||
| import 'package:freezed_annotation/freezed_annotation.dart'; | ||||
| import 'package:island/pods/theme.dart'; | ||||
| import 'package:riverpod_annotation/riverpod_annotation.dart'; | ||||
| import 'package:shared_preferences/shared_preferences.dart'; | ||||
|  | ||||
| part 'config.freezed.dart'; | ||||
| part 'config.g.dart'; | ||||
|  | ||||
| const kTokenPairStoreKey = 'dyn_user_tk'; | ||||
|  | ||||
| @@ -14,13 +17,8 @@ const kAppbarTransparentStoreKey = 'app_bar_transparent'; | ||||
| const kAppBackgroundStoreKey = 'app_has_background'; | ||||
| const kAppColorSchemeStoreKey = 'app_color_scheme'; | ||||
| const kAppNotifyWithHaptic = 'app_notify_with_haptic'; | ||||
| const kAppExpandPostLink = 'app_expand_post_link'; | ||||
| const kAppExpandChatLink = 'app_expand_chat_link'; | ||||
| const kAppRealmCompactView = 'app_realm_compact_view'; | ||||
| const kAppCustomFonts = 'app_custom_fonts'; | ||||
| const kAppMixedFeed = 'app_mixed_feed'; | ||||
| const kAppAutoTranslate = 'app_auto_translate'; | ||||
| const kAppHideBottomNav = 'app_hide_bottom_nav'; | ||||
| const kAppSoundEffects = 'app_sound_effects'; | ||||
| const kAppAprilFoolFeatures = 'app_april_fool_features'; | ||||
| const kAppWindowSize = 'app_window_size'; | ||||
| @@ -51,75 +49,79 @@ final serverUrlProvider = Provider<String>((ref) { | ||||
| }); | ||||
|  | ||||
| @freezed | ||||
| abstract class AppSettings with _$AppSettings { | ||||
| sealed class AppSettings with _$AppSettings { | ||||
|   const factory AppSettings({ | ||||
|     required bool realmCompactView, | ||||
|     required bool mixedFeed, | ||||
|     required bool autoTranslate, | ||||
|     required bool hideBottomNav, | ||||
|     required bool soundEffects, | ||||
|     required bool aprilFoolFeatures, | ||||
|     required bool enterToSend, | ||||
|     required bool appBarTransparent, | ||||
|     required String? customFonts, | ||||
|     required int? appColorScheme, // The color stored via the int type | ||||
|   }) = _AppSettings; | ||||
| } | ||||
|  | ||||
| class AppSettingsNotifier extends StateNotifier<AppSettings> { | ||||
|   final SharedPreferences prefs; | ||||
|  | ||||
|   AppSettingsNotifier(this.prefs) | ||||
|     : super( | ||||
|         AppSettings( | ||||
|           realmCompactView: prefs.getBool(kAppRealmCompactView) ?? false, | ||||
|           mixedFeed: prefs.getBool(kAppMixedFeed) ?? true, | ||||
|           autoTranslate: prefs.getBool(kAppAutoTranslate) ?? false, | ||||
|           hideBottomNav: prefs.getBool(kAppHideBottomNav) ?? false, | ||||
|           soundEffects: prefs.getBool(kAppSoundEffects) ?? true, | ||||
|           aprilFoolFeatures: prefs.getBool(kAppAprilFoolFeatures) ?? true, | ||||
|           enterToSend: prefs.getBool(kAppEnterToSend) ?? true, | ||||
|         ), | ||||
|       ); | ||||
|  | ||||
|   void setRealmCompactView(bool value) { | ||||
|     prefs.setBool(kAppRealmCompactView, value); | ||||
|     state = state.copyWith(realmCompactView: value); | ||||
|   } | ||||
|  | ||||
|   void setMixedFeed(bool value) { | ||||
|     prefs.setBool(kAppMixedFeed, value); | ||||
|     state = state.copyWith(mixedFeed: value); | ||||
| @riverpod | ||||
| class AppSettingsNotifier extends _$AppSettingsNotifier { | ||||
|   @override | ||||
|   AppSettings build() { | ||||
|     final prefs = ref.watch(sharedPreferencesProvider); | ||||
|     return AppSettings( | ||||
|       autoTranslate: prefs.getBool(kAppAutoTranslate) ?? false, | ||||
|       soundEffects: prefs.getBool(kAppSoundEffects) ?? true, | ||||
|       aprilFoolFeatures: prefs.getBool(kAppAprilFoolFeatures) ?? true, | ||||
|       enterToSend: prefs.getBool(kAppEnterToSend) ?? true, | ||||
|       appBarTransparent: prefs.getBool(kAppbarTransparentStoreKey) ?? false, | ||||
|       customFonts: prefs.getString(kAppCustomFonts), | ||||
|       appColorScheme: prefs.getInt(kAppColorSchemeStoreKey), | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   void setAutoTranslate(bool value) { | ||||
|     final prefs = ref.read(sharedPreferencesProvider); | ||||
|     prefs.setBool(kAppAutoTranslate, value); | ||||
|     state = state.copyWith(autoTranslate: value); | ||||
|   } | ||||
|  | ||||
|   void setHideBottomNav(bool value) { | ||||
|     prefs.setBool(kAppHideBottomNav, value); | ||||
|     state = state.copyWith(hideBottomNav: value); | ||||
|   } | ||||
|  | ||||
|   void setSoundEffects(bool value) { | ||||
|     final prefs = ref.read(sharedPreferencesProvider); | ||||
|     prefs.setBool(kAppSoundEffects, value); | ||||
|     state = state.copyWith(soundEffects: value); | ||||
|   } | ||||
|  | ||||
|   void setAprilFoolFeatures(bool value) { | ||||
|     final prefs = ref.read(sharedPreferencesProvider); | ||||
|     prefs.setBool(kAppAprilFoolFeatures, value); | ||||
|     state = state.copyWith(aprilFoolFeatures: value); | ||||
|   } | ||||
|  | ||||
|   void setEnterToSend(bool value) { | ||||
|     final prefs = ref.read(sharedPreferencesProvider); | ||||
|     prefs.setBool(kAppEnterToSend, value); | ||||
|     state = state.copyWith(enterToSend: value); | ||||
|   } | ||||
| } | ||||
|  | ||||
| final appSettingsProvider = | ||||
|     StateNotifierProvider<AppSettingsNotifier, AppSettings>((ref) { | ||||
|       final prefs = ref.watch(sharedPreferencesProvider); | ||||
|       return AppSettingsNotifier(prefs); | ||||
|     }); | ||||
|   void setAppBarTransparent(bool value) { | ||||
|     final prefs = ref.read(sharedPreferencesProvider); | ||||
|     prefs.setBool(kAppbarTransparentStoreKey, value); | ||||
|     state = state.copyWith(appBarTransparent: value); | ||||
|     ref.read(themeProvider.notifier).reloadTheme(); | ||||
|   } | ||||
|  | ||||
|   void setCustomFonts(String? value) { | ||||
|     final prefs = ref.read(sharedPreferencesProvider); | ||||
|     prefs.setString(kAppCustomFonts, value ?? ''); | ||||
|     state = state.copyWith(customFonts: value); | ||||
|     ref.read(themeProvider.notifier).reloadTheme(); | ||||
|   } | ||||
|  | ||||
|   void setAppColorScheme(int? value) { | ||||
|     final prefs = ref.read(sharedPreferencesProvider); | ||||
|     prefs.setInt(kAppColorSchemeStoreKey, value ?? 0); | ||||
|     state = state.copyWith(appColorScheme: value); | ||||
|     ref.read(themeProvider.notifier).reloadTheme(); | ||||
|   } | ||||
| } | ||||
|  | ||||
| final updateInfoProvider = | ||||
|     StateNotifierProvider<UpdateInfoNotifier, (String?, String?)>((ref) { | ||||
|   | ||||
| @@ -15,7 +15,7 @@ T _$identity<T>(T value) => value; | ||||
| /// @nodoc | ||||
| mixin _$AppSettings { | ||||
|  | ||||
|  bool get realmCompactView; bool get mixedFeed; bool get autoTranslate; bool get hideBottomNav; bool get soundEffects; bool get aprilFoolFeatures; bool get enterToSend; | ||||
|  bool get autoTranslate; bool get soundEffects; bool get aprilFoolFeatures; bool get enterToSend; bool get appBarTransparent; String? get customFonts; int? get appColorScheme; | ||||
| /// Create a copy of AppSettings | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @JsonKey(includeFromJson: false, includeToJson: false) | ||||
| @@ -26,16 +26,16 @@ $AppSettingsCopyWith<AppSettings> get copyWith => _$AppSettingsCopyWithImpl<AppS | ||||
|  | ||||
| @override | ||||
| bool operator ==(Object other) { | ||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is AppSettings&&(identical(other.realmCompactView, realmCompactView) || other.realmCompactView == realmCompactView)&&(identical(other.mixedFeed, mixedFeed) || other.mixedFeed == mixedFeed)&&(identical(other.autoTranslate, autoTranslate) || other.autoTranslate == autoTranslate)&&(identical(other.hideBottomNav, hideBottomNav) || other.hideBottomNav == hideBottomNav)&&(identical(other.soundEffects, soundEffects) || other.soundEffects == soundEffects)&&(identical(other.aprilFoolFeatures, aprilFoolFeatures) || other.aprilFoolFeatures == aprilFoolFeatures)&&(identical(other.enterToSend, enterToSend) || other.enterToSend == enterToSend)); | ||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is AppSettings&&(identical(other.autoTranslate, autoTranslate) || other.autoTranslate == autoTranslate)&&(identical(other.soundEffects, soundEffects) || other.soundEffects == soundEffects)&&(identical(other.aprilFoolFeatures, aprilFoolFeatures) || other.aprilFoolFeatures == aprilFoolFeatures)&&(identical(other.enterToSend, enterToSend) || other.enterToSend == enterToSend)&&(identical(other.appBarTransparent, appBarTransparent) || other.appBarTransparent == appBarTransparent)&&(identical(other.customFonts, customFonts) || other.customFonts == customFonts)&&(identical(other.appColorScheme, appColorScheme) || other.appColorScheme == appColorScheme)); | ||||
| } | ||||
|  | ||||
|  | ||||
| @override | ||||
| int get hashCode => Object.hash(runtimeType,realmCompactView,mixedFeed,autoTranslate,hideBottomNav,soundEffects,aprilFoolFeatures,enterToSend); | ||||
| int get hashCode => Object.hash(runtimeType,autoTranslate,soundEffects,aprilFoolFeatures,enterToSend,appBarTransparent,customFonts,appColorScheme); | ||||
|  | ||||
| @override | ||||
| String toString() { | ||||
|   return 'AppSettings(realmCompactView: $realmCompactView, mixedFeed: $mixedFeed, autoTranslate: $autoTranslate, hideBottomNav: $hideBottomNav, soundEffects: $soundEffects, aprilFoolFeatures: $aprilFoolFeatures, enterToSend: $enterToSend)'; | ||||
|   return 'AppSettings(autoTranslate: $autoTranslate, soundEffects: $soundEffects, aprilFoolFeatures: $aprilFoolFeatures, enterToSend: $enterToSend, appBarTransparent: $appBarTransparent, customFonts: $customFonts, appColorScheme: $appColorScheme)'; | ||||
| } | ||||
|  | ||||
|  | ||||
| @@ -46,7 +46,7 @@ abstract mixin class $AppSettingsCopyWith<$Res>  { | ||||
|   factory $AppSettingsCopyWith(AppSettings value, $Res Function(AppSettings) _then) = _$AppSettingsCopyWithImpl; | ||||
| @useResult | ||||
| $Res call({ | ||||
|  bool realmCompactView, bool mixedFeed, bool autoTranslate, bool hideBottomNav, bool soundEffects, bool aprilFoolFeatures, bool enterToSend | ||||
|  bool autoTranslate, bool soundEffects, bool aprilFoolFeatures, bool enterToSend, bool appBarTransparent, String? customFonts, int? appColorScheme | ||||
| }); | ||||
|  | ||||
|  | ||||
| @@ -63,16 +63,16 @@ class _$AppSettingsCopyWithImpl<$Res> | ||||
|  | ||||
| /// Create a copy of AppSettings | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @pragma('vm:prefer-inline') @override $Res call({Object? realmCompactView = null,Object? mixedFeed = null,Object? autoTranslate = null,Object? hideBottomNav = null,Object? soundEffects = null,Object? aprilFoolFeatures = null,Object? enterToSend = null,}) { | ||||
| @pragma('vm:prefer-inline') @override $Res call({Object? autoTranslate = null,Object? soundEffects = null,Object? aprilFoolFeatures = null,Object? enterToSend = null,Object? appBarTransparent = null,Object? customFonts = freezed,Object? appColorScheme = freezed,}) { | ||||
|   return _then(_self.copyWith( | ||||
| realmCompactView: null == realmCompactView ? _self.realmCompactView : realmCompactView // ignore: cast_nullable_to_non_nullable | ||||
| as bool,mixedFeed: null == mixedFeed ? _self.mixedFeed : mixedFeed // ignore: cast_nullable_to_non_nullable | ||||
| as bool,autoTranslate: null == autoTranslate ? _self.autoTranslate : autoTranslate // ignore: cast_nullable_to_non_nullable | ||||
| as bool,hideBottomNav: null == hideBottomNav ? _self.hideBottomNav : hideBottomNav // ignore: cast_nullable_to_non_nullable | ||||
| autoTranslate: null == autoTranslate ? _self.autoTranslate : autoTranslate // ignore: cast_nullable_to_non_nullable | ||||
| as bool,soundEffects: null == soundEffects ? _self.soundEffects : soundEffects // ignore: cast_nullable_to_non_nullable | ||||
| as bool,aprilFoolFeatures: null == aprilFoolFeatures ? _self.aprilFoolFeatures : aprilFoolFeatures // ignore: cast_nullable_to_non_nullable | ||||
| as bool,enterToSend: null == enterToSend ? _self.enterToSend : enterToSend // ignore: cast_nullable_to_non_nullable | ||||
| as bool, | ||||
| as bool,appBarTransparent: null == appBarTransparent ? _self.appBarTransparent : appBarTransparent // ignore: cast_nullable_to_non_nullable | ||||
| as bool,customFonts: freezed == customFonts ? _self.customFonts : customFonts // ignore: cast_nullable_to_non_nullable | ||||
| as String?,appColorScheme: freezed == appColorScheme ? _self.appColorScheme : appColorScheme // ignore: cast_nullable_to_non_nullable | ||||
| as int?, | ||||
|   )); | ||||
| } | ||||
|  | ||||
| @@ -83,16 +83,16 @@ as bool, | ||||
|  | ||||
|  | ||||
| class _AppSettings implements AppSettings { | ||||
|   const _AppSettings({required this.realmCompactView, required this.mixedFeed, required this.autoTranslate, required this.hideBottomNav, required this.soundEffects, required this.aprilFoolFeatures, required this.enterToSend}); | ||||
|   const _AppSettings({required this.autoTranslate, required this.soundEffects, required this.aprilFoolFeatures, required this.enterToSend, required this.appBarTransparent, required this.customFonts, required this.appColorScheme}); | ||||
|    | ||||
|  | ||||
| @override final  bool realmCompactView; | ||||
| @override final  bool mixedFeed; | ||||
| @override final  bool autoTranslate; | ||||
| @override final  bool hideBottomNav; | ||||
| @override final  bool soundEffects; | ||||
| @override final  bool aprilFoolFeatures; | ||||
| @override final  bool enterToSend; | ||||
| @override final  bool appBarTransparent; | ||||
| @override final  String? customFonts; | ||||
| @override final  int? appColorScheme; | ||||
|  | ||||
| /// Create a copy of AppSettings | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @@ -104,16 +104,16 @@ _$AppSettingsCopyWith<_AppSettings> get copyWith => __$AppSettingsCopyWithImpl<_ | ||||
|  | ||||
| @override | ||||
| bool operator ==(Object other) { | ||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is _AppSettings&&(identical(other.realmCompactView, realmCompactView) || other.realmCompactView == realmCompactView)&&(identical(other.mixedFeed, mixedFeed) || other.mixedFeed == mixedFeed)&&(identical(other.autoTranslate, autoTranslate) || other.autoTranslate == autoTranslate)&&(identical(other.hideBottomNav, hideBottomNav) || other.hideBottomNav == hideBottomNav)&&(identical(other.soundEffects, soundEffects) || other.soundEffects == soundEffects)&&(identical(other.aprilFoolFeatures, aprilFoolFeatures) || other.aprilFoolFeatures == aprilFoolFeatures)&&(identical(other.enterToSend, enterToSend) || other.enterToSend == enterToSend)); | ||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is _AppSettings&&(identical(other.autoTranslate, autoTranslate) || other.autoTranslate == autoTranslate)&&(identical(other.soundEffects, soundEffects) || other.soundEffects == soundEffects)&&(identical(other.aprilFoolFeatures, aprilFoolFeatures) || other.aprilFoolFeatures == aprilFoolFeatures)&&(identical(other.enterToSend, enterToSend) || other.enterToSend == enterToSend)&&(identical(other.appBarTransparent, appBarTransparent) || other.appBarTransparent == appBarTransparent)&&(identical(other.customFonts, customFonts) || other.customFonts == customFonts)&&(identical(other.appColorScheme, appColorScheme) || other.appColorScheme == appColorScheme)); | ||||
| } | ||||
|  | ||||
|  | ||||
| @override | ||||
| int get hashCode => Object.hash(runtimeType,realmCompactView,mixedFeed,autoTranslate,hideBottomNav,soundEffects,aprilFoolFeatures,enterToSend); | ||||
| int get hashCode => Object.hash(runtimeType,autoTranslate,soundEffects,aprilFoolFeatures,enterToSend,appBarTransparent,customFonts,appColorScheme); | ||||
|  | ||||
| @override | ||||
| String toString() { | ||||
|   return 'AppSettings(realmCompactView: $realmCompactView, mixedFeed: $mixedFeed, autoTranslate: $autoTranslate, hideBottomNav: $hideBottomNav, soundEffects: $soundEffects, aprilFoolFeatures: $aprilFoolFeatures, enterToSend: $enterToSend)'; | ||||
|   return 'AppSettings(autoTranslate: $autoTranslate, soundEffects: $soundEffects, aprilFoolFeatures: $aprilFoolFeatures, enterToSend: $enterToSend, appBarTransparent: $appBarTransparent, customFonts: $customFonts, appColorScheme: $appColorScheme)'; | ||||
| } | ||||
|  | ||||
|  | ||||
| @@ -124,7 +124,7 @@ abstract mixin class _$AppSettingsCopyWith<$Res> implements $AppSettingsCopyWith | ||||
|   factory _$AppSettingsCopyWith(_AppSettings value, $Res Function(_AppSettings) _then) = __$AppSettingsCopyWithImpl; | ||||
| @override @useResult | ||||
| $Res call({ | ||||
|  bool realmCompactView, bool mixedFeed, bool autoTranslate, bool hideBottomNav, bool soundEffects, bool aprilFoolFeatures, bool enterToSend | ||||
|  bool autoTranslate, bool soundEffects, bool aprilFoolFeatures, bool enterToSend, bool appBarTransparent, String? customFonts, int? appColorScheme | ||||
| }); | ||||
|  | ||||
|  | ||||
| @@ -141,16 +141,16 @@ class __$AppSettingsCopyWithImpl<$Res> | ||||
|  | ||||
| /// Create a copy of AppSettings | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @override @pragma('vm:prefer-inline') $Res call({Object? realmCompactView = null,Object? mixedFeed = null,Object? autoTranslate = null,Object? hideBottomNav = null,Object? soundEffects = null,Object? aprilFoolFeatures = null,Object? enterToSend = null,}) { | ||||
| @override @pragma('vm:prefer-inline') $Res call({Object? autoTranslate = null,Object? soundEffects = null,Object? aprilFoolFeatures = null,Object? enterToSend = null,Object? appBarTransparent = null,Object? customFonts = freezed,Object? appColorScheme = freezed,}) { | ||||
|   return _then(_AppSettings( | ||||
| realmCompactView: null == realmCompactView ? _self.realmCompactView : realmCompactView // ignore: cast_nullable_to_non_nullable | ||||
| as bool,mixedFeed: null == mixedFeed ? _self.mixedFeed : mixedFeed // ignore: cast_nullable_to_non_nullable | ||||
| as bool,autoTranslate: null == autoTranslate ? _self.autoTranslate : autoTranslate // ignore: cast_nullable_to_non_nullable | ||||
| as bool,hideBottomNav: null == hideBottomNav ? _self.hideBottomNav : hideBottomNav // ignore: cast_nullable_to_non_nullable | ||||
| autoTranslate: null == autoTranslate ? _self.autoTranslate : autoTranslate // ignore: cast_nullable_to_non_nullable | ||||
| as bool,soundEffects: null == soundEffects ? _self.soundEffects : soundEffects // ignore: cast_nullable_to_non_nullable | ||||
| as bool,aprilFoolFeatures: null == aprilFoolFeatures ? _self.aprilFoolFeatures : aprilFoolFeatures // ignore: cast_nullable_to_non_nullable | ||||
| as bool,enterToSend: null == enterToSend ? _self.enterToSend : enterToSend // ignore: cast_nullable_to_non_nullable | ||||
| as bool, | ||||
| as bool,appBarTransparent: null == appBarTransparent ? _self.appBarTransparent : appBarTransparent // ignore: cast_nullable_to_non_nullable | ||||
| as bool,customFonts: freezed == customFonts ? _self.customFonts : customFonts // ignore: cast_nullable_to_non_nullable | ||||
| as String?,appColorScheme: freezed == appColorScheme ? _self.appColorScheme : appColorScheme // ignore: cast_nullable_to_non_nullable | ||||
| as int?, | ||||
|   )); | ||||
| } | ||||
|  | ||||
|   | ||||
							
								
								
									
										28
									
								
								lib/pods/config.g.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								lib/pods/config.g.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | ||||
| // GENERATED CODE - DO NOT MODIFY BY HAND | ||||
|  | ||||
| part of 'config.dart'; | ||||
|  | ||||
| // ************************************************************************** | ||||
| // RiverpodGenerator | ||||
| // ************************************************************************** | ||||
|  | ||||
| String _$appSettingsNotifierHash() => | ||||
|     r'4f727d448ee17a87b5698b8e36ef67521655406c'; | ||||
|  | ||||
| /// See also [AppSettingsNotifier]. | ||||
| @ProviderFor(AppSettingsNotifier) | ||||
| final appSettingsNotifierProvider = | ||||
|     AutoDisposeNotifierProvider<AppSettingsNotifier, AppSettings>.internal( | ||||
|       AppSettingsNotifier.new, | ||||
|       name: r'appSettingsNotifierProvider', | ||||
|       debugGetCreateSourceHash: | ||||
|           const bool.fromEnvironment('dart.vm.product') | ||||
|               ? null | ||||
|               : _$appSettingsNotifierHash, | ||||
|       dependencies: null, | ||||
|       allTransitiveDependencies: null, | ||||
|     ); | ||||
|  | ||||
| typedef _$AppSettingsNotifier = AutoDisposeNotifier<AppSettings>; | ||||
| // 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 | ||||
							
								
								
									
										56
									
								
								lib/pods/event_calendar.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								lib/pods/event_calendar.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,56 @@ | ||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||
| import 'package:island/models/activity.dart'; | ||||
| import 'package:island/pods/network.dart'; | ||||
| import 'package:riverpod_annotation/riverpod_annotation.dart'; | ||||
|  | ||||
| part 'event_calendar.g.dart'; | ||||
|  | ||||
| /// Query parameters for fetching event calendar data | ||||
| class EventCalendarQuery { | ||||
|   /// Username to fetch calendar for, null means current user ('me') | ||||
|   final String? uname; | ||||
|    | ||||
|   /// Year to fetch calendar for | ||||
|   final int year; | ||||
|    | ||||
|   /// Month to fetch calendar for | ||||
|   final int month; | ||||
|  | ||||
|   const EventCalendarQuery({ | ||||
|     required this.uname, | ||||
|     required this.year, | ||||
|     required this.month, | ||||
|   }); | ||||
|  | ||||
|   @override | ||||
|   bool operator ==(Object other) => | ||||
|       identical(this, other) || | ||||
|       other is EventCalendarQuery && | ||||
|           runtimeType == other.runtimeType && | ||||
|           uname == other.uname && | ||||
|           year == other.year && | ||||
|           month == other.month; | ||||
|  | ||||
|   @override | ||||
|   int get hashCode => uname.hashCode ^ year.hashCode ^ month.hashCode; | ||||
| } | ||||
|  | ||||
| /// Provider for fetching event calendar data | ||||
| /// This can be used anywhere in the app where calendar data is needed | ||||
| @riverpod | ||||
| Future<List<SnEventCalendarEntry>> eventCalendar( | ||||
|   Ref ref, | ||||
|   EventCalendarQuery query, | ||||
| ) async { | ||||
|   final client = ref.watch(apiClientProvider); | ||||
|   final resp = await client.get('/accounts/${query.uname ?? 'me'}/calendar',  | ||||
|     queryParameters: { | ||||
|       'year': query.year, | ||||
|       'month': query.month, | ||||
|     }, | ||||
|   ); | ||||
|   return resp.data | ||||
|       .map((e) => SnEventCalendarEntry.fromJson(e)) | ||||
|       .cast<SnEventCalendarEntry>() | ||||
|       .toList(); | ||||
| } | ||||
| @@ -6,8 +6,7 @@ part of 'event_calendar.dart'; | ||||
| // RiverpodGenerator | ||||
| // ************************************************************************** | ||||
| 
 | ||||
| String _$accountEventCalendarHash() => | ||||
|     r'57405caaf53a83d121b6bb4b70540134fb581525'; | ||||
| String _$eventCalendarHash() => r'6f2454404fa8660b96334d654490e1a40ee53e10'; | ||||
| 
 | ||||
| /// Copied from Dart SDK | ||||
| class _SystemHash { | ||||
| @@ -30,24 +29,36 @@ class _SystemHash { | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /// See also [accountEventCalendar]. | ||||
| @ProviderFor(accountEventCalendar) | ||||
| const accountEventCalendarProvider = AccountEventCalendarFamily(); | ||||
| /// Provider for fetching event calendar data | ||||
| /// This can be used anywhere in the app where calendar data is needed | ||||
| /// | ||||
| /// Copied from [eventCalendar]. | ||||
| @ProviderFor(eventCalendar) | ||||
| const eventCalendarProvider = EventCalendarFamily(); | ||||
| 
 | ||||
| /// See also [accountEventCalendar]. | ||||
| class AccountEventCalendarFamily | ||||
| /// Provider for fetching event calendar data | ||||
| /// This can be used anywhere in the app where calendar data is needed | ||||
| /// | ||||
| /// Copied from [eventCalendar]. | ||||
| class EventCalendarFamily | ||||
|     extends Family<AsyncValue<List<SnEventCalendarEntry>>> { | ||||
|   /// See also [accountEventCalendar]. | ||||
|   const AccountEventCalendarFamily(); | ||||
|   /// Provider for fetching event calendar data | ||||
|   /// This can be used anywhere in the app where calendar data is needed | ||||
|   /// | ||||
|   /// Copied from [eventCalendar]. | ||||
|   const EventCalendarFamily(); | ||||
| 
 | ||||
|   /// See also [accountEventCalendar]. | ||||
|   AccountEventCalendarProvider call(EventCalendarQuery query) { | ||||
|     return AccountEventCalendarProvider(query); | ||||
|   /// Provider for fetching event calendar data | ||||
|   /// This can be used anywhere in the app where calendar data is needed | ||||
|   /// | ||||
|   /// Copied from [eventCalendar]. | ||||
|   EventCalendarProvider call(EventCalendarQuery query) { | ||||
|     return EventCalendarProvider(query); | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   AccountEventCalendarProvider getProviderOverride( | ||||
|     covariant AccountEventCalendarProvider provider, | ||||
|   EventCalendarProvider getProviderOverride( | ||||
|     covariant EventCalendarProvider provider, | ||||
|   ) { | ||||
|     return call(provider.query); | ||||
|   } | ||||
| @@ -64,29 +75,35 @@ class AccountEventCalendarFamily | ||||
|       _allTransitiveDependencies; | ||||
| 
 | ||||
|   @override | ||||
|   String? get name => r'accountEventCalendarProvider'; | ||||
|   String? get name => r'eventCalendarProvider'; | ||||
| } | ||||
| 
 | ||||
| /// See also [accountEventCalendar]. | ||||
| class AccountEventCalendarProvider | ||||
| /// Provider for fetching event calendar data | ||||
| /// This can be used anywhere in the app where calendar data is needed | ||||
| /// | ||||
| /// Copied from [eventCalendar]. | ||||
| class EventCalendarProvider | ||||
|     extends AutoDisposeFutureProvider<List<SnEventCalendarEntry>> { | ||||
|   /// See also [accountEventCalendar]. | ||||
|   AccountEventCalendarProvider(EventCalendarQuery query) | ||||
|   /// Provider for fetching event calendar data | ||||
|   /// This can be used anywhere in the app where calendar data is needed | ||||
|   /// | ||||
|   /// Copied from [eventCalendar]. | ||||
|   EventCalendarProvider(EventCalendarQuery query) | ||||
|     : this._internal( | ||||
|         (ref) => accountEventCalendar(ref as AccountEventCalendarRef, query), | ||||
|         from: accountEventCalendarProvider, | ||||
|         name: r'accountEventCalendarProvider', | ||||
|         (ref) => eventCalendar(ref as EventCalendarRef, query), | ||||
|         from: eventCalendarProvider, | ||||
|         name: r'eventCalendarProvider', | ||||
|         debugGetCreateSourceHash: | ||||
|             const bool.fromEnvironment('dart.vm.product') | ||||
|                 ? null | ||||
|                 : _$accountEventCalendarHash, | ||||
|         dependencies: AccountEventCalendarFamily._dependencies, | ||||
|                 : _$eventCalendarHash, | ||||
|         dependencies: EventCalendarFamily._dependencies, | ||||
|         allTransitiveDependencies: | ||||
|             AccountEventCalendarFamily._allTransitiveDependencies, | ||||
|             EventCalendarFamily._allTransitiveDependencies, | ||||
|         query: query, | ||||
|       ); | ||||
| 
 | ||||
|   AccountEventCalendarProvider._internal( | ||||
|   EventCalendarProvider._internal( | ||||
|     super._createNotifier, { | ||||
|     required super.name, | ||||
|     required super.dependencies, | ||||
| @@ -100,15 +117,13 @@ class AccountEventCalendarProvider | ||||
| 
 | ||||
|   @override | ||||
|   Override overrideWith( | ||||
|     FutureOr<List<SnEventCalendarEntry>> Function( | ||||
|       AccountEventCalendarRef provider, | ||||
|     ) | ||||
|     FutureOr<List<SnEventCalendarEntry>> Function(EventCalendarRef provider) | ||||
|     create, | ||||
|   ) { | ||||
|     return ProviderOverride( | ||||
|       origin: this, | ||||
|       override: AccountEventCalendarProvider._internal( | ||||
|         (ref) => create(ref as AccountEventCalendarRef), | ||||
|       override: EventCalendarProvider._internal( | ||||
|         (ref) => create(ref as EventCalendarRef), | ||||
|         from: from, | ||||
|         name: null, | ||||
|         dependencies: null, | ||||
| @@ -121,12 +136,12 @@ class AccountEventCalendarProvider | ||||
| 
 | ||||
|   @override | ||||
|   AutoDisposeFutureProviderElement<List<SnEventCalendarEntry>> createElement() { | ||||
|     return _AccountEventCalendarProviderElement(this); | ||||
|     return _EventCalendarProviderElement(this); | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   bool operator ==(Object other) { | ||||
|     return other is AccountEventCalendarProvider && other.query == query; | ||||
|     return other is EventCalendarProvider && other.query == query; | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
| @@ -140,20 +155,19 @@ class AccountEventCalendarProvider | ||||
| 
 | ||||
| @Deprecated('Will be removed in 3.0. Use Ref instead') | ||||
| // ignore: unused_element | ||||
| mixin AccountEventCalendarRef | ||||
| mixin EventCalendarRef | ||||
|     on AutoDisposeFutureProviderRef<List<SnEventCalendarEntry>> { | ||||
|   /// The parameter `query` of this provider. | ||||
|   EventCalendarQuery get query; | ||||
| } | ||||
| 
 | ||||
| class _AccountEventCalendarProviderElement | ||||
| class _EventCalendarProviderElement | ||||
|     extends AutoDisposeFutureProviderElement<List<SnEventCalendarEntry>> | ||||
|     with AccountEventCalendarRef { | ||||
|   _AccountEventCalendarProviderElement(super.provider); | ||||
|     with EventCalendarRef { | ||||
|   _EventCalendarProviderElement(super.provider); | ||||
| 
 | ||||
|   @override | ||||
|   EventCalendarQuery get query => | ||||
|       (origin as AccountEventCalendarProvider).query; | ||||
|   EventCalendarQuery get query => (origin as EventCalendarProvider).query; | ||||
| } | ||||
| 
 | ||||
| // ignore_for_file: type=lint | ||||
| @@ -1,6 +1,5 @@ | ||||
| import 'dart:async'; | ||||
| import 'dart:convert'; | ||||
| import 'dart:developer'; | ||||
| import 'dart:io'; | ||||
|  | ||||
| import 'package:flutter/foundation.dart'; | ||||
| @@ -17,27 +16,42 @@ import 'config.dart'; | ||||
| final imagePickerProvider = Provider((ref) => ImagePicker()); | ||||
|  | ||||
| final userAgentProvider = FutureProvider<String>((ref) async { | ||||
|   // Helper function to sanitize strings for HTTP headers | ||||
|   String sanitizeForHeader(String input) { | ||||
|     // Remove or replace characters that are not allowed in HTTP headers | ||||
|     // Keep only ASCII printable characters (32-126) and replace others with underscore | ||||
|     return input.runes.map((rune) { | ||||
|       if (rune >= 32 && rune <= 126) { | ||||
|         return String.fromCharCode(rune); | ||||
|       } else { | ||||
|         return '_'; | ||||
|       } | ||||
|     }).join(); | ||||
|   } | ||||
|  | ||||
|   final String platformInfo; | ||||
|   if (kIsWeb) { | ||||
|     final deviceInfo = await DeviceInfoPlugin().webBrowserInfo; | ||||
|     platformInfo = 'Web; ${deviceInfo.vendor}'; | ||||
|     platformInfo = 'Web; ${sanitizeForHeader(deviceInfo.vendor ?? 'Unknown')}'; | ||||
|   } else if (Platform.isAndroid) { | ||||
|     final deviceInfo = await DeviceInfoPlugin().androidInfo; | ||||
|     platformInfo = | ||||
|         'Android; ${deviceInfo.brand} ${deviceInfo.model}; ${deviceInfo.id}'; | ||||
|         'Android; ${sanitizeForHeader(deviceInfo.brand)} ${sanitizeForHeader(deviceInfo.model)}; ${sanitizeForHeader(deviceInfo.id)}'; | ||||
|   } else if (Platform.isIOS) { | ||||
|     final deviceInfo = await DeviceInfoPlugin().iosInfo; | ||||
|     platformInfo = 'iOS; ${deviceInfo.model}; ${deviceInfo.name}'; | ||||
|     platformInfo = | ||||
|         'iOS; ${sanitizeForHeader(deviceInfo.model)}; ${sanitizeForHeader(deviceInfo.name)}'; | ||||
|   } else if (Platform.isMacOS) { | ||||
|     final deviceInfo = await DeviceInfoPlugin().macOsInfo; | ||||
|     platformInfo = 'MacOS; ${deviceInfo.model}; ${deviceInfo.hostName}'; | ||||
|     platformInfo = | ||||
|         'MacOS; ${sanitizeForHeader(deviceInfo.model)}; ${sanitizeForHeader(deviceInfo.hostName)}'; | ||||
|   } else if (Platform.isWindows) { | ||||
|     final deviceInfo = await DeviceInfoPlugin().windowsInfo; | ||||
|     platformInfo = | ||||
|         'Windows NT; ${deviceInfo.productName}; ${deviceInfo.computerName}'; | ||||
|         'Windows NT; ${sanitizeForHeader(deviceInfo.productName)}; ${sanitizeForHeader(deviceInfo.computerName)}'; | ||||
|   } else if (Platform.isLinux) { | ||||
|     final deviceInfo = await DeviceInfoPlugin().linuxInfo; | ||||
|     platformInfo = 'Linux; ${deviceInfo.prettyName}'; | ||||
|     platformInfo = 'Linux; ${sanitizeForHeader(deviceInfo.prettyName)}'; | ||||
|   } else { | ||||
|     platformInfo = 'Unknown'; | ||||
|   } | ||||
| @@ -68,16 +82,9 @@ final apiClientProvider = Provider<Dio>((ref) { | ||||
|         RequestInterceptorHandler handler, | ||||
|       ) async { | ||||
|         try { | ||||
|           final atk = await getFreshAtk( | ||||
|             ref.watch(tokenPairProvider), | ||||
|             ref.watch(serverUrlProvider), | ||||
|             onRefreshed: (atk, rtk) { | ||||
|               setTokenPair(ref.watch(sharedPreferencesProvider), atk, rtk); | ||||
|               ref.invalidate(tokenPairProvider); | ||||
|             }, | ||||
|           ); | ||||
|           if (atk != null) { | ||||
|             options.headers['Authorization'] = 'Bearer $atk'; | ||||
|           final token = await getToken(ref.watch(tokenProvider)); | ||||
|           if (token != null) { | ||||
|             options.headers['Authorization'] = 'AtField $token'; | ||||
|           } | ||||
|         } catch (err) { | ||||
|           // ignore | ||||
| @@ -95,105 +102,21 @@ final apiClientProvider = Provider<Dio>((ref) { | ||||
|   return dio; | ||||
| }); | ||||
|  | ||||
| final tokenPairProvider = Provider<AppTokenPair?>((ref) { | ||||
| final tokenProvider = Provider<AppToken?>((ref) { | ||||
|   final prefs = ref.watch(sharedPreferencesProvider); | ||||
|   final tkPairString = prefs.getString(kTokenPairStoreKey); | ||||
|   if (tkPairString == null) return null; | ||||
|   return AppTokenPair.fromJson(jsonDecode(tkPairString)); | ||||
|   final tokenString = prefs.getString(kTokenPairStoreKey); | ||||
|   if (tokenString == null) return null; | ||||
|   return AppToken.fromJson(jsonDecode(tokenString)); | ||||
| }); | ||||
|  | ||||
| Future<(String, String)?> refreshToken(String baseUrl, String? rtk) async { | ||||
|   if (rtk == null) return null; | ||||
| // Token refresh functionality removed as per backend changes | ||||
|  | ||||
|   final dio = Dio(); | ||||
|   dio.options.baseUrl = baseUrl; | ||||
|  | ||||
|   final resp = await dio.post( | ||||
|     '/auth/token', | ||||
|     data: {'grant_type': 'refresh_token', 'refresh_token': rtk}, | ||||
|   ); | ||||
|  | ||||
|   final String atk = resp.data['access_token']; | ||||
|   final String nRtk = resp.data['refresh_token']; | ||||
|  | ||||
|   return (atk, nRtk); | ||||
| Future<String?> getToken(AppToken? token) async { | ||||
|   return token?.token; | ||||
| } | ||||
|  | ||||
| Completer<String?>? _refreshCompleter; | ||||
|  | ||||
| Future<String?> getFreshAtk( | ||||
|   AppTokenPair? tkPair, | ||||
|   String baseUrl, { | ||||
|   Function(String, String)? onRefreshed, | ||||
| }) async { | ||||
|   var atk = tkPair?.accessToken; | ||||
|   var rtk = tkPair?.refreshToken; | ||||
|  | ||||
|   if (_refreshCompleter != null) { | ||||
|     return await _refreshCompleter!.future; | ||||
|   } else { | ||||
|     _refreshCompleter = Completer<String?>(); | ||||
|   } | ||||
|  | ||||
|   try { | ||||
|     if (atk != null) { | ||||
|       final atkParts = atk.split('.'); | ||||
|       if (atkParts.length != 3) { | ||||
|         throw Exception('invalid format of access token'); | ||||
|       } | ||||
|  | ||||
|       var rawPayload = atkParts[1].replaceAll('-', '+').replaceAll('_', '/'); | ||||
|       switch (rawPayload.length % 4) { | ||||
|         case 0: | ||||
|           break; | ||||
|         case 2: | ||||
|           rawPayload += '=='; | ||||
|           break; | ||||
|         case 3: | ||||
|           rawPayload += '='; | ||||
|           break; | ||||
|         default: | ||||
|           throw Exception('illegal format of access token payload'); | ||||
|       } | ||||
|  | ||||
|       final b64 = utf8.fuse(base64Url); | ||||
|       final payload = b64.decode(rawPayload); | ||||
|       final exp = jsonDecode(payload)['exp']; | ||||
|       if (exp <= DateTime.now().millisecondsSinceEpoch ~/ 1000) { | ||||
|         log('[Auth] Access token need refresh, doing it at ${DateTime.now()}'); | ||||
|         final result = await refreshToken(baseUrl, rtk); | ||||
|         if (result == null) { | ||||
|           atk = null; | ||||
|         } else { | ||||
|           onRefreshed?.call(result.$1, result.$2); | ||||
|           atk = result.$1; | ||||
|         } | ||||
|       } | ||||
|  | ||||
|       if (atk != null) { | ||||
|         _refreshCompleter!.complete(atk); | ||||
|         return atk; | ||||
|       } else { | ||||
|         log('[Auth] Access token refresh failed...'); | ||||
|         _refreshCompleter!.complete(null); | ||||
|       } | ||||
|     } | ||||
|   } catch (err) { | ||||
|     log('[Auth] Failed to authenticate user... $err'); | ||||
|     _refreshCompleter!.completeError(err); | ||||
|   } finally { | ||||
|     _refreshCompleter = null; | ||||
|   } | ||||
|  | ||||
|   return null; | ||||
| } | ||||
|  | ||||
| Future<void> setTokenPair( | ||||
|   SharedPreferences prefs, | ||||
|   String atk, | ||||
|   String rtk, | ||||
| ) async { | ||||
|   final tkPair = AppTokenPair(accessToken: atk, refreshToken: rtk); | ||||
|   final tkPairString = jsonEncode(tkPair); | ||||
|   prefs.setString(kTokenPairStoreKey, tkPairString); | ||||
| Future<void> setToken(SharedPreferences prefs, String token) async { | ||||
|   final appToken = AppToken(token: token); | ||||
|   final tokenString = jsonEncode(appToken); | ||||
|   prefs.setString(kTokenPairStoreKey, tokenString); | ||||
| } | ||||
|   | ||||
| @@ -13,7 +13,7 @@ class UserInfoNotifier extends StateNotifier<AsyncValue<SnAccount?>> { | ||||
|  | ||||
|   Future<String?> getAccessToken() async { | ||||
|     final prefs = _ref.read(sharedPreferencesProvider); | ||||
|     return prefs.getString('dyn_user_atk'); | ||||
|     return prefs.getString(kTokenPairStoreKey); | ||||
|   } | ||||
|  | ||||
|   Future<void> fetchUser() async { | ||||
| @@ -33,6 +33,7 @@ class UserInfoNotifier extends StateNotifier<AsyncValue<SnAccount?>> { | ||||
|     final prefs = _ref.read(sharedPreferencesProvider); | ||||
|     await prefs.remove(kTokenPairStoreKey); | ||||
|     _ref.invalidate(userInfoProvider); | ||||
|     _ref.invalidate(tokenProvider); | ||||
|   } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -14,7 +14,7 @@ part 'websocket.freezed.dart'; | ||||
| part 'websocket.g.dart'; | ||||
|  | ||||
| @freezed | ||||
| abstract class WebSocketState with _$WebSocketState { | ||||
| sealed class WebSocketState with _$WebSocketState { | ||||
|   const factory WebSocketState.connected() = _Connected; | ||||
|   const factory WebSocketState.connecting() = _Connecting; | ||||
|   const factory WebSocketState.disconnected() = _Disconnected; | ||||
| @@ -22,7 +22,7 @@ abstract class WebSocketState with _$WebSocketState { | ||||
| } | ||||
|  | ||||
| @freezed | ||||
| abstract class WebSocketPacket with _$WebSocketPacket { | ||||
| sealed class WebSocketPacket with _$WebSocketPacket { | ||||
|   const factory WebSocketPacket({ | ||||
|     required String type, | ||||
|     required Map<String, dynamic>? data, | ||||
| @@ -51,27 +51,21 @@ class WebSocketService { | ||||
|  | ||||
|   Future<void> connect(Ref ref) async { | ||||
|     _ref = ref; | ||||
|     _statusStreamController.sink.add(WebSocketState.connecting()); | ||||
|  | ||||
|     final baseUrl = ref.watch(serverUrlProvider); | ||||
|     final atk = await getFreshAtk( | ||||
|       ref.watch(tokenPairProvider), | ||||
|       baseUrl, | ||||
|       onRefreshed: (atk, rtk) { | ||||
|         setTokenPair(ref.watch(sharedPreferencesProvider), atk, rtk); | ||||
|         ref.invalidate(tokenPairProvider); | ||||
|       }, | ||||
|     ); | ||||
|     final token = await getToken(ref.watch(tokenProvider)); | ||||
|  | ||||
|     final url = '$baseUrl/ws'.replaceFirst('http', 'ws'); | ||||
|  | ||||
|     log('[WebSocket] Trying connecting to $url'); | ||||
|     try { | ||||
|       if (kIsWeb) { | ||||
|         _channel = WebSocketChannel.connect(Uri.parse('$url?tk=$atk')); | ||||
|         _channel = WebSocketChannel.connect(Uri.parse('$url?tk=$token')); | ||||
|       } else { | ||||
|         _channel = IOWebSocketChannel.connect( | ||||
|           Uri.parse(url), | ||||
|           headers: {'Authorization': 'Bearer $atk'}, | ||||
|           headers: {'Authorization': 'Bearer $token'}, | ||||
|         ); | ||||
|       } | ||||
|       await _channel!.ready; | ||||
| @@ -140,23 +134,10 @@ class WebSocketStateNotifier extends StateNotifier<WebSocketState> { | ||||
|     state = const WebSocketState.connecting(); | ||||
|     try { | ||||
|       final service = ref.read(websocketProvider); | ||||
|       final baseUrl = ref.watch(serverUrlProvider); | ||||
|       final atk = await getFreshAtk( | ||||
|         ref.watch(tokenPairProvider), | ||||
|         baseUrl, | ||||
|         onRefreshed: (atk, rtk) { | ||||
|           setTokenPair(ref.watch(sharedPreferencesProvider), atk, rtk); | ||||
|           ref.invalidate(tokenPairProvider); | ||||
|         }, | ||||
|       ); | ||||
|       if (atk == null) { | ||||
|         state = const WebSocketState.error('Unauthorized'); | ||||
|         return; | ||||
|       } | ||||
|       await service.connect(ref); | ||||
|       state = const WebSocketState.connected(); | ||||
|       service.statusStream.listen((event) { | ||||
|         state = event; | ||||
|         if (mounted) state = event; | ||||
|       }); | ||||
|     } catch (err) { | ||||
|       state = WebSocketState.error('Failed to connect: $err'); | ||||
|   | ||||
| @@ -8,15 +8,15 @@ class AppRouter extends RootStackRouter { | ||||
|  | ||||
|   @override | ||||
|   List<AutoRoute> get routes => [ | ||||
|     RedirectRoute(path: '/', redirectTo: '/explore'), | ||||
|     AutoRoute(page: PostComposeRoute.page, path: '/posts/compose'), | ||||
|     AutoRoute(page: PostEditRoute.page, path: '/posts/:id/edit'), | ||||
|     AutoRoute( | ||||
|       page: ExploreShellRoute.page, | ||||
|       path: '/explore', | ||||
|       path: '/', | ||||
|       children: [ | ||||
|         AutoRoute(page: ExploreRoute.page, path: ''), | ||||
|         AutoRoute(page: PostComposeRoute.page, path: 'posts/compose'), | ||||
|         AutoRoute(page: PostDetailRoute.page, path: 'posts/:id'), | ||||
|         AutoRoute(page: PostEditRoute.page, path: 'posts/:id/edit'), | ||||
|         AutoRoute(page: PublisherProfileRoute.page, path: 'publishers/:name'), | ||||
|       ], | ||||
|     ), | ||||
|     AutoRoute( | ||||
| @@ -28,12 +28,11 @@ class AppRouter extends RootStackRouter { | ||||
|         AutoRoute(page: WalletRoute.page, path: 'wallet'), | ||||
|         AutoRoute(page: RelationshipRoute.page, path: 'relationships'), | ||||
|         AutoRoute(page: AccountProfileRoute.page, path: ':name'), | ||||
|         AutoRoute(page: PublisherProfileRoute.page, path: ':name/calendar'), | ||||
|         AutoRoute(page: MyselfEventCalendarRoute.page, path: 'me/calendar'), | ||||
|         AutoRoute(page: UpdateProfileRoute.page, path: 'me/update'), | ||||
|         AutoRoute(page: AccountSettingsRoute.page, path: 'settings'), | ||||
|       ], | ||||
|     ), | ||||
|     AutoRoute(page: EventCalanderRoute.page, path: '/account/:name/calendar'), | ||||
|     AutoRoute(page: RealmListRoute.page, path: '/realms'), | ||||
|     AutoRoute( | ||||
|       page: ChatShellRoute.page, | ||||
| @@ -41,6 +40,7 @@ class AppRouter extends RootStackRouter { | ||||
|       children: [ | ||||
|         AutoRoute(page: ChatListRoute.page, path: ''), | ||||
|         AutoRoute(page: ChatRoomRoute.page, path: ':id'), | ||||
|         AutoRoute(page: CallRoute.page, path: ':id/call'), | ||||
|         AutoRoute(page: NewChatRoute.page, path: 'new'), | ||||
|         AutoRoute(page: EditChatRoute.page, path: ':id/edit'), | ||||
|         AutoRoute(page: ChatDetailRoute.page, path: ':id/detail'), | ||||
| @@ -51,6 +51,7 @@ class AppRouter extends RootStackRouter { | ||||
|       path: '/creators', | ||||
|       children: [ | ||||
|         AutoRoute(page: CreatorHubRoute.page, path: ''), | ||||
|         AutoRoute(page: CreatorPostListRoute.page, path: ':name/posts'), | ||||
|         AutoRoute(page: StickersRoute.page, path: ':name/stickers'), | ||||
|         AutoRoute(page: NewStickerPacksRoute.page, path: ':name/stickers/new'), | ||||
|         AutoRoute( | ||||
|   | ||||
							
								
								
									
										1106
									
								
								lib/route.gr.dart
									
									
									
									
									
								
							
							
						
						
									
										1106
									
								
								lib/route.gr.dart
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -11,6 +11,7 @@ import 'package:island/pods/userinfo.dart'; | ||||
| import 'package:island/route.gr.dart'; | ||||
| import 'package:island/screens/notification.dart'; | ||||
| import 'package:island/services/responsive.dart'; | ||||
| import 'package:island/widgets/account/account_name.dart'; | ||||
| import 'package:island/widgets/account/status.dart'; | ||||
| import 'package:island/widgets/account/leveling_progress.dart'; | ||||
| import 'package:island/widgets/app_scaffold.dart'; | ||||
| @@ -74,7 +75,7 @@ class AccountScreen extends HookConsumerWidget { | ||||
|               child: Column( | ||||
|                 crossAxisAlignment: CrossAxisAlignment.start, | ||||
|                 children: [ | ||||
|                   if (user.value?.profile.backgroundId != null) | ||||
|                   if (user.value?.profile.background?.id != null) | ||||
|                     ClipRRect( | ||||
|                       borderRadius: BorderRadius.only( | ||||
|                         topLeft: Radius.circular(8), | ||||
| @@ -83,7 +84,7 @@ class AccountScreen extends HookConsumerWidget { | ||||
|                       child: AspectRatio( | ||||
|                         aspectRatio: 16 / 7, | ||||
|                         child: CloudImageWidget( | ||||
|                           fileId: user.value!.profile.backgroundId!, | ||||
|                           file: user.value?.profile.background, | ||||
|                           fit: BoxFit.cover, | ||||
|                         ), | ||||
|                       ), | ||||
| @@ -94,7 +95,7 @@ class AccountScreen extends HookConsumerWidget { | ||||
|                     children: [ | ||||
|                       GestureDetector( | ||||
|                         child: ProfilePictureWidget( | ||||
|                           fileId: user.value?.profile.pictureId, | ||||
|                           file: user.value?.profile.picture, | ||||
|                           radius: 24, | ||||
|                         ), | ||||
|                         onTap: () { | ||||
| @@ -112,7 +113,13 @@ class AccountScreen extends HookConsumerWidget { | ||||
|                               crossAxisAlignment: CrossAxisAlignment.baseline, | ||||
|                               textBaseline: TextBaseline.alphabetic, | ||||
|                               children: [ | ||||
|                                 Text(user.value!.nick).bold().fontSize(16), | ||||
|                                 AccountName( | ||||
|                                   account: user.value!, | ||||
|                                   style: TextStyle( | ||||
|                                     fontSize: 16, | ||||
|                                     fontWeight: FontWeight.bold, | ||||
|                                   ), | ||||
|                                 ), | ||||
|                                 Text('@${user.value!.name}'), | ||||
|                               ], | ||||
|                             ), | ||||
| @@ -214,16 +221,6 @@ class AccountScreen extends HookConsumerWidget { | ||||
|                 context.router.push(RelationshipRoute()); | ||||
|               }, | ||||
|             ), | ||||
|             ListTile( | ||||
|               minTileHeight: 48, | ||||
|               leading: const Icon(Symbols.edit), | ||||
|               trailing: const Icon(Symbols.chevron_right), | ||||
|               contentPadding: EdgeInsets.symmetric(horizontal: 24), | ||||
|               title: Text('updateYourProfile').tr(), | ||||
|               onTap: () { | ||||
|                 context.router.push(UpdateProfileRoute()); | ||||
|               }, | ||||
|             ), | ||||
|             const Divider(height: 1).padding(vertical: 8), | ||||
|             ListTile( | ||||
|               minTileHeight: 48, | ||||
| @@ -235,6 +232,26 @@ class AccountScreen extends HookConsumerWidget { | ||||
|                 context.router.push(SettingsRoute()); | ||||
|               }, | ||||
|             ), | ||||
|             ListTile( | ||||
|               minTileHeight: 48, | ||||
|               leading: const Icon(Symbols.person_edit), | ||||
|               trailing: const Icon(Symbols.chevron_right), | ||||
|               contentPadding: EdgeInsets.symmetric(horizontal: 24), | ||||
|               title: Text('updateYourProfile').tr(), | ||||
|               onTap: () { | ||||
|                 context.router.push(UpdateProfileRoute()); | ||||
|               }, | ||||
|             ), | ||||
|             ListTile( | ||||
|               minTileHeight: 48, | ||||
|               leading: const Icon(Symbols.manage_accounts), | ||||
|               trailing: const Icon(Symbols.chevron_right), | ||||
|               contentPadding: EdgeInsets.symmetric(horizontal: 24), | ||||
|               title: Text('accountSettings').tr(), | ||||
|               onTap: () { | ||||
|                 context.router.push(AccountSettingsRoute()); | ||||
|               }, | ||||
|             ), | ||||
|             if (kDebugMode) const Divider(height: 1).padding(vertical: 8), | ||||
|             if (kDebugMode) | ||||
|               ListTile( | ||||
| @@ -244,8 +261,8 @@ class AccountScreen extends HookConsumerWidget { | ||||
|                 contentPadding: EdgeInsets.symmetric(horizontal: 24), | ||||
|                 title: Text('Copy access token'), | ||||
|                 onTap: () async { | ||||
|                   final tk = ref.watch(tokenPairProvider); | ||||
|                   Clipboard.setData(ClipboardData(text: tk!.accessToken)); | ||||
|                   final tk = ref.watch(tokenProvider); | ||||
|                   Clipboard.setData(ClipboardData(text: tk!.token)); | ||||
|                 }, | ||||
|               ), | ||||
|             if (kDebugMode) | ||||
| @@ -284,7 +301,7 @@ class _UnauthorizedAccountScreen extends StatelessWidget { | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return AppScaffold( | ||||
|       appBar: AppBar(title: const Text('Account')), | ||||
|       appBar: AppBar(title: const Text('account').tr()), | ||||
|       body: | ||||
|           ConstrainedBox( | ||||
|             constraints: const BoxConstraints(maxWidth: 360), | ||||
|   | ||||
							
								
								
									
										120
									
								
								lib/screens/account/event_calendar.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										120
									
								
								lib/screens/account/event_calendar.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,120 @@ | ||||
| import 'package:auto_route/auto_route.dart'; | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter_hooks/flutter_hooks.dart'; | ||||
| import 'package:gap/gap.dart'; | ||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||
| import 'package:island/pods/event_calendar.dart'; | ||||
| import 'package:island/screens/account/profile.dart'; | ||||
| import 'package:island/widgets/account/account_nameplate.dart'; | ||||
| import 'package:island/widgets/app_scaffold.dart'; | ||||
| import 'package:island/widgets/account/event_calendar.dart'; | ||||
| import 'package:island/widgets/account/fortune_graph.dart'; | ||||
| import 'package:styled_widget/styled_widget.dart'; | ||||
|  | ||||
| @RoutePage() | ||||
| class EventCalanderScreen extends HookConsumerWidget { | ||||
|   final String name; | ||||
|   const EventCalanderScreen({super.key, @PathParam("name") required this.name}); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context, WidgetRef ref) { | ||||
|     // Get the current date | ||||
|     final now = DateTime.now(); | ||||
|  | ||||
|     // Create the query for the current month | ||||
|     final query = useState( | ||||
|       EventCalendarQuery(uname: name, year: now.year, month: now.month), | ||||
|     ); | ||||
|  | ||||
|     // Watch the event calendar data | ||||
|     final events = ref.watch(eventCalendarProvider(query.value)); | ||||
|     final user = ref.watch(accountProvider(name)); | ||||
|  | ||||
|     // Track the selected day for synchronizing between widgets | ||||
|     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 | ||||
|     void onDaySelected(DateTime day) { | ||||
|       selectedDay.value = day; | ||||
|     } | ||||
|  | ||||
|     return AppScaffold( | ||||
|       noBackground: false, | ||||
|       appBar: AppBar( | ||||
|         leading: const PageBackButton(), | ||||
|         title: Text('eventCalander').tr(), | ||||
|       ), | ||||
|       body: SingleChildScrollView( | ||||
|         child: | ||||
|             MediaQuery.of(context).size.width > 480 | ||||
|                 ? ConstrainedBox( | ||||
|                   constraints: BoxConstraints(maxWidth: 480), | ||||
|                   child: Column( | ||||
|                     children: [ | ||||
|                       Card( | ||||
|                         margin: EdgeInsets.all(16), | ||||
|                         child: Column( | ||||
|                           children: [ | ||||
|                             // Use the reusable EventCalendarWidget | ||||
|                             EventCalendarWidget( | ||||
|                               events: events, | ||||
|                               initialDate: now, | ||||
|                               showEventDetails: true, | ||||
|                               onMonthChanged: onMonthChanged, | ||||
|                               onDaySelected: onDaySelected, | ||||
|                             ), | ||||
|                           ], | ||||
|                         ), | ||||
|                       ), | ||||
|  | ||||
|                       // Add the fortune graph widget | ||||
|                       const Divider(height: 1), | ||||
|                       FortuneGraphWidget( | ||||
|                         events: events, | ||||
|                         constrainWidth: true, | ||||
|                         onPointSelected: onDaySelected, | ||||
|                       ), | ||||
|  | ||||
|                       // Show user profile if viewing someone else's calendar | ||||
|                       if (name != 'me' && user.hasValue) | ||||
|                         AccountNameplate(name: name), | ||||
|                     ], | ||||
|                   ), | ||||
|                 ).center() | ||||
|                 : Column( | ||||
|                   children: [ | ||||
|                     // Use the reusable EventCalendarWidget | ||||
|                     EventCalendarWidget( | ||||
|                       events: events, | ||||
|                       initialDate: now, | ||||
|                       showEventDetails: true, | ||||
|                       onMonthChanged: onMonthChanged, | ||||
|                       onDaySelected: onDaySelected, | ||||
|                     ), | ||||
|  | ||||
|                     // Add the fortune graph widget | ||||
|                     const Divider(height: 1), | ||||
|                     FortuneGraphWidget( | ||||
|                       events: events, | ||||
|                       onPointSelected: onDaySelected, | ||||
|                     ).padding(horizontal: 8, vertical: 4), | ||||
|  | ||||
|                     // Show user profile if viewing someone else's calendar | ||||
|                     if (name != 'me' && user.hasValue) | ||||
|                       AccountNameplate(name: name), | ||||
|                     Gap(MediaQuery.of(context).padding.bottom + 16), | ||||
|                   ], | ||||
|                 ), | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| @@ -1,199 +0,0 @@ | ||||
| import 'package:auto_route/auto_route.dart'; | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter_hooks/flutter_hooks.dart'; | ||||
| import 'package:freezed_annotation/freezed_annotation.dart'; | ||||
| import 'package:gap/gap.dart'; | ||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||
| import 'package:island/models/activity.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:styled_widget/styled_widget.dart'; | ||||
| import 'package:table_calendar/table_calendar.dart'; | ||||
|  | ||||
| part 'event_calendar.g.dart'; | ||||
| part 'event_calendar.freezed.dart'; | ||||
|  | ||||
| @freezed | ||||
| abstract class EventCalendarQuery with _$EventCalendarQuery { | ||||
|   const factory EventCalendarQuery({ | ||||
|     required String? uname, | ||||
|     required int year, | ||||
|     required int month, | ||||
|   }) = _EventCalendarQuery; | ||||
| } | ||||
|  | ||||
| @riverpod | ||||
| Future<List<SnEventCalendarEntry>> accountEventCalendar( | ||||
|   Ref ref, | ||||
|   EventCalendarQuery query, | ||||
| ) async { | ||||
|   final client = ref.watch(apiClientProvider); | ||||
|   final resp = await client.get('/accounts/${query.uname ?? 'me'}/calendar'); | ||||
|   return resp.data | ||||
|       .map((e) => SnEventCalendarEntry.fromJson(e)) | ||||
|       .cast<SnEventCalendarEntry>() | ||||
|       .toList(); | ||||
| } | ||||
|  | ||||
| @RoutePage() | ||||
| class MyselfEventCalendarScreen extends HookConsumerWidget { | ||||
|   const MyselfEventCalendarScreen({super.key}); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context, WidgetRef ref) { | ||||
|     final selectedMonth = useState(DateTime.now().month); | ||||
|     final selectedYear = useState(DateTime.now().year); | ||||
|  | ||||
|     final selectedDay = useState(DateTime.now()); | ||||
|  | ||||
|     final events = ref.watch( | ||||
|       accountEventCalendarProvider( | ||||
|         EventCalendarQuery( | ||||
|           uname: 'me', | ||||
|           year: selectedYear.value, | ||||
|           month: selectedMonth.value, | ||||
|         ), | ||||
|       ), | ||||
|     ); | ||||
|  | ||||
|     return AppScaffold( | ||||
|       appBar: AppBar( | ||||
|         leading: const PageBackButton(), | ||||
|         title: Text('eventCalander').tr(), | ||||
|       ), | ||||
|       body: SingleChildScrollView( | ||||
|         child: Column( | ||||
|           children: [ | ||||
|             TableCalendar( | ||||
|               locale: EasyLocalization.of(context)!.locale.toString(), | ||||
|               firstDay: DateTime.now().add(Duration(days: -3650)), | ||||
|               lastDay: DateTime.now().add(Duration(days: 3650)), | ||||
|               focusedDay: DateTime.utc( | ||||
|                 selectedYear.value, | ||||
|                 selectedMonth.value, | ||||
|                 DateTime.now().day, | ||||
|               ), | ||||
|               calendarFormat: CalendarFormat.month, | ||||
|               selectedDayPredicate: (day) { | ||||
|                 return isSameDay(selectedDay.value, day); | ||||
|               }, | ||||
|               onDaySelected: (value, _) { | ||||
|                 selectedDay.value = value; | ||||
|               }, | ||||
|               onPageChanged: (focusedDay) { | ||||
|                 selectedMonth.value = focusedDay.month; | ||||
|                 selectedYear.value = focusedDay.year; | ||||
|               }, | ||||
|               eventLoader: (day) { | ||||
|                 return events.value | ||||
|                         ?.where((e) => isSameDay(e.date, day)) | ||||
|                         .expand((e) => [...e.statuses, e.checkInResult]) | ||||
|                         .where((e) => e != null) | ||||
|                         .toList() ?? | ||||
|                     []; | ||||
|               }, | ||||
|               calendarBuilders: CalendarBuilders( | ||||
|                 dowBuilder: (context, day) { | ||||
|                   final text = DateFormat.EEEEE().format(day); | ||||
|                   return Center(child: Text(text)); | ||||
|                 }, | ||||
|                 markerBuilder: (context, day, events) { | ||||
|                   var checkInResult = | ||||
|                       events.whereType<SnCheckInResult>().firstOrNull; | ||||
|                   if (checkInResult != null) { | ||||
|                     return Positioned( | ||||
|                       top: 32, | ||||
|                       child: Text( | ||||
|                         ['大凶', '凶', '中平', '吉', '大吉'][checkInResult.level], | ||||
|                         style: TextStyle( | ||||
|                           fontSize: 9, | ||||
|                           color: | ||||
|                               isSameDay(selectedDay.value, day) | ||||
|                                   ? Theme.of( | ||||
|                                     context, | ||||
|                                   ).colorScheme.onPrimaryContainer | ||||
|                                   : isSameDay(DateTime.now(), day) | ||||
|                                   ? Theme.of( | ||||
|                                     context, | ||||
|                                   ).colorScheme.onSecondaryContainer | ||||
|                                   : Theme.of(context).colorScheme.onSurface, | ||||
|                         ), | ||||
|                       ), | ||||
|                     ); | ||||
|                   } | ||||
|                   return null; | ||||
|                 }, | ||||
|               ), | ||||
|             ), | ||||
|             const Divider(height: 1).padding(top: 8), | ||||
|             AnimatedSwitcher( | ||||
|               duration: const Duration(milliseconds: 300), | ||||
|               child: Builder( | ||||
|                 builder: (context) { | ||||
|                   final event = | ||||
|                       events.value | ||||
|                           ?.where((e) => isSameDay(e.date, selectedDay.value)) | ||||
|                           .firstOrNull; | ||||
|                   return Column( | ||||
|                     crossAxisAlignment: CrossAxisAlignment.stretch, | ||||
|                     children: [ | ||||
|                       Text(DateFormat.EEEE().format(selectedDay.value)) | ||||
|                           .fontSize(16) | ||||
|                           .bold() | ||||
|                           .textColor( | ||||
|                             Theme.of(context).colorScheme.onSecondaryContainer, | ||||
|                           ), | ||||
|                       Text(DateFormat.yMd().format(selectedDay.value)) | ||||
|                           .fontSize(12) | ||||
|                           .textColor( | ||||
|                             Theme.of(context).colorScheme.onSecondaryContainer, | ||||
|                           ), | ||||
|                       const Gap(16), | ||||
|                       if (event?.checkInResult != null) | ||||
|                         Column( | ||||
|                           crossAxisAlignment: CrossAxisAlignment.stretch, | ||||
|                           children: [ | ||||
|                             Text( | ||||
|                               'checkInResultLevel${event!.checkInResult!.level}', | ||||
|                             ).tr().fontSize(16).bold(), | ||||
|                             for (final tip in event.checkInResult!.tips) | ||||
|                               Row( | ||||
|                                 crossAxisAlignment: CrossAxisAlignment.start, | ||||
|                                 spacing: 8, | ||||
|                                 children: [ | ||||
|                                   Icon( | ||||
|                                     Symbols.circle, | ||||
|                                     size: 12, | ||||
|                                     fill: 1, | ||||
|                                   ).padding(top: 4, right: 4), | ||||
|                                   Expanded( | ||||
|                                     child: Column( | ||||
|                                       crossAxisAlignment: | ||||
|                                           CrossAxisAlignment.start, | ||||
|                                       children: [ | ||||
|                                         Text(tip.title).bold(), | ||||
|                                         Text(tip.content), | ||||
|                                       ], | ||||
|                                     ), | ||||
|                                   ), | ||||
|                                 ], | ||||
|                               ).padding(top: 8), | ||||
|                           ], | ||||
|                         ), | ||||
|                       if (event?.checkInResult == null && | ||||
|                           (event?.statuses.isEmpty ?? true)) | ||||
|                         Text('eventCalanderEmpty').tr(), | ||||
|                     ], | ||||
|                   ).padding(vertical: 24, horizontal: 24); | ||||
|                 }, | ||||
|               ), | ||||
|             ), | ||||
|           ], | ||||
|         ), | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| @@ -1,148 +0,0 @@ | ||||
| // dart format width=80 | ||||
| // coverage:ignore-file | ||||
| // GENERATED CODE - DO NOT MODIFY BY HAND | ||||
| // ignore_for_file: type=lint | ||||
| // ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark | ||||
|  | ||||
| part of 'event_calendar.dart'; | ||||
|  | ||||
| // ************************************************************************** | ||||
| // FreezedGenerator | ||||
| // ************************************************************************** | ||||
|  | ||||
| // dart format off | ||||
| T _$identity<T>(T value) => value; | ||||
| /// @nodoc | ||||
| mixin _$EventCalendarQuery { | ||||
|  | ||||
|  String? get uname; int get year; int get month; | ||||
| /// Create a copy of EventCalendarQuery | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @JsonKey(includeFromJson: false, includeToJson: false) | ||||
| @pragma('vm:prefer-inline') | ||||
| $EventCalendarQueryCopyWith<EventCalendarQuery> get copyWith => _$EventCalendarQueryCopyWithImpl<EventCalendarQuery>(this as EventCalendarQuery, _$identity); | ||||
|  | ||||
|  | ||||
|  | ||||
| @override | ||||
| bool operator ==(Object other) { | ||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is EventCalendarQuery&&(identical(other.uname, uname) || other.uname == uname)&&(identical(other.year, year) || other.year == year)&&(identical(other.month, month) || other.month == month)); | ||||
| } | ||||
|  | ||||
|  | ||||
| @override | ||||
| int get hashCode => Object.hash(runtimeType,uname,year,month); | ||||
|  | ||||
| @override | ||||
| String toString() { | ||||
|   return 'EventCalendarQuery(uname: $uname, year: $year, month: $month)'; | ||||
| } | ||||
|  | ||||
|  | ||||
| } | ||||
|  | ||||
| /// @nodoc | ||||
| abstract mixin class $EventCalendarQueryCopyWith<$Res>  { | ||||
|   factory $EventCalendarQueryCopyWith(EventCalendarQuery value, $Res Function(EventCalendarQuery) _then) = _$EventCalendarQueryCopyWithImpl; | ||||
| @useResult | ||||
| $Res call({ | ||||
|  String? uname, int year, int month | ||||
| }); | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| } | ||||
| /// @nodoc | ||||
| class _$EventCalendarQueryCopyWithImpl<$Res> | ||||
|     implements $EventCalendarQueryCopyWith<$Res> { | ||||
|   _$EventCalendarQueryCopyWithImpl(this._self, this._then); | ||||
|  | ||||
|   final EventCalendarQuery _self; | ||||
|   final $Res Function(EventCalendarQuery) _then; | ||||
|  | ||||
| /// Create a copy of EventCalendarQuery | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @pragma('vm:prefer-inline') @override $Res call({Object? uname = freezed,Object? year = null,Object? month = null,}) { | ||||
|   return _then(_self.copyWith( | ||||
| uname: freezed == uname ? _self.uname : uname // ignore: cast_nullable_to_non_nullable | ||||
| as String?,year: null == year ? _self.year : year // ignore: cast_nullable_to_non_nullable | ||||
| as int,month: null == month ? _self.month : month // ignore: cast_nullable_to_non_nullable | ||||
| as int, | ||||
|   )); | ||||
| } | ||||
|  | ||||
| } | ||||
|  | ||||
|  | ||||
| /// @nodoc | ||||
|  | ||||
|  | ||||
| class _EventCalendarQuery implements EventCalendarQuery { | ||||
|   const _EventCalendarQuery({required this.uname, required this.year, required this.month}); | ||||
|    | ||||
|  | ||||
| @override final  String? uname; | ||||
| @override final  int year; | ||||
| @override final  int month; | ||||
|  | ||||
| /// Create a copy of EventCalendarQuery | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @override @JsonKey(includeFromJson: false, includeToJson: false) | ||||
| @pragma('vm:prefer-inline') | ||||
| _$EventCalendarQueryCopyWith<_EventCalendarQuery> get copyWith => __$EventCalendarQueryCopyWithImpl<_EventCalendarQuery>(this, _$identity); | ||||
|  | ||||
|  | ||||
|  | ||||
| @override | ||||
| bool operator ==(Object other) { | ||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is _EventCalendarQuery&&(identical(other.uname, uname) || other.uname == uname)&&(identical(other.year, year) || other.year == year)&&(identical(other.month, month) || other.month == month)); | ||||
| } | ||||
|  | ||||
|  | ||||
| @override | ||||
| int get hashCode => Object.hash(runtimeType,uname,year,month); | ||||
|  | ||||
| @override | ||||
| String toString() { | ||||
|   return 'EventCalendarQuery(uname: $uname, year: $year, month: $month)'; | ||||
| } | ||||
|  | ||||
|  | ||||
| } | ||||
|  | ||||
| /// @nodoc | ||||
| abstract mixin class _$EventCalendarQueryCopyWith<$Res> implements $EventCalendarQueryCopyWith<$Res> { | ||||
|   factory _$EventCalendarQueryCopyWith(_EventCalendarQuery value, $Res Function(_EventCalendarQuery) _then) = __$EventCalendarQueryCopyWithImpl; | ||||
| @override @useResult | ||||
| $Res call({ | ||||
|  String? uname, int year, int month | ||||
| }); | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| } | ||||
| /// @nodoc | ||||
| class __$EventCalendarQueryCopyWithImpl<$Res> | ||||
|     implements _$EventCalendarQueryCopyWith<$Res> { | ||||
|   __$EventCalendarQueryCopyWithImpl(this._self, this._then); | ||||
|  | ||||
|   final _EventCalendarQuery _self; | ||||
|   final $Res Function(_EventCalendarQuery) _then; | ||||
|  | ||||
| /// Create a copy of EventCalendarQuery | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @override @pragma('vm:prefer-inline') $Res call({Object? uname = freezed,Object? year = null,Object? month = null,}) { | ||||
|   return _then(_EventCalendarQuery( | ||||
| uname: freezed == uname ? _self.uname : uname // ignore: cast_nullable_to_non_nullable | ||||
| as String?,year: null == year ? _self.year : year // ignore: cast_nullable_to_non_nullable | ||||
| as int,month: null == month ? _self.month : month // ignore: cast_nullable_to_non_nullable | ||||
| as int, | ||||
|   )); | ||||
| } | ||||
|  | ||||
|  | ||||
| } | ||||
|  | ||||
| // dart format on | ||||
| @@ -1,8 +1,55 @@ | ||||
| import 'dart:io'; | ||||
|  | ||||
| import 'package:auto_route/annotations.dart'; | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:flutter/foundation.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter/services.dart'; | ||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||
| import 'package:island/models/auth.dart'; | ||||
| import 'package:island/models/user.dart'; | ||||
| import 'package:island/pods/network.dart'; | ||||
| import 'package:island/pods/userinfo.dart'; | ||||
| import 'package:island/screens/account/me/settings_auth_factors.dart'; | ||||
| import 'package:island/screens/account/me/settings_connections.dart'; | ||||
| import 'package:island/screens/account/me/settings_contacts.dart'; | ||||
| import 'package:island/screens/auth/captcha.dart'; | ||||
| import 'package:island/screens/auth/login.dart'; | ||||
| import 'package:island/services/responsive.dart'; | ||||
| import 'package:island/widgets/account/account_session_sheet.dart'; | ||||
| import 'package:island/widgets/alert.dart'; | ||||
| import 'package:island/widgets/app_scaffold.dart'; | ||||
| import 'package:island/widgets/response.dart'; | ||||
| import 'package:material_symbols_icons/symbols.dart'; | ||||
| import 'package:riverpod_annotation/riverpod_annotation.dart'; | ||||
| import 'package:styled_widget/styled_widget.dart'; | ||||
|  | ||||
| part 'settings.g.dart'; | ||||
|  | ||||
| @riverpod | ||||
| Future<List<SnAuthFactor>> authFactors(Ref ref) async { | ||||
|   final client = ref.read(apiClientProvider); | ||||
|   final res = await client.get('/accounts/me/factors'); | ||||
|   return res.data.map<SnAuthFactor>((e) => SnAuthFactor.fromJson(e)).toList(); | ||||
| } | ||||
|  | ||||
| @riverpod | ||||
| Future<List<SnContactMethod>> contactMethods(Ref ref) async { | ||||
|   final client = ref.read(apiClientProvider); | ||||
|   final resp = await client.get('/accounts/me/contacts'); | ||||
|   return resp.data | ||||
|       .map<SnContactMethod>((e) => SnContactMethod.fromJson(e)) | ||||
|       .toList(); | ||||
| } | ||||
|  | ||||
| @riverpod | ||||
| Future<List<SnAccountConnection>> accountConnections(Ref ref) async { | ||||
|   final client = ref.read(apiClientProvider); | ||||
|   final resp = await client.get('/accounts/me/connections'); | ||||
|   return resp.data | ||||
|       .map<SnAccountConnection>((e) => SnAccountConnection.fromJson(e)) | ||||
|       .toList(); | ||||
| } | ||||
|  | ||||
| @RoutePage() | ||||
| class AccountSettingsScreen extends HookConsumerWidget { | ||||
| @@ -10,9 +57,513 @@ class AccountSettingsScreen extends HookConsumerWidget { | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context, WidgetRef ref) { | ||||
|     final isDesktop = | ||||
|         !kIsWeb && (Platform.isWindows || Platform.isMacOS || Platform.isLinux); | ||||
|     final isWide = isWideScreen(context); | ||||
|  | ||||
|     Future<void> requestAccountDeletion() async { | ||||
|       final confirm = await showConfirmAlert( | ||||
|         'accountDeletionHint'.tr(), | ||||
|         'accountDeletion'.tr(), | ||||
|       ); | ||||
|       if (!confirm || !context.mounted) return; | ||||
|       try { | ||||
|         showLoadingModal(context); | ||||
|         final client = ref.read(apiClientProvider); | ||||
|         await client.delete('/accounts/me'); | ||||
|         if (context.mounted) { | ||||
|           showSnackBar(context, 'accountDeletionSent'.tr()); | ||||
|         } | ||||
|       } catch (err) { | ||||
|         showErrorAlert(err); | ||||
|       } finally { | ||||
|         if (context.mounted) hideLoadingModal(context); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     Future<void> requestResetPassword() async { | ||||
|       final confirm = await showConfirmAlert( | ||||
|         'accountPasswordChangeDescription'.tr(), | ||||
|         'accountPasswordChange'.tr(), | ||||
|       ); | ||||
|       if (!confirm || !context.mounted) return; | ||||
|       final captchaTk = await Navigator.of( | ||||
|         context, | ||||
|       ).push(MaterialPageRoute(builder: (context) => CaptchaScreen())); | ||||
|       if (captchaTk == null) return; | ||||
|       try { | ||||
|         if (context.mounted) showLoadingModal(context); | ||||
|         final userInfo = ref.read(userInfoProvider); | ||||
|         final client = ref.read(apiClientProvider); | ||||
|         await client.post( | ||||
|           '/accounts/recovery/password', | ||||
|           data: {'account': userInfo.value!.name, 'captcha_token': captchaTk}, | ||||
|         ); | ||||
|         if (context.mounted) { | ||||
|           showSnackBar(context, 'accountPasswordChangeSent'.tr()); | ||||
|         } | ||||
|       } catch (err) { | ||||
|         showErrorAlert(err); | ||||
|       } finally { | ||||
|         if (context.mounted) hideLoadingModal(context); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     final authFactors = ref.watch(authFactorsProvider); | ||||
|  | ||||
|     // Group settings into categories for better organization | ||||
|     final securitySettings = [ | ||||
|       ListTile( | ||||
|         minLeadingWidth: 48, | ||||
|         leading: const Icon(Symbols.devices), | ||||
|         title: Text('authSessions').tr(), | ||||
|         subtitle: Text('authSessionsDescription').tr().fontSize(12), | ||||
|         contentPadding: const EdgeInsets.only(left: 24, right: 17), | ||||
|         trailing: const Icon(Symbols.chevron_right), | ||||
|         onTap: () { | ||||
|           showModalBottomSheet( | ||||
|             context: context, | ||||
|             isScrollControlled: true, | ||||
|             builder: (context) => const AccountSessionSheet(), | ||||
|           ); | ||||
|         }, | ||||
|       ), | ||||
|       ExpansionTile( | ||||
|         leading: const Icon( | ||||
|           Symbols.link, | ||||
|         ).alignment(Alignment.centerLeft).width(48), | ||||
|         title: Text('accountConnections').tr(), | ||||
|         subtitle: Text('accountConnectionsDescription').tr().fontSize(12), | ||||
|         tilePadding: const EdgeInsets.only(left: 24, right: 17), | ||||
|         children: [ | ||||
|           ref | ||||
|               .watch(accountConnectionsProvider) | ||||
|               .when( | ||||
|                 data: | ||||
|                     (connections) => Column( | ||||
|                       children: [ | ||||
|                         for (final connection in connections) | ||||
|                           ListTile( | ||||
|                             minLeadingWidth: 48, | ||||
|                             contentPadding: const EdgeInsets.only( | ||||
|                               left: 16, | ||||
|                               right: 17, | ||||
|                               top: 2, | ||||
|                               bottom: 4, | ||||
|                             ), | ||||
|                             title: | ||||
|                                 Text( | ||||
|                                   getLocalizedProviderName(connection.provider), | ||||
|                                 ).tr(), | ||||
|                             subtitle: | ||||
|                                 connection.meta['email'] != null | ||||
|                                     ? Text(connection.meta['email']) | ||||
|                                     : Text(connection.providedIdentifier), | ||||
|                             leading: CircleAvatar( | ||||
|                               child: getProviderIcon( | ||||
|                                 connection.provider, | ||||
|                                 size: 16, | ||||
|                                 color: | ||||
|                                     Theme.of( | ||||
|                                       context, | ||||
|                                     ).colorScheme.onPrimaryContainer, | ||||
|                               ), | ||||
|                             ).padding(top: 4), | ||||
|                             trailing: const Icon(Symbols.chevron_right), | ||||
|                             onTap: () { | ||||
|                               showModalBottomSheet( | ||||
|                                 context: context, | ||||
|                                 builder: | ||||
|                                     (context) => AccountConnectionSheet( | ||||
|                                       connection: connection, | ||||
|                                     ), | ||||
|                               ).then((value) { | ||||
|                                 if (value == true) { | ||||
|                                   ref.invalidate(accountConnectionsProvider); | ||||
|                                 } | ||||
|                               }); | ||||
|                             }, | ||||
|                           ), | ||||
|                         if (connections.isNotEmpty) const Divider(height: 1), | ||||
|                         ListTile( | ||||
|                           minLeadingWidth: 48, | ||||
|                           contentPadding: const EdgeInsets.only( | ||||
|                             left: 24, | ||||
|                             right: 17, | ||||
|                           ), | ||||
|                           title: Text('accountConnectionAdd').tr(), | ||||
|                           leading: const Icon(Symbols.add), | ||||
|                           trailing: const Icon(Symbols.chevron_right), | ||||
|                           onTap: () { | ||||
|                             showModalBottomSheet( | ||||
|                               context: context, | ||||
|                               builder: | ||||
|                                   (context) => | ||||
|                                       const AccountConnectionNewSheet(), | ||||
|                             ).then((value) { | ||||
|                               if (value == true) { | ||||
|                                 ref.invalidate(accountConnectionsProvider); | ||||
|                               } | ||||
|                             }); | ||||
|                           }, | ||||
|                         ), | ||||
|                       ], | ||||
|                     ), | ||||
|                 error: | ||||
|                     (err, _) => ResponseErrorWidget( | ||||
|                       error: err, | ||||
|                       onRetry: () => ref.invalidate(accountConnectionsProvider), | ||||
|                     ), | ||||
|                 loading: () => const ResponseLoadingWidget(), | ||||
|               ), | ||||
|         ], | ||||
|       ), | ||||
|       ExpansionTile( | ||||
|         leading: const Icon( | ||||
|           Symbols.security, | ||||
|         ).alignment(Alignment.centerLeft).width(48), | ||||
|         title: Text('accountAuthFactor').tr(), | ||||
|         subtitle: Text('accountAuthFactorDescription').tr().fontSize(12), | ||||
|         tilePadding: const EdgeInsets.only(left: 24, right: 17), | ||||
|         children: [ | ||||
|           authFactors.when( | ||||
|             data: | ||||
|                 (factors) => Column( | ||||
|                   children: [ | ||||
|                     for (final factor in factors) | ||||
|                       ListTile( | ||||
|                         minLeadingWidth: 48, | ||||
|                         contentPadding: const EdgeInsets.only( | ||||
|                           left: 16, | ||||
|                           right: 17, | ||||
|                           top: 2, | ||||
|                           bottom: 4, | ||||
|                         ), | ||||
|                         title: | ||||
|                             Text( | ||||
|                               kFactorTypes[factor.type]!.$1, | ||||
|                               style: | ||||
|                                   factor.enabledAt == null | ||||
|                                       ? TextStyle( | ||||
|                                         decoration: TextDecoration.lineThrough, | ||||
|                                       ) | ||||
|                                       : null, | ||||
|                             ).tr(), | ||||
|                         subtitle: | ||||
|                             Text( | ||||
|                               kFactorTypes[factor.type]!.$2, | ||||
|                               style: | ||||
|                                   factor.enabledAt == null | ||||
|                                       ? TextStyle( | ||||
|                                         decoration: TextDecoration.lineThrough, | ||||
|                                       ) | ||||
|                                       : null, | ||||
|                             ).tr(), | ||||
|                         leading: CircleAvatar( | ||||
|                           backgroundColor: | ||||
|                               factor.enabledAt == null | ||||
|                                   ? Theme.of( | ||||
|                                     context, | ||||
|                                   ).colorScheme.secondaryContainer | ||||
|                                   : Theme.of( | ||||
|                                     context, | ||||
|                                   ).colorScheme.primaryContainer, | ||||
|                           child: Icon(kFactorTypes[factor.type]!.$3), | ||||
|                         ).padding(top: 4), | ||||
|                         trailing: const Icon(Symbols.chevron_right), | ||||
|                         isThreeLine: true, | ||||
|                         onTap: () { | ||||
|                           if (factor.type == 0) { | ||||
|                             requestResetPassword(); | ||||
|                             return; | ||||
|                           } | ||||
|                           showModalBottomSheet( | ||||
|                             context: context, | ||||
|                             builder: | ||||
|                                 (context) => AuthFactorSheet(factor: factor), | ||||
|                           ).then((value) { | ||||
|                             if (value == true) { | ||||
|                               ref.invalidate(authFactorsProvider); | ||||
|                             } | ||||
|                           }); | ||||
|                         }, | ||||
|                       ), | ||||
|                     if (factors.isNotEmpty) Divider(height: 1), | ||||
|                     ListTile( | ||||
|                       minLeadingWidth: 48, | ||||
|                       contentPadding: const EdgeInsets.only( | ||||
|                         left: 24, | ||||
|                         right: 17, | ||||
|                       ), | ||||
|                       title: Text('authFactorNew').tr(), | ||||
|                       leading: const Icon(Symbols.add), | ||||
|                       trailing: const Icon(Symbols.chevron_right), | ||||
|                       onTap: () { | ||||
|                         showModalBottomSheet( | ||||
|                           context: context, | ||||
|                           builder: (context) => const AuthFactorNewSheet(), | ||||
|                         ).then((value) { | ||||
|                           if (value == true) { | ||||
|                             ref.invalidate(authFactorsProvider); | ||||
|                           } | ||||
|                         }); | ||||
|                       }, | ||||
|                     ), | ||||
|                   ], | ||||
|                 ), | ||||
|             error: | ||||
|                 (err, _) => ResponseErrorWidget( | ||||
|                   error: err, | ||||
|                   onRetry: () => ref.invalidate(authFactorsProvider), | ||||
|                 ), | ||||
|             loading: () => ResponseLoadingWidget(), | ||||
|           ), | ||||
|         ], | ||||
|       ), | ||||
|       ExpansionTile( | ||||
|         leading: const Icon( | ||||
|           Symbols.contact_mail, | ||||
|         ).alignment(Alignment.centerLeft).width(48), | ||||
|         title: Text('accountContactMethod').tr(), | ||||
|         subtitle: Text('accountContactMethodDescription').tr().fontSize(12), | ||||
|         tilePadding: const EdgeInsets.only(left: 24, right: 17), | ||||
|         children: [ | ||||
|           ref | ||||
|               .watch(contactMethodsProvider) | ||||
|               .when( | ||||
|                 data: | ||||
|                     (contacts) => Column( | ||||
|                       children: [ | ||||
|                         for (final contact in contacts) | ||||
|                           ListTile( | ||||
|                             minLeadingWidth: 48, | ||||
|                             contentPadding: const EdgeInsets.only( | ||||
|                               left: 16, | ||||
|                               right: 17, | ||||
|                               top: 2, | ||||
|                               bottom: 4, | ||||
|                             ), | ||||
|                             title: Text( | ||||
|                               contact.content, | ||||
|                               style: | ||||
|                                   contact.verifiedAt == null | ||||
|                                       ? TextStyle( | ||||
|                                         decoration: TextDecoration.lineThrough, | ||||
|                                       ) | ||||
|                                       : null, | ||||
|                             ), | ||||
|                             subtitle: Text( | ||||
|                               contact.type == 0 | ||||
|                                   ? 'contactMethodTypeEmail'.tr() | ||||
|                                   : 'contactMethodTypePhone'.tr(), | ||||
|                               style: | ||||
|                                   contact.verifiedAt == null | ||||
|                                       ? TextStyle( | ||||
|                                         decoration: TextDecoration.lineThrough, | ||||
|                                       ) | ||||
|                                       : null, | ||||
|                             ), | ||||
|                             leading: CircleAvatar( | ||||
|                               backgroundColor: | ||||
|                                   contact.verifiedAt == null | ||||
|                                       ? Theme.of( | ||||
|                                         context, | ||||
|                                       ).colorScheme.secondaryContainer | ||||
|                                       : Theme.of( | ||||
|                                         context, | ||||
|                                       ).colorScheme.primaryContainer, | ||||
|                               child: Icon( | ||||
|                                 contact.type == 0 | ||||
|                                     ? Symbols.mail | ||||
|                                     : Symbols.phone, | ||||
|                               ), | ||||
|                             ).padding(top: 4), | ||||
|                             trailing: const Icon(Symbols.chevron_right), | ||||
|                             isThreeLine: false, | ||||
|                             onTap: () { | ||||
|                               showModalBottomSheet( | ||||
|                                 context: context, | ||||
|                                 builder: | ||||
|                                     (context) => | ||||
|                                         ContactMethodSheet(contact: contact), | ||||
|                               ).then((value) { | ||||
|                                 if (value == true) { | ||||
|                                   ref.invalidate(contactMethodsProvider); | ||||
|                                 } | ||||
|                               }); | ||||
|                             }, | ||||
|                           ), | ||||
|                         if (contacts.isNotEmpty) const Divider(height: 1), | ||||
|                         ListTile( | ||||
|                           minLeadingWidth: 48, | ||||
|                           contentPadding: const EdgeInsets.only( | ||||
|                             left: 24, | ||||
|                             right: 17, | ||||
|                           ), | ||||
|                           title: Text('contactMethodNew').tr(), | ||||
|                           leading: const Icon(Symbols.add), | ||||
|                           trailing: const Icon(Symbols.chevron_right), | ||||
|                           onTap: () { | ||||
|                             showModalBottomSheet( | ||||
|                               context: context, | ||||
|                               builder: | ||||
|                                   (context) => const ContactMethodNewSheet(), | ||||
|                             ).then((value) { | ||||
|                               if (value == true) { | ||||
|                                 ref.invalidate(contactMethodsProvider); | ||||
|                               } | ||||
|                             }); | ||||
|                           }, | ||||
|                         ), | ||||
|                       ], | ||||
|                     ), | ||||
|                 error: | ||||
|                     (err, _) => ResponseErrorWidget( | ||||
|                       error: err, | ||||
|                       onRetry: () => ref.invalidate(contactMethodsProvider), | ||||
|                     ), | ||||
|                 loading: () => const ResponseLoadingWidget(), | ||||
|               ), | ||||
|         ], | ||||
|       ), | ||||
|     ]; | ||||
|  | ||||
|     final dangerZoneSettings = [ | ||||
|       ListTile( | ||||
|         minLeadingWidth: 48, | ||||
|         title: Text('accountDeletion').tr(), | ||||
|         subtitle: Text('accountDeletionDescription').tr().fontSize(12), | ||||
|         contentPadding: const EdgeInsets.only(left: 24, right: 17), | ||||
|         leading: const Icon(Symbols.delete_forever, color: Colors.red), | ||||
|         trailing: const Icon(Symbols.chevron_right), | ||||
|         onTap: requestAccountDeletion, | ||||
|       ), | ||||
|     ]; | ||||
|  | ||||
|     // Create a responsive layout based on screen width | ||||
|     Widget buildSettingsList() { | ||||
|       if (isWide) { | ||||
|         // Two-column layout for wide screens | ||||
|         return Row( | ||||
|           crossAxisAlignment: CrossAxisAlignment.start, | ||||
|           children: [ | ||||
|             Expanded( | ||||
|               child: Column( | ||||
|                 crossAxisAlignment: CrossAxisAlignment.start, | ||||
|                 children: [ | ||||
|                   _SettingsSection( | ||||
|                     title: 'accountSecurityTitle', | ||||
|                     children: securitySettings, | ||||
|                   ), | ||||
|                 ], | ||||
|               ), | ||||
|             ), | ||||
|             Expanded( | ||||
|               child: Column( | ||||
|                 crossAxisAlignment: CrossAxisAlignment.start, | ||||
|                 children: [ | ||||
|                   _SettingsSection( | ||||
|                     title: 'accountDangerZoneTitle', | ||||
|                     children: dangerZoneSettings, | ||||
|                   ), | ||||
|                 ], | ||||
|               ), | ||||
|             ), | ||||
|           ], | ||||
|         ).padding(horizontal: 16); | ||||
|       } else { | ||||
|         // Single column layout for narrow screens | ||||
|         return Column( | ||||
|           crossAxisAlignment: CrossAxisAlignment.start, | ||||
|           children: [ | ||||
|             _SettingsSection( | ||||
|               title: 'accountSecurityTitle', | ||||
|               children: securitySettings, | ||||
|             ), | ||||
|             _SettingsSection( | ||||
|               title: 'accountDangerZoneTitle', | ||||
|               children: dangerZoneSettings, | ||||
|             ), | ||||
|           ], | ||||
|         ); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     return AppScaffold( | ||||
|       appBar: AppBar(title: Text('accountSettings').tr()), | ||||
|       body: SingleChildScrollView(child: Column(children: [])), | ||||
|       appBar: AppBar( | ||||
|         title: Text('accountSettings').tr(), | ||||
|         actions: | ||||
|             isDesktop | ||||
|                 ? [ | ||||
|                   IconButton( | ||||
|                     icon: const Icon(Symbols.help_outline), | ||||
|                     onPressed: () { | ||||
|                       // Show help dialog | ||||
|                       showDialog( | ||||
|                         context: context, | ||||
|                         builder: | ||||
|                             (context) => AlertDialog( | ||||
|                               title: Text('accountSettingsHelp').tr(), | ||||
|                               content: Text('accountSettingsHelpContent').tr(), | ||||
|                               actions: [ | ||||
|                                 TextButton( | ||||
|                                   onPressed: () => Navigator.of(context).pop(), | ||||
|                                   child: Text('Close').tr(), | ||||
|                                 ), | ||||
|                               ], | ||||
|                             ), | ||||
|                       ); | ||||
|                     }, | ||||
|                   ), | ||||
|                 ] | ||||
|                 : null, | ||||
|       ), | ||||
|       body: Focus( | ||||
|         autofocus: true, | ||||
|         onKeyEvent: (node, event) { | ||||
|           // Add keyboard shortcuts for desktop | ||||
|           if (isDesktop && | ||||
|               event is KeyDownEvent && | ||||
|               event.logicalKey == LogicalKeyboardKey.escape) { | ||||
|             Navigator.of(context).pop(); | ||||
|             return KeyEventResult.handled; | ||||
|           } | ||||
|           return KeyEventResult.ignored; | ||||
|         }, | ||||
|         child: SingleChildScrollView( | ||||
|           padding: const EdgeInsets.symmetric(vertical: 16), | ||||
|           child: buildSettingsList(), | ||||
|         ), | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|  | ||||
| // Helper widget for displaying settings sections with titles | ||||
| class _SettingsSection extends StatelessWidget { | ||||
|   final String title; | ||||
|   final List<Widget> children; | ||||
|  | ||||
|   const _SettingsSection({required this.title, required this.children}); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return Column( | ||||
|       crossAxisAlignment: CrossAxisAlignment.start, | ||||
|       children: [ | ||||
|         Padding( | ||||
|           padding: const EdgeInsets.fromLTRB(24, 16, 24, 8), | ||||
|           child: Text( | ||||
|             title.tr(), | ||||
|             style: Theme.of(context).textTheme.titleMedium?.copyWith( | ||||
|               color: Theme.of(context).colorScheme.primary, | ||||
|               fontWeight: FontWeight.bold, | ||||
|             ), | ||||
|           ), | ||||
|         ), | ||||
|         ...children, | ||||
|         const SizedBox(height: 16), | ||||
|       ], | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|   | ||||
							
								
								
									
										69
									
								
								lib/screens/account/me/settings.g.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								lib/screens/account/me/settings.g.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,69 @@ | ||||
| // GENERATED CODE - DO NOT MODIFY BY HAND | ||||
|  | ||||
| part of 'settings.dart'; | ||||
|  | ||||
| // ************************************************************************** | ||||
| // RiverpodGenerator | ||||
| // ************************************************************************** | ||||
|  | ||||
| String _$authFactorsHash() => r'4bb65bc0c065c4091c209ee81e57ddef41051ae2'; | ||||
|  | ||||
| /// See also [authFactors]. | ||||
| @ProviderFor(authFactors) | ||||
| final authFactorsProvider = | ||||
|     AutoDisposeFutureProvider<List<SnAuthFactor>>.internal( | ||||
|       authFactors, | ||||
|       name: r'authFactorsProvider', | ||||
|       debugGetCreateSourceHash: | ||||
|           const bool.fromEnvironment('dart.vm.product') | ||||
|               ? null | ||||
|               : _$authFactorsHash, | ||||
|       dependencies: null, | ||||
|       allTransitiveDependencies: null, | ||||
|     ); | ||||
|  | ||||
| @Deprecated('Will be removed in 3.0. Use Ref instead') | ||||
| // ignore: unused_element | ||||
| typedef AuthFactorsRef = AutoDisposeFutureProviderRef<List<SnAuthFactor>>; | ||||
| String _$contactMethodsHash() => r'4d7952fc196dce4dc646314565a49c115fd1d292'; | ||||
|  | ||||
| /// See also [contactMethods]. | ||||
| @ProviderFor(contactMethods) | ||||
| final contactMethodsProvider = | ||||
|     AutoDisposeFutureProvider<List<SnContactMethod>>.internal( | ||||
|       contactMethods, | ||||
|       name: r'contactMethodsProvider', | ||||
|       debugGetCreateSourceHash: | ||||
|           const bool.fromEnvironment('dart.vm.product') | ||||
|               ? null | ||||
|               : _$contactMethodsHash, | ||||
|       dependencies: null, | ||||
|       allTransitiveDependencies: null, | ||||
|     ); | ||||
|  | ||||
| @Deprecated('Will be removed in 3.0. Use Ref instead') | ||||
| // ignore: unused_element | ||||
| typedef ContactMethodsRef = AutoDisposeFutureProviderRef<List<SnContactMethod>>; | ||||
| String _$accountConnectionsHash() => | ||||
|     r'38a309d596e0ea2539cd92ea86984e1e4fb346e4'; | ||||
|  | ||||
| /// See also [accountConnections]. | ||||
| @ProviderFor(accountConnections) | ||||
| final accountConnectionsProvider = | ||||
|     AutoDisposeFutureProvider<List<SnAccountConnection>>.internal( | ||||
|       accountConnections, | ||||
|       name: r'accountConnectionsProvider', | ||||
|       debugGetCreateSourceHash: | ||||
|           const bool.fromEnvironment('dart.vm.product') | ||||
|               ? null | ||||
|               : _$accountConnectionsHash, | ||||
|       dependencies: null, | ||||
|       allTransitiveDependencies: null, | ||||
|     ); | ||||
|  | ||||
| @Deprecated('Will be removed in 3.0. Use Ref instead') | ||||
| // ignore: unused_element | ||||
| typedef AccountConnectionsRef = | ||||
|     AutoDisposeFutureProviderRef<List<SnAccountConnection>>; | ||||
| // 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 | ||||
							
								
								
									
										342
									
								
								lib/screens/account/me/settings_auth_factors.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										342
									
								
								lib/screens/account/me/settings_auth_factors.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,342 @@ | ||||
| import 'dart:convert'; | ||||
|  | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter_hooks/flutter_hooks.dart'; | ||||
| import 'package:flutter_otp_text_field/flutter_otp_text_field.dart'; | ||||
| import 'package:gap/gap.dart'; | ||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||
| import 'package:island/models/auth.dart'; | ||||
| import 'package:island/pods/network.dart'; | ||||
| import 'package:island/screens/auth/login.dart'; | ||||
| import 'package:island/widgets/alert.dart'; | ||||
| import 'package:island/widgets/content/sheet.dart'; | ||||
| import 'package:material_symbols_icons/symbols.dart'; | ||||
| import 'package:qr_flutter/qr_flutter.dart'; | ||||
| import 'package:styled_widget/styled_widget.dart'; | ||||
|  | ||||
| class AuthFactorSheet extends HookConsumerWidget { | ||||
|   final SnAuthFactor factor; | ||||
|   const AuthFactorSheet({super.key, required this.factor}); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context, WidgetRef ref) { | ||||
|     Future<void> deleteFactor() async { | ||||
|       final confirm = await showConfirmAlert( | ||||
|         'authFactorDeleteHint'.tr(), | ||||
|         'authFactorDelete'.tr(), | ||||
|       ); | ||||
|       if (!confirm || !context.mounted) return; | ||||
|       try { | ||||
|         showLoadingModal(context); | ||||
|         final client = ref.read(apiClientProvider); | ||||
|         await client.delete('/accounts/me/factors/${factor.id}'); | ||||
|         if (context.mounted) Navigator.pop(context, true); | ||||
|       } catch (err) { | ||||
|         showErrorAlert(err); | ||||
|       } finally { | ||||
|         if (context.mounted) hideLoadingModal(context); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     Future<void> disableFactor() async { | ||||
|       final confirm = await showConfirmAlert( | ||||
|         'authFactorDisableHint'.tr(), | ||||
|         'authFactorDisable'.tr(), | ||||
|       ); | ||||
|       if (!confirm || !context.mounted) return; | ||||
|       try { | ||||
|         showLoadingModal(context); | ||||
|         final client = ref.read(apiClientProvider); | ||||
|         await client.post('/accounts/me/factors/${factor.id}/disable'); | ||||
|         if (context.mounted) Navigator.pop(context, true); | ||||
|       } catch (err) { | ||||
|         showErrorAlert(err); | ||||
|       } finally { | ||||
|         if (context.mounted) hideLoadingModal(context); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     Future<void> enableFactor() async { | ||||
|       String? password; | ||||
|       if ([3].contains(factor.type)) { | ||||
|         final confirmed = await showDialog<bool>( | ||||
|           context: context, | ||||
|           builder: | ||||
|               (context) => AlertDialog( | ||||
|                 title: Text('authFactorEnable').tr(), | ||||
|                 content: Column( | ||||
|                   mainAxisSize: MainAxisSize.min, | ||||
|                   children: [ | ||||
|                     Text('authFactorEnableHint').tr(), | ||||
|                     const SizedBox(height: 16), | ||||
|                     OtpTextField( | ||||
|                       showCursor: false, | ||||
|                       numberOfFields: 6, | ||||
|                       obscureText: false, | ||||
|                       showFieldAsBox: true, | ||||
|                       focusedBorderColor: Theme.of(context).colorScheme.primary, | ||||
|                       onSubmit: (String verificationCode) { | ||||
|                         password = verificationCode; | ||||
|                       }, | ||||
|                       textStyle: Theme.of(context).textTheme.titleLarge!, | ||||
|                     ), | ||||
|                   ], | ||||
|                 ), | ||||
|                 actions: [ | ||||
|                   TextButton( | ||||
|                     onPressed: () => Navigator.of(context).pop(false), | ||||
|                     child: Text('cancel').tr(), | ||||
|                   ), | ||||
|                   TextButton( | ||||
|                     onPressed: () => Navigator.of(context).pop(true), | ||||
|                     child: Text('confirm').tr(), | ||||
|                   ), | ||||
|                 ], | ||||
|               ), | ||||
|         ); | ||||
|         if (confirmed == false || | ||||
|             (password?.isEmpty ?? true) || | ||||
|             !context.mounted) { | ||||
|           return; | ||||
|         } | ||||
|       } | ||||
|       try { | ||||
|         showLoadingModal(context); | ||||
|         final client = ref.read(apiClientProvider); | ||||
|         await client.post( | ||||
|           '/accounts/me/factors/${factor.id}/enable', | ||||
|           data: jsonEncode(password), | ||||
|         ); | ||||
|         if (context.mounted) Navigator.pop(context, true); | ||||
|       } catch (err) { | ||||
|         showErrorAlert(err); | ||||
|       } finally { | ||||
|         if (context.mounted) hideLoadingModal(context); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     return SheetScaffold( | ||||
|       titleText: 'authFactor'.tr(), | ||||
|       child: Column( | ||||
|         crossAxisAlignment: CrossAxisAlignment.stretch, | ||||
|         children: [ | ||||
|           Column( | ||||
|             crossAxisAlignment: CrossAxisAlignment.start, | ||||
|             mainAxisAlignment: MainAxisAlignment.center, | ||||
|             children: [ | ||||
|               Icon(kFactorTypes[factor.type]!.$3, size: 32), | ||||
|               const Gap(8), | ||||
|               Text(kFactorTypes[factor.type]!.$1).tr(), | ||||
|               const Gap(4), | ||||
|               Text( | ||||
|                 kFactorTypes[factor.type]!.$2, | ||||
|                 style: Theme.of(context).textTheme.bodySmall, | ||||
|               ).tr(), | ||||
|               const Gap(10), | ||||
|               Row( | ||||
|                 children: [ | ||||
|                   if (factor.enabledAt == null) | ||||
|                     Badge( | ||||
|                       label: Text('authFactorDisabled'.tr()), | ||||
|                       textColor: Theme.of(context).colorScheme.onSecondary, | ||||
|                       backgroundColor: Theme.of(context).colorScheme.secondary, | ||||
|                     ) | ||||
|                   else | ||||
|                     Badge( | ||||
|                       label: Text('authFactorEnabled'.tr()), | ||||
|                       textColor: Theme.of(context).colorScheme.onPrimary, | ||||
|                       backgroundColor: Theme.of(context).colorScheme.primary, | ||||
|                     ), | ||||
|                 ], | ||||
|               ), | ||||
|             ], | ||||
|           ).padding(all: 20), | ||||
|           const Divider(height: 1), | ||||
|           if (factor.enabledAt != null) | ||||
|             ListTile( | ||||
|               leading: const Icon(Symbols.disabled_by_default), | ||||
|               title: Text('authFactorDisable').tr(), | ||||
|               onTap: disableFactor, | ||||
|               contentPadding: EdgeInsets.symmetric(horizontal: 20), | ||||
|             ) | ||||
|           else | ||||
|             ListTile( | ||||
|               leading: const Icon(Symbols.check_circle), | ||||
|               title: Text('authFactorEnable').tr(), | ||||
|               onTap: enableFactor, | ||||
|               contentPadding: EdgeInsets.symmetric(horizontal: 20), | ||||
|             ), | ||||
|           ListTile( | ||||
|             leading: const Icon(Symbols.delete), | ||||
|             title: Text('authFactorDelete').tr(), | ||||
|             onTap: deleteFactor, | ||||
|             contentPadding: EdgeInsets.symmetric(horizontal: 20), | ||||
|           ), | ||||
|         ], | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|  | ||||
| class AuthFactorNewSheet extends HookConsumerWidget { | ||||
|   const AuthFactorNewSheet({super.key}); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context, WidgetRef ref) { | ||||
|     final factorType = useState<int>(0); | ||||
|     final secretController = useTextEditingController(); | ||||
|  | ||||
|     Future<void> addFactor() async { | ||||
|       try { | ||||
|         showLoadingModal(context); | ||||
|         final apiClient = ref.read(apiClientProvider); | ||||
|         final resp = await apiClient.post( | ||||
|           '/accounts/me/factors', | ||||
|           data: {'type': factorType.value, 'secret': secretController.text}, | ||||
|         ); | ||||
|         final factor = SnAuthFactor.fromJson(resp.data); | ||||
|         if (!context.mounted) return; | ||||
|         hideLoadingModal(context); | ||||
|         if (factor.type == 3) { | ||||
|           showModalBottomSheet( | ||||
|             context: context, | ||||
|             builder: (context) => AuthFactorNewAdditonalSheet(factor: factor), | ||||
|           ).then((_) { | ||||
|             if (context.mounted) { | ||||
|               showSnackBar(context, 'contactMethodVerificationNeeded'.tr()); | ||||
|             } | ||||
|             if (context.mounted) Navigator.pop(context, true); | ||||
|           }); | ||||
|         } else { | ||||
|           Navigator.pop(context, true); | ||||
|         } | ||||
|       } catch (err) { | ||||
|         showErrorAlert(err); | ||||
|         if (context.mounted) hideLoadingModal(context); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     return SheetScaffold( | ||||
|       titleText: 'authFactorNew'.tr(), | ||||
|       child: Column( | ||||
|         spacing: 16, | ||||
|         crossAxisAlignment: CrossAxisAlignment.stretch, | ||||
|         children: [ | ||||
|           DropdownButtonFormField<int>( | ||||
|             value: factorType.value, | ||||
|             decoration: InputDecoration( | ||||
|               labelText: 'authFactor'.tr(), | ||||
|               border: const OutlineInputBorder(), | ||||
|             ), | ||||
|             items: | ||||
|                 kFactorTypes.entries.map((entry) { | ||||
|                   return DropdownMenuItem<int>( | ||||
|                     value: entry.key, | ||||
|                     child: Row( | ||||
|                       children: [ | ||||
|                         Icon(entry.value.$3), | ||||
|                         const Gap(8), | ||||
|                         Text(entry.value.$1).tr(), | ||||
|                       ], | ||||
|                     ), | ||||
|                   ); | ||||
|                 }).toList(), | ||||
|             onChanged: (value) { | ||||
|               if (value != null) { | ||||
|                 factorType.value = value; | ||||
|               } | ||||
|             }, | ||||
|           ), | ||||
|           if (factorType.value == 0) | ||||
|             TextField( | ||||
|               controller: secretController, | ||||
|               decoration: InputDecoration( | ||||
|                 prefixIcon: const Icon(Symbols.password_2), | ||||
|                 labelText: 'authFactorSecret'.tr(), | ||||
|                 hintText: 'authFactorSecretHint'.tr(), | ||||
|                 border: const OutlineInputBorder(), | ||||
|               ), | ||||
|               onTapOutside: | ||||
|                   (_) => FocusManager.instance.primaryFocus?.unfocus(), | ||||
|             ), | ||||
|           Padding( | ||||
|             padding: const EdgeInsets.symmetric(horizontal: 16.0), | ||||
|             child: Text(kFactorTypes[factorType.value]!.$2).tr(), | ||||
|           ), | ||||
|           Row( | ||||
|             mainAxisAlignment: MainAxisAlignment.end, | ||||
|             children: [ | ||||
|               TextButton.icon( | ||||
|                 onPressed: addFactor, | ||||
|                 icon: Icon(Symbols.add), | ||||
|                 label: Text('create').tr(), | ||||
|               ), | ||||
|             ], | ||||
|           ), | ||||
|         ], | ||||
|       ).padding(horizontal: 20, vertical: 24), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|  | ||||
| class AuthFactorNewAdditonalSheet extends StatelessWidget { | ||||
|   final SnAuthFactor factor; | ||||
|   const AuthFactorNewAdditonalSheet({super.key, required this.factor}); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     final uri = factor.createdResponse?['uri']; | ||||
|  | ||||
|     return SheetScaffold( | ||||
|       titleText: 'authFactorAdditional'.tr(), | ||||
|       child: Column( | ||||
|         crossAxisAlignment: CrossAxisAlignment.stretch, | ||||
|         children: [ | ||||
|           if (uri != null) ...[ | ||||
|             const SizedBox(height: 16), | ||||
|             Center( | ||||
|               child: ClipRRect( | ||||
|                 borderRadius: BorderRadius.circular(16), | ||||
|                 child: QrImageView( | ||||
|                   data: uri, | ||||
|                   version: QrVersions.auto, | ||||
|                   size: 200, | ||||
|                   backgroundColor: Theme.of(context).colorScheme.surface, | ||||
|                   foregroundColor: Theme.of(context).colorScheme.onSurface, | ||||
|                 ), | ||||
|               ), | ||||
|             ), | ||||
|             const Gap(16), | ||||
|             Padding( | ||||
|               padding: const EdgeInsets.symmetric(horizontal: 16), | ||||
|               child: Text( | ||||
|                 'authFactorQrCodeScan'.tr(), | ||||
|                 textAlign: TextAlign.center, | ||||
|                 style: Theme.of(context).textTheme.bodySmall, | ||||
|               ), | ||||
|             ), | ||||
|           ] else ...[ | ||||
|             const SizedBox(height: 16), | ||||
|             Center( | ||||
|               child: Text( | ||||
|                 'authFactorNoQrCode'.tr(), | ||||
|                 textAlign: TextAlign.center, | ||||
|                 style: Theme.of(context).textTheme.bodyMedium, | ||||
|               ), | ||||
|             ), | ||||
|           ], | ||||
|           const Gap(16), | ||||
|           Padding( | ||||
|             padding: const EdgeInsets.symmetric(horizontal: 16), | ||||
|             child: TextButton.icon( | ||||
|               onPressed: () => Navigator.of(context).pop(), | ||||
|               icon: const Icon(Symbols.check), | ||||
|               label: Text('next'.tr()), | ||||
|             ), | ||||
|           ), | ||||
|         ], | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										381
									
								
								lib/screens/account/me/settings_connections.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										381
									
								
								lib/screens/account/me/settings_connections.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,381 @@ | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter_hooks/flutter_hooks.dart'; | ||||
| import 'package:flutter_svg/flutter_svg.dart'; | ||||
| import 'package:gap/gap.dart'; | ||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||
| import 'package:island/models/auth.dart'; | ||||
| import 'package:island/pods/network.dart'; | ||||
| import 'package:island/screens/account/me/settings.dart'; | ||||
| import 'package:island/screens/auth/oidc.native.dart'; | ||||
| import 'package:island/services/text.dart'; | ||||
| import 'package:island/services/time.dart'; | ||||
| import 'package:island/widgets/alert.dart'; | ||||
| import 'package:island/widgets/content/sheet.dart'; | ||||
| import 'package:island/widgets/response.dart'; | ||||
| import 'package:material_symbols_icons/symbols.dart'; | ||||
| import 'package:sign_in_with_apple/sign_in_with_apple.dart'; | ||||
| import 'package:styled_widget/styled_widget.dart'; | ||||
|  | ||||
| // Helper function to get provider icon and localized name | ||||
| Widget getProviderIcon(String provider, {double size = 24, Color? color}) { | ||||
|   final providerLower = provider.toLowerCase(); | ||||
|  | ||||
|   // Check if we have an SVG for this provider | ||||
|   switch (providerLower) { | ||||
|     case 'apple': | ||||
|     case 'microsoft': | ||||
|     case 'google': | ||||
|     case 'github': | ||||
|     case 'discord': | ||||
|       return SvgPicture.asset( | ||||
|         'assets/images/oidc/$providerLower.svg', | ||||
|         width: size, | ||||
|         height: size, | ||||
|         color: color, | ||||
|       ); | ||||
|     default: | ||||
|       return Icon(Symbols.link, size: size); | ||||
|   } | ||||
| } | ||||
|  | ||||
| String getLocalizedProviderName(String provider) { | ||||
|   switch (provider.toLowerCase()) { | ||||
|     case 'apple': | ||||
|       return 'accountConnectionProviderApple'.tr(); | ||||
|     case 'microsoft': | ||||
|       return 'accountConnectionProviderMicrosoft'.tr(); | ||||
|     case 'google': | ||||
|       return 'accountConnectionProviderGoogle'.tr(); | ||||
|     case 'github': | ||||
|       return 'accountConnectionProviderGithub'.tr(); | ||||
|     case 'discord': | ||||
|       return 'accountConnectionProviderDiscord'.tr(); | ||||
|     default: | ||||
|       return provider; | ||||
|   } | ||||
| } | ||||
|  | ||||
| class AccountConnectionSheet extends HookConsumerWidget { | ||||
|   final SnAccountConnection connection; | ||||
|   const AccountConnectionSheet({super.key, required this.connection}); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context, WidgetRef ref) { | ||||
|     Future<void> deleteConnection() async { | ||||
|       final confirm = await showConfirmAlert( | ||||
|         'accountConnectionDeleteHint'.tr(), | ||||
|         'accountConnectionDelete'.tr(), | ||||
|       ); | ||||
|       if (!confirm || !context.mounted) return; | ||||
|       try { | ||||
|         showLoadingModal(context); | ||||
|         final client = ref.read(apiClientProvider); | ||||
|         await client.delete('/accounts/me/connections/${connection.id}'); | ||||
|         if (context.mounted) Navigator.pop(context, true); | ||||
|       } catch (err) { | ||||
|         showErrorAlert(err); | ||||
|       } finally { | ||||
|         if (context.mounted) hideLoadingModal(context); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     return SheetScaffold( | ||||
|       titleText: 'accountConnections'.tr(), | ||||
|       child: Column( | ||||
|         crossAxisAlignment: CrossAxisAlignment.stretch, | ||||
|         children: [ | ||||
|           Column( | ||||
|             crossAxisAlignment: CrossAxisAlignment.start, | ||||
|             mainAxisAlignment: MainAxisAlignment.center, | ||||
|             children: [ | ||||
|               getProviderIcon( | ||||
|                 connection.provider, | ||||
|                 size: 32, | ||||
|                 color: Theme.of(context).colorScheme.onSurface, | ||||
|               ), | ||||
|               const Gap(8), | ||||
|               Text(getLocalizedProviderName(connection.provider)).tr(), | ||||
|               const Gap(4), | ||||
|               if (connection.meta.isNotEmpty) | ||||
|                 Column( | ||||
|                   crossAxisAlignment: CrossAxisAlignment.stretch, | ||||
|                   mainAxisSize: MainAxisSize.min, | ||||
|                   children: [ | ||||
|                     for (final meta in connection.meta.entries) | ||||
|                       Text( | ||||
|                         '${meta.key.replaceAll('_', ' ').capitalizeEachWord()}: ${meta.value}', | ||||
|                         style: const TextStyle(fontSize: 12), | ||||
|                       ), | ||||
|                   ], | ||||
|                 ), | ||||
|               Text( | ||||
|                 connection.providedIdentifier, | ||||
|                 style: Theme.of(context).textTheme.bodySmall, | ||||
|               ), | ||||
|               const Gap(8), | ||||
|               Text( | ||||
|                 connection.lastUsedAt.formatSystem(), | ||||
|                 style: Theme.of(context).textTheme.bodySmall, | ||||
|               ).opacity(0.85), | ||||
|             ], | ||||
|           ).padding(all: 20), | ||||
|           const Divider(height: 1), | ||||
|           ListTile( | ||||
|             leading: const Icon(Symbols.delete), | ||||
|             title: Text('accountConnectionDelete').tr(), | ||||
|             onTap: deleteConnection, | ||||
|             contentPadding: const EdgeInsets.symmetric(horizontal: 20), | ||||
|           ), | ||||
|         ], | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|  | ||||
| class AccountConnectionNewSheet extends HookConsumerWidget { | ||||
|   const AccountConnectionNewSheet({super.key}); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context, WidgetRef ref) { | ||||
|     final selectedProvider = useState<String>('apple'); | ||||
|  | ||||
|     // List of available providers | ||||
|     final providers = ['apple', 'microsoft', 'google', 'github', 'discord']; | ||||
|  | ||||
|     Future<void> addConnection() async { | ||||
|       final client = ref.watch(apiClientProvider); | ||||
|  | ||||
|       switch (selectedProvider.value.toLowerCase()) { | ||||
|         case 'apple': | ||||
|           try { | ||||
|             final credential = await SignInWithApple.getAppleIDCredential( | ||||
|               scopes: [AppleIDAuthorizationScopes.email], | ||||
|               webAuthenticationOptions: WebAuthenticationOptions( | ||||
|                 clientId: 'dev.solsynth.solarpass', | ||||
|                 redirectUri: Uri.parse( | ||||
|                   'https://nt.solian.app/auth/callback/apple', | ||||
|                 ), | ||||
|               ), | ||||
|             ); | ||||
|  | ||||
|             if (context.mounted) showLoadingModal(context); | ||||
|  | ||||
|             await client.post( | ||||
|               '/auth/connect/apple/mobile', | ||||
|               data: { | ||||
|                 'identity_token': credential.identityToken!, | ||||
|                 'authorization_code': credential.authorizationCode, | ||||
|               }, | ||||
|             ); | ||||
|             if (context.mounted) { | ||||
|               showSnackBar(context, 'accountConnectionAddSuccess'.tr()); | ||||
|               Navigator.pop(context, true); | ||||
|             } | ||||
|           } catch (err) { | ||||
|             if (err is SignInWithAppleAuthorizationException) return; | ||||
|             showErrorAlert(err); | ||||
|           } finally { | ||||
|             if (context.mounted) hideLoadingModal(context); | ||||
|           } | ||||
|         case 'microsoft': | ||||
|         case 'google': | ||||
|         case 'github': | ||||
|         case 'discord': | ||||
|           await Navigator.of(context).push( | ||||
|             MaterialPageRoute( | ||||
|               builder: | ||||
|                   (context) => OidcScreen( | ||||
|                     provider: selectedProvider.value.toLowerCase(), | ||||
|                     title: | ||||
|                         'Connect with ${selectedProvider.value.capitalizeEachWord()}', | ||||
|                   ), | ||||
|             ), | ||||
|           ); | ||||
|           if (context.mounted) Navigator.pop(context, true); | ||||
|           break; | ||||
|         default: | ||||
|           showSnackBar(context, 'accountConnectionAddError'.tr()); | ||||
|           return; | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     return SheetScaffold( | ||||
|       titleText: 'accountConnectionAdd'.tr(), | ||||
|       child: Column( | ||||
|         spacing: 16, | ||||
|         crossAxisAlignment: CrossAxisAlignment.stretch, | ||||
|         children: [ | ||||
|           DropdownButtonFormField<String>( | ||||
|             value: selectedProvider.value, | ||||
|             decoration: InputDecoration( | ||||
|               prefixIcon: getProviderIcon( | ||||
|                 selectedProvider.value, | ||||
|                 size: 16, | ||||
|                 color: Theme.of(context).colorScheme.onSurface, | ||||
|               ).padding(all: 16), | ||||
|               labelText: 'accountConnectionProvider'.tr(), | ||||
|               border: const OutlineInputBorder(), | ||||
|             ), | ||||
|             items: | ||||
|                 providers.map((String provider) { | ||||
|                   return DropdownMenuItem<String>( | ||||
|                     value: provider, | ||||
|                     child: Row( | ||||
|                       children: [Text(getLocalizedProviderName(provider)).tr()], | ||||
|                     ), | ||||
|                   ); | ||||
|                 }).toList(), | ||||
|             onChanged: (String? newValue) { | ||||
|               if (newValue != null) { | ||||
|                 selectedProvider.value = newValue; | ||||
|               } | ||||
|             }, | ||||
|           ), | ||||
|           Padding( | ||||
|             padding: const EdgeInsets.symmetric(horizontal: 16.0), | ||||
|             child: Text('accountConnectionDescription'.tr()), | ||||
|           ), | ||||
|           Row( | ||||
|             mainAxisAlignment: MainAxisAlignment.end, | ||||
|             children: [ | ||||
|               TextButton.icon( | ||||
|                 onPressed: addConnection, | ||||
|                 icon: const Icon(Symbols.add), | ||||
|                 label: Text('next').tr(), | ||||
|               ), | ||||
|             ], | ||||
|           ), | ||||
|         ], | ||||
|       ).padding(horizontal: 20, vertical: 24), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|  | ||||
| class AccountConnectionsSheet extends HookConsumerWidget { | ||||
|   const AccountConnectionsSheet({super.key}); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context, WidgetRef ref) { | ||||
|     final connections = ref.watch(accountConnectionsProvider); | ||||
|  | ||||
|     return SheetScaffold( | ||||
|       titleText: 'accountConnections'.tr(), | ||||
|       actions: [ | ||||
|         IconButton( | ||||
|           icon: const Icon(Symbols.add), | ||||
|           onPressed: () async { | ||||
|             final result = await showModalBottomSheet<bool>( | ||||
|               context: context, | ||||
|               isScrollControlled: true, | ||||
|               builder: (context) => const AccountConnectionNewSheet(), | ||||
|             ); | ||||
|             if (result == true) { | ||||
|               ref.invalidate(accountConnectionsProvider); | ||||
|             } | ||||
|           }, | ||||
|         ), | ||||
|       ], | ||||
|       child: connections.when( | ||||
|         data: | ||||
|             (data) => RefreshIndicator( | ||||
|               onRefresh: | ||||
|                   () => Future.sync( | ||||
|                     () => ref.invalidate(accountConnectionsProvider), | ||||
|                   ), | ||||
|               child: | ||||
|                   data.isEmpty | ||||
|                       ? Center( | ||||
|                         child: Text( | ||||
|                           'accountConnectionsEmpty'.tr(), | ||||
|                           textAlign: TextAlign.center, | ||||
|                         ).padding(horizontal: 32), | ||||
|                       ) | ||||
|                       : ListView.builder( | ||||
|                         padding: EdgeInsets.zero, | ||||
|                         itemCount: data.length, | ||||
|                         itemBuilder: (context, index) { | ||||
|                           final connection = data[index]; | ||||
|                           return Dismissible( | ||||
|                             key: Key('connection-${connection.id}'), | ||||
|                             direction: DismissDirection.endToStart, | ||||
|                             background: Container( | ||||
|                               color: Colors.red, | ||||
|                               alignment: Alignment.centerRight, | ||||
|                               padding: const EdgeInsets.symmetric( | ||||
|                                 horizontal: 20, | ||||
|                               ), | ||||
|                               child: const Icon( | ||||
|                                 Icons.delete, | ||||
|                                 color: Colors.white, | ||||
|                               ), | ||||
|                             ), | ||||
|                             confirmDismiss: (direction) async { | ||||
|                               final confirm = await showConfirmAlert( | ||||
|                                 'accountConnectionDeleteHint'.tr(), | ||||
|                                 'accountConnectionDelete'.tr(), | ||||
|                               ); | ||||
|                               if (confirm && context.mounted) { | ||||
|                                 try { | ||||
|                                   final client = ref.read(apiClientProvider); | ||||
|                                   await client.delete( | ||||
|                                     '/accounts/me/connections/${connection.id}', | ||||
|                                   ); | ||||
|                                   ref.invalidate(accountConnectionsProvider); | ||||
|                                   return true; | ||||
|                                 } catch (err) { | ||||
|                                   showErrorAlert(err); | ||||
|                                   return false; | ||||
|                                 } | ||||
|                               } | ||||
|                               return false; | ||||
|                             }, | ||||
|                             child: ListTile( | ||||
|                               leading: getProviderIcon( | ||||
|                                 connection.provider, | ||||
|                                 color: Theme.of(context).colorScheme.onSurface, | ||||
|                               ), | ||||
|                               title: | ||||
|                                   Text( | ||||
|                                     getLocalizedProviderName( | ||||
|                                       connection.provider, | ||||
|                                     ), | ||||
|                                   ).tr(), | ||||
|                               subtitle: | ||||
|                                   connection.meta['email'] != null | ||||
|                                       ? Text(connection.meta['email']) | ||||
|                                       : Text(connection.providedIdentifier), | ||||
|                               trailing: Text( | ||||
|                                 DateFormat.yMd().format( | ||||
|                                   connection.lastUsedAt.toLocal(), | ||||
|                                 ), | ||||
|                                 style: Theme.of(context).textTheme.bodySmall, | ||||
|                               ), | ||||
|                               onTap: () async { | ||||
|                                 final result = await showModalBottomSheet<bool>( | ||||
|                                   context: context, | ||||
|                                   isScrollControlled: true, | ||||
|                                   builder: | ||||
|                                       (context) => AccountConnectionSheet( | ||||
|                                         connection: connection, | ||||
|                                       ), | ||||
|                                 ); | ||||
|                                 if (result == true) { | ||||
|                                   ref.invalidate(accountConnectionsProvider); | ||||
|                                 } | ||||
|                               }, | ||||
|                             ), | ||||
|                           ); | ||||
|                         }, | ||||
|                       ), | ||||
|             ), | ||||
|         error: | ||||
|             (err, _) => ResponseErrorWidget( | ||||
|               error: err, | ||||
|               onRetry: () => ref.invalidate(accountConnectionsProvider), | ||||
|             ), | ||||
|         loading: () => const ResponseLoadingWidget(), | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										281
									
								
								lib/screens/account/me/settings_contacts.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										281
									
								
								lib/screens/account/me/settings_contacts.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,281 @@ | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter_hooks/flutter_hooks.dart'; | ||||
| import 'package:gap/gap.dart'; | ||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||
| import 'package:island/models/user.dart'; | ||||
| import 'package:island/pods/network.dart'; | ||||
| import 'package:island/widgets/alert.dart'; | ||||
| import 'package:island/widgets/content/sheet.dart'; | ||||
| import 'package:material_symbols_icons/symbols.dart'; | ||||
| import 'package:styled_widget/styled_widget.dart'; | ||||
|  | ||||
| class ContactMethodSheet extends HookConsumerWidget { | ||||
|   final SnContactMethod contact; | ||||
|   const ContactMethodSheet({super.key, required this.contact}); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context, WidgetRef ref) { | ||||
|     Future<void> deleteContactMethod() async { | ||||
|       final confirm = await showConfirmAlert( | ||||
|         'contactMethodDeleteHint'.tr(), | ||||
|         'contactMethodDelete'.tr(), | ||||
|       ); | ||||
|       if (!confirm || !context.mounted) return; | ||||
|       try { | ||||
|         showLoadingModal(context); | ||||
|         final client = ref.read(apiClientProvider); | ||||
|         await client.delete('/accounts/me/contacts/${contact.id}'); | ||||
|         if (context.mounted) Navigator.pop(context, true); | ||||
|       } catch (err) { | ||||
|         showErrorAlert(err); | ||||
|       } finally { | ||||
|         if (context.mounted) hideLoadingModal(context); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     Future<void> verifyContactMethod() async { | ||||
|       try { | ||||
|         showLoadingModal(context); | ||||
|         final client = ref.read(apiClientProvider); | ||||
|         await client.post('/accounts/me/contacts/${contact.id}/verify'); | ||||
|         if (context.mounted) { | ||||
|           showSnackBar(context, 'contactMethodVerificationSent'.tr()); | ||||
|         } | ||||
|       } catch (err) { | ||||
|         showErrorAlert(err); | ||||
|       } finally { | ||||
|         if (context.mounted) hideLoadingModal(context); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     Future<void> setContactMethodAsPrimary() async { | ||||
|       try { | ||||
|         showLoadingModal(context); | ||||
|         final client = ref.read(apiClientProvider); | ||||
|         await client.post('/accounts/me/contacts/${contact.id}/primary'); | ||||
|         if (context.mounted) Navigator.pop(context, true); | ||||
|       } catch (err) { | ||||
|         showErrorAlert(err); | ||||
|       } finally { | ||||
|         if (context.mounted) hideLoadingModal(context); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     return SheetScaffold( | ||||
|       titleText: 'contactMethod'.tr(), | ||||
|       child: Column( | ||||
|         crossAxisAlignment: CrossAxisAlignment.stretch, | ||||
|         children: [ | ||||
|           Column( | ||||
|             crossAxisAlignment: CrossAxisAlignment.start, | ||||
|             mainAxisAlignment: MainAxisAlignment.center, | ||||
|             children: [ | ||||
|               Icon(switch (contact.type) { | ||||
|                 0 => Symbols.mail, | ||||
|                 1 => Symbols.phone, | ||||
|                 _ => Symbols.home, | ||||
|               }, size: 32), | ||||
|               const Gap(8), | ||||
|               Text(switch (contact.type) { | ||||
|                 0 => 'contactMethodTypeEmail'.tr(), | ||||
|                 1 => 'contactMethodTypePhone'.tr(), | ||||
|                 _ => 'contactMethodTypeAddress'.tr(), | ||||
|               }), | ||||
|               const Gap(4), | ||||
|               Text( | ||||
|                 contact.content, | ||||
|                 style: Theme.of(context).textTheme.bodySmall, | ||||
|               ), | ||||
|               const Gap(10), | ||||
|               Row( | ||||
|                 children: [ | ||||
|                   if (contact.verifiedAt == null) | ||||
|                     Badge( | ||||
|                       label: Text('contactMethodUnverified'.tr()), | ||||
|                       textColor: Theme.of(context).colorScheme.onSecondary, | ||||
|                       backgroundColor: Theme.of(context).colorScheme.secondary, | ||||
|                     ) | ||||
|                   else | ||||
|                     Badge( | ||||
|                       label: Text('contactMethodVerified'.tr()), | ||||
|                       textColor: Theme.of(context).colorScheme.onPrimary, | ||||
|                       backgroundColor: Theme.of(context).colorScheme.primary, | ||||
|                     ), | ||||
|                   if (contact.isPrimary) | ||||
|                     Padding( | ||||
|                       padding: const EdgeInsets.only(left: 8.0), | ||||
|                       child: Badge( | ||||
|                         label: Text('contactMethodPrimary'.tr()), | ||||
|                         textColor: Theme.of(context).colorScheme.onTertiary, | ||||
|                         backgroundColor: Theme.of(context).colorScheme.tertiary, | ||||
|                       ), | ||||
|                     ), | ||||
|                 ], | ||||
|               ), | ||||
|             ], | ||||
|           ).padding(all: 20), | ||||
|           const Divider(height: 1), | ||||
|           if (contact.verifiedAt == null) | ||||
|             ListTile( | ||||
|               leading: const Icon(Symbols.verified), | ||||
|               title: Text('contactMethodVerify').tr(), | ||||
|               onTap: verifyContactMethod, | ||||
|               contentPadding: EdgeInsets.symmetric(horizontal: 20), | ||||
|             ), | ||||
|           if (contact.verifiedAt != null && !contact.isPrimary) | ||||
|             ListTile( | ||||
|               leading: const Icon(Symbols.star), | ||||
|               title: Text('contactMethodSetPrimary').tr(), | ||||
|               onTap: setContactMethodAsPrimary, | ||||
|               contentPadding: EdgeInsets.symmetric(horizontal: 20), | ||||
|             ), | ||||
|           ListTile( | ||||
|             leading: const Icon(Symbols.delete), | ||||
|             title: Text('contactMethodDelete').tr(), | ||||
|             onTap: deleteContactMethod, | ||||
|             contentPadding: EdgeInsets.symmetric(horizontal: 20), | ||||
|           ), | ||||
|         ], | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|  | ||||
| class ContactMethodNewSheet extends HookConsumerWidget { | ||||
|   const ContactMethodNewSheet({super.key}); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context, WidgetRef ref) { | ||||
|     final contactType = useState<int>(0); | ||||
|     final contentController = useTextEditingController(); | ||||
|  | ||||
|     Future<void> addContactMethod() async { | ||||
|       if (contentController.text.isEmpty) { | ||||
|         showSnackBar(context, 'contactMethodContentEmpty'.tr()); | ||||
|         return; | ||||
|       } | ||||
|  | ||||
|       try { | ||||
|         showLoadingModal(context); | ||||
|         final apiClient = ref.read(apiClientProvider); | ||||
|         await apiClient.post( | ||||
|           '/accounts/me/contacts', | ||||
|           data: {'type': contactType.value, 'content': contentController.text}, | ||||
|         ); | ||||
|         if (context.mounted) { | ||||
|           showSnackBar(context, 'contactMethodVerificationNeeded'.tr()); | ||||
|           Navigator.pop(context, true); | ||||
|         } | ||||
|       } catch (err) { | ||||
|         showErrorAlert(err); | ||||
|       } finally { | ||||
|         if (context.mounted) hideLoadingModal(context); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     return SheetScaffold( | ||||
|       titleText: 'contactMethodNew'.tr(), | ||||
|       child: Column( | ||||
|         spacing: 16, | ||||
|         crossAxisAlignment: CrossAxisAlignment.stretch, | ||||
|         children: [ | ||||
|           DropdownButtonFormField<int>( | ||||
|             value: contactType.value, | ||||
|             decoration: InputDecoration( | ||||
|               labelText: 'contactMethodType'.tr(), | ||||
|               border: const OutlineInputBorder(), | ||||
|             ), | ||||
|             items: [ | ||||
|               DropdownMenuItem<int>( | ||||
|                 value: 0, | ||||
|                 child: Row( | ||||
|                   children: [ | ||||
|                     Icon(Symbols.mail), | ||||
|                     const Gap(8), | ||||
|                     Text('contactMethodTypeEmail'.tr()), | ||||
|                   ], | ||||
|                 ), | ||||
|               ), | ||||
|               DropdownMenuItem<int>( | ||||
|                 value: 1, | ||||
|                 child: Row( | ||||
|                   children: [ | ||||
|                     Icon(Symbols.phone), | ||||
|                     const Gap(8), | ||||
|                     Text('contactMethodTypePhone'.tr()), | ||||
|                   ], | ||||
|                 ), | ||||
|               ), | ||||
|               DropdownMenuItem<int>( | ||||
|                 value: 2, | ||||
|                 child: Row( | ||||
|                   children: [ | ||||
|                     Icon(Symbols.home), | ||||
|                     const Gap(8), | ||||
|                     Text('contactMethodTypeAddress'.tr()), | ||||
|                   ], | ||||
|                 ), | ||||
|               ), | ||||
|             ], | ||||
|             onChanged: (value) { | ||||
|               if (value != null) { | ||||
|                 contactType.value = value; | ||||
|               } | ||||
|             }, | ||||
|           ), | ||||
|           TextField( | ||||
|             controller: contentController, | ||||
|             decoration: InputDecoration( | ||||
|               prefixIcon: Icon(switch (contactType.value) { | ||||
|                 0 => Symbols.mail, | ||||
|                 1 => Symbols.phone, | ||||
|                 _ => Symbols.home, | ||||
|               }), | ||||
|               labelText: switch (contactType.value) { | ||||
|                 0 => 'contactMethodTypeEmail'.tr(), | ||||
|                 1 => 'contactMethodTypePhone'.tr(), | ||||
|                 _ => 'contactMethodTypeAddress'.tr(), | ||||
|               }, | ||||
|               hintText: switch (contactType.value) { | ||||
|                 0 => 'contactMethodEmailHint'.tr(), | ||||
|                 1 => 'contactMethodPhoneHint'.tr(), | ||||
|                 _ => 'contactMethodAddressHint'.tr(), | ||||
|               }, | ||||
|               border: const OutlineInputBorder(), | ||||
|             ), | ||||
|             keyboardType: switch (contactType.value) { | ||||
|               0 => TextInputType.emailAddress, | ||||
|               1 => TextInputType.phone, | ||||
|               _ => TextInputType.multiline, | ||||
|             }, | ||||
|             maxLines: switch (contactType.value) { | ||||
|               2 => 3, | ||||
|               _ => 1, | ||||
|             }, | ||||
|             onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), | ||||
|           ), | ||||
|           Padding( | ||||
|             padding: const EdgeInsets.symmetric(horizontal: 16.0), | ||||
|             child: | ||||
|                 Text(switch (contactType.value) { | ||||
|                   0 => 'contactMethodEmailDescription', | ||||
|                   1 => 'contactMethodPhoneDescription', | ||||
|                   _ => 'contactMethodAddressDescription', | ||||
|                 }).tr(), | ||||
|           ), | ||||
|           Row( | ||||
|             mainAxisAlignment: MainAxisAlignment.end, | ||||
|             children: [ | ||||
|               TextButton.icon( | ||||
|                 onPressed: addContactMethod, | ||||
|                 icon: Icon(Symbols.add), | ||||
|                 label: Text('create').tr(), | ||||
|               ), | ||||
|             ], | ||||
|           ), | ||||
|         ], | ||||
|       ).padding(horizontal: 20, vertical: 24), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| @@ -6,10 +6,12 @@ import 'package:flutter/material.dart'; | ||||
| import 'package:flutter_hooks/flutter_hooks.dart'; | ||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||
| import 'package:image_picker/image_picker.dart'; | ||||
| import 'package:island/models/file.dart'; | ||||
| import 'package:island/pods/config.dart'; | ||||
| import 'package:island/pods/network.dart'; | ||||
| import 'package:island/pods/userinfo.dart'; | ||||
| import 'package:island/services/file.dart'; | ||||
| import 'package:island/services/timezone.dart'; | ||||
| import 'package:island/widgets/alert.dart'; | ||||
| import 'package:island/widgets/app_scaffold.dart'; | ||||
| import 'package:island/widgets/content/cloud_files.dart'; | ||||
| @@ -59,19 +61,15 @@ class UpdateProfileScreen extends HookConsumerWidget { | ||||
|       submitting.value = true; | ||||
|       try { | ||||
|         final baseUrl = ref.watch(serverUrlProvider); | ||||
|         final atk = await getFreshAtk( | ||||
|           ref.watch(tokenPairProvider), | ||||
|           baseUrl, | ||||
|           onRefreshed: (atk, rtk) { | ||||
|             setTokenPair(ref.watch(sharedPreferencesProvider), atk, rtk); | ||||
|             ref.invalidate(tokenPairProvider); | ||||
|           }, | ||||
|         ); | ||||
|         if (atk == null) throw ArgumentError('Access token is null'); | ||||
|         final token = await getToken(ref.watch(tokenProvider)); | ||||
|         if (token == null) throw ArgumentError('Token is null'); | ||||
|         final cloudFile = | ||||
|             await putMediaToCloud( | ||||
|               fileData: result, | ||||
|               atk: atk, | ||||
|               fileData: UniversalFile( | ||||
|                 data: result, | ||||
|                 type: UniversalFileType.image, | ||||
|               ), | ||||
|               atk: token, | ||||
|               baseUrl: baseUrl, | ||||
|               filename: result.name, | ||||
|               mimetype: result.mimeType ?? 'image/jpeg', | ||||
| @@ -123,9 +121,33 @@ class UpdateProfileScreen extends HookConsumerWidget { | ||||
|     } | ||||
|  | ||||
|     final formKeyProfile = useMemoized(GlobalKey<FormState>.new, const []); | ||||
|     final birthday = useState<DateTime?>( | ||||
|       user.value!.profile.birthday?.toLocal(), | ||||
|     ); | ||||
|     final firstNameController = useTextEditingController( | ||||
|       text: user.value!.profile.firstName, | ||||
|     ); | ||||
|     final middleNameController = useTextEditingController( | ||||
|       text: user.value!.profile.middleName, | ||||
|     ); | ||||
|     final lastNameController = useTextEditingController( | ||||
|       text: user.value!.profile.lastName, | ||||
|     ); | ||||
|     final bioController = useTextEditingController( | ||||
|       text: user.value!.profile.bio, | ||||
|     ); | ||||
|     final genderController = useTextEditingController( | ||||
|       text: user.value!.profile.gender, | ||||
|     ); | ||||
|     final pronounsController = useTextEditingController( | ||||
|       text: user.value!.profile.pronouns, | ||||
|     ); | ||||
|     final locationController = useTextEditingController( | ||||
|       text: user.value!.profile.location, | ||||
|     ); | ||||
|     final timeZoneController = useTextEditingController( | ||||
|       text: user.value!.profile.timeZone, | ||||
|     ); | ||||
|  | ||||
|     void updateProfile() async { | ||||
|       if (!formKeyProfile.currentState!.validate()) return; | ||||
| @@ -135,7 +157,17 @@ class UpdateProfileScreen extends HookConsumerWidget { | ||||
|         final client = ref.watch(apiClientProvider); | ||||
|         await client.patch( | ||||
|           '/accounts/me/profile', | ||||
|           data: {'bio': bioController.text}, | ||||
|           data: { | ||||
|             'bio': bioController.text, | ||||
|             'first_name': firstNameController.text, | ||||
|             'middle_name': middleNameController.text, | ||||
|             'last_name': lastNameController.text, | ||||
|             'gender': genderController.text, | ||||
|             'pronouns': pronounsController.text, | ||||
|             'location': locationController.text, | ||||
|             'time_zone': timeZoneController.text, | ||||
|             'birthday': birthday.value?.toUtc().toIso8601String(), | ||||
|           }, | ||||
|         ); | ||||
|         final userNotifier = ref.read(userInfoProvider.notifier); | ||||
|         userNotifier.fetchUser(); | ||||
| @@ -166,9 +198,9 @@ class UpdateProfileScreen extends HookConsumerWidget { | ||||
|                     child: Container( | ||||
|                       color: Theme.of(context).colorScheme.surfaceContainerHigh, | ||||
|                       child: | ||||
|                           user.value!.profile.backgroundId != null | ||||
|                           user.value!.profile.background?.id != null | ||||
|                               ? CloudImageWidget( | ||||
|                                 fileId: user.value!.profile.backgroundId!, | ||||
|                                 fileId: user.value!.profile.background!.id, | ||||
|                                 fit: BoxFit.cover, | ||||
|                               ) | ||||
|                               : const SizedBox.shrink(), | ||||
| @@ -182,7 +214,7 @@ class UpdateProfileScreen extends HookConsumerWidget { | ||||
|                     bottom: -32, | ||||
|                     child: GestureDetector( | ||||
|                       child: ProfilePictureWidget( | ||||
|                         fileId: user.value!.profile.pictureId, | ||||
|                         fileId: user.value!.profile.picture?.id, | ||||
|                         radius: 40, | ||||
|                       ), | ||||
|                       onTap: () { | ||||
| @@ -271,6 +303,45 @@ class UpdateProfileScreen extends HookConsumerWidget { | ||||
|                 crossAxisAlignment: CrossAxisAlignment.start, | ||||
|                 spacing: 16, | ||||
|                 children: [ | ||||
|                   Row( | ||||
|                     spacing: 16, | ||||
|                     children: [ | ||||
|                       Expanded( | ||||
|                         child: TextFormField( | ||||
|                           decoration: InputDecoration( | ||||
|                             labelText: 'firstName'.tr(), | ||||
|                           ), | ||||
|                           controller: firstNameController, | ||||
|                           onTapOutside: | ||||
|                               (_) => | ||||
|                                   FocusManager.instance.primaryFocus?.unfocus(), | ||||
|                         ), | ||||
|                       ), | ||||
|                       Expanded( | ||||
|                         child: TextFormField( | ||||
|                           decoration: InputDecoration( | ||||
|                             labelText: 'middleName'.tr(), | ||||
|                           ), | ||||
|                           controller: middleNameController, | ||||
|                           onTapOutside: | ||||
|                               (_) => | ||||
|                                   FocusManager.instance.primaryFocus?.unfocus(), | ||||
|                         ), | ||||
|                       ), | ||||
|                       Expanded( | ||||
|                         child: TextFormField( | ||||
|                           decoration: InputDecoration( | ||||
|                             labelText: 'lastName'.tr(), | ||||
|                           ), | ||||
|                           controller: lastNameController, | ||||
|                           onTapOutside: | ||||
|                               (_) => | ||||
|                                   FocusManager.instance.primaryFocus?.unfocus(), | ||||
|                         ), | ||||
|                       ), | ||||
|                     ], | ||||
|                   ), | ||||
|  | ||||
|                   TextFormField( | ||||
|                     decoration: InputDecoration(labelText: 'bio'.tr()), | ||||
|                     maxLines: null, | ||||
| @@ -279,6 +350,213 @@ class UpdateProfileScreen extends HookConsumerWidget { | ||||
|                     onTapOutside: | ||||
|                         (_) => FocusManager.instance.primaryFocus?.unfocus(), | ||||
|                   ), | ||||
|                   Row( | ||||
|                     spacing: 16, | ||||
|                     children: [ | ||||
|                       Expanded( | ||||
|                         child: Autocomplete<String>( | ||||
|                           optionsBuilder: (TextEditingValue textEditingValue) { | ||||
|                             final options = ['Male', 'Female']; | ||||
|                             if (textEditingValue.text == '') { | ||||
|                               return options; | ||||
|                             } | ||||
|                             return options.where( | ||||
|                               (option) => option.toLowerCase().contains( | ||||
|                                 textEditingValue.text.toLowerCase(), | ||||
|                               ), | ||||
|                             ); | ||||
|                           }, | ||||
|                           onSelected: (String selection) { | ||||
|                             genderController.text = selection; | ||||
|                           }, | ||||
|                           fieldViewBuilder: ( | ||||
|                             context, | ||||
|                             controller, | ||||
|                             focusNode, | ||||
|                             onFieldSubmitted, | ||||
|                           ) { | ||||
|                             // Initialize the controller with the current value | ||||
|                             if (controller.text.isEmpty && | ||||
|                                 genderController.text.isNotEmpty) { | ||||
|                               controller.text = genderController.text; | ||||
|                             } | ||||
|  | ||||
|                             return TextFormField( | ||||
|                               controller: controller, | ||||
|                               focusNode: focusNode, | ||||
|                               decoration: InputDecoration( | ||||
|                                 labelText: 'gender'.tr(), | ||||
|                               ), | ||||
|                               onChanged: (value) { | ||||
|                                 genderController.text = value; | ||||
|                               }, | ||||
|                               onTapOutside: | ||||
|                                   (_) => | ||||
|                                       FocusManager.instance.primaryFocus | ||||
|                                           ?.unfocus(), | ||||
|                             ); | ||||
|                           }, | ||||
|                         ), | ||||
|                       ), | ||||
|                       Expanded( | ||||
|                         child: TextFormField( | ||||
|                           decoration: InputDecoration( | ||||
|                             labelText: 'pronouns'.tr(), | ||||
|                           ), | ||||
|                           controller: pronounsController, | ||||
|                           onTapOutside: | ||||
|                               (_) => | ||||
|                                   FocusManager.instance.primaryFocus?.unfocus(), | ||||
|                         ), | ||||
|                       ), | ||||
|                     ], | ||||
|                   ), | ||||
|                   Row( | ||||
|                     spacing: 16, | ||||
|                     children: [ | ||||
|                       Expanded( | ||||
|                         child: TextFormField( | ||||
|                           decoration: InputDecoration( | ||||
|                             labelText: 'location'.tr(), | ||||
|                           ), | ||||
|                           controller: locationController, | ||||
|                           onTapOutside: | ||||
|                               (_) => | ||||
|                                   FocusManager.instance.primaryFocus?.unfocus(), | ||||
|                         ), | ||||
|                       ), | ||||
|                       Expanded( | ||||
|                         child: Autocomplete<String>( | ||||
|                           optionsBuilder: (TextEditingValue textEditingValue) { | ||||
|                             if (textEditingValue.text.isEmpty) { | ||||
|                               return const Iterable<String>.empty(); | ||||
|                             } | ||||
|                             final lowercaseQuery = | ||||
|                                 textEditingValue.text.toLowerCase(); | ||||
|                             return getAvailableTz().where((tz) { | ||||
|                               return tz.toLowerCase().contains(lowercaseQuery); | ||||
|                             }); | ||||
|                           }, | ||||
|                           onSelected: (String selection) { | ||||
|                             timeZoneController.text = selection; | ||||
|                           }, | ||||
|                           fieldViewBuilder: ( | ||||
|                             context, | ||||
|                             controller, | ||||
|                             focusNode, | ||||
|                             onFieldSubmitted, | ||||
|                           ) { | ||||
|                             // Sync the controller with timeZoneController when the widget is built | ||||
|                             if (controller.text != timeZoneController.text) { | ||||
|                               controller.text = timeZoneController.text; | ||||
|                             } | ||||
|  | ||||
|                             return TextFormField( | ||||
|                               controller: controller, | ||||
|                               focusNode: focusNode, | ||||
|                               decoration: InputDecoration( | ||||
|                                 labelText: 'timeZone'.tr(), | ||||
|                                 suffix: InkWell( | ||||
|                                   child: const Icon( | ||||
|                                     Symbols.my_location, | ||||
|                                     size: 18, | ||||
|                                   ), | ||||
|                                   onTap: () async { | ||||
|                                     try { | ||||
|                                       showLoadingModal(context); | ||||
|                                       final machineTz = await getMachineTz(); | ||||
|                                       controller.text = machineTz; | ||||
|                                       timeZoneController.text = machineTz; | ||||
|                                     } finally { | ||||
|                                       if (context.mounted) { | ||||
|                                         hideLoadingModal(context); | ||||
|                                       } | ||||
|                                     } | ||||
|                                   }, | ||||
|                                 ), | ||||
|                               ), | ||||
|                               onChanged: (value) { | ||||
|                                 timeZoneController.text = value; | ||||
|                               }, | ||||
|                             ); | ||||
|                           }, | ||||
|                           optionsViewBuilder: (context, onSelected, options) { | ||||
|                             return Align( | ||||
|                               alignment: Alignment.topLeft, | ||||
|                               child: Material( | ||||
|                                 elevation: 4.0, | ||||
|                                 child: ConstrainedBox( | ||||
|                                   constraints: const BoxConstraints( | ||||
|                                     maxHeight: 200, | ||||
|                                     maxWidth: 300, | ||||
|                                   ), | ||||
|                                   child: ListView.builder( | ||||
|                                     padding: const EdgeInsets.all(8.0), | ||||
|                                     itemCount: options.length, | ||||
|                                     itemBuilder: ( | ||||
|                                       BuildContext context, | ||||
|                                       int index, | ||||
|                                     ) { | ||||
|                                       final option = options.elementAt(index); | ||||
|                                       return ListTile( | ||||
|                                         title: Text( | ||||
|                                           option, | ||||
|                                           overflow: TextOverflow.ellipsis, | ||||
|                                         ), | ||||
|                                         onTap: () { | ||||
|                                           onSelected(option); | ||||
|                                         }, | ||||
|                                       ); | ||||
|                                     }, | ||||
|                                   ), | ||||
|                                 ), | ||||
|                               ), | ||||
|                             ); | ||||
|                           }, | ||||
|                         ), | ||||
|                       ), | ||||
|                     ], | ||||
|                   ), | ||||
|                   GestureDetector( | ||||
|                     onTap: () async { | ||||
|                       final date = await showDatePicker( | ||||
|                         context: context, | ||||
|                         initialDate: birthday.value ?? DateTime.now(), | ||||
|                         firstDate: DateTime(1900), | ||||
|                         lastDate: DateTime.now(), | ||||
|                       ); | ||||
|                       if (date != null) { | ||||
|                         birthday.value = date; | ||||
|                       } | ||||
|                     }, | ||||
|                     child: Container( | ||||
|                       padding: const EdgeInsets.symmetric(vertical: 8), | ||||
|                       decoration: BoxDecoration( | ||||
|                         border: Border( | ||||
|                           bottom: BorderSide( | ||||
|                             color: Theme.of(context).dividerColor, | ||||
|                             width: 1, | ||||
|                           ), | ||||
|                         ), | ||||
|                       ), | ||||
|                       child: Column( | ||||
|                         crossAxisAlignment: CrossAxisAlignment.stretch, | ||||
|                         children: [ | ||||
|                           Text( | ||||
|                             'birthday'.tr(), | ||||
|                             style: TextStyle( | ||||
|                               color: Theme.of(context).hintColor, | ||||
|                             ), | ||||
|                           ), | ||||
|                           Text( | ||||
|                             birthday.value != null | ||||
|                                 ? DateFormat.yMMMd().format(birthday.value!) | ||||
|                                 : 'Select a date'.tr(), | ||||
|                           ), | ||||
|                         ], | ||||
|                       ), | ||||
|                     ), | ||||
|                   ), | ||||
|                   Align( | ||||
|                     alignment: Alignment.centerRight, | ||||
|                     child: TextButton.icon( | ||||
|   | ||||
| @@ -1,15 +1,29 @@ | ||||
| import 'package:auto_route/auto_route.dart'; | ||||
| import 'package:dio/dio.dart'; | ||||
| 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/chat.dart'; | ||||
| import 'package:island/models/relationship.dart'; | ||||
| import 'package:island/models/user.dart'; | ||||
| import 'package:island/pods/config.dart'; | ||||
| import 'package:island/pods/event_calendar.dart'; | ||||
| import 'package:island/pods/network.dart'; | ||||
| import 'package:island/pods/userinfo.dart'; | ||||
| import 'package:island/services/color.dart'; | ||||
| import 'package:island/services/time.dart'; | ||||
| import 'package:island/services/timezone/native.dart'; | ||||
| import 'package:island/widgets/account/account_name.dart'; | ||||
| import 'package:island/widgets/account/badge.dart'; | ||||
| import 'package:island/widgets/account/fortune_graph.dart'; | ||||
| import 'package:island/widgets/account/leveling_progress.dart'; | ||||
| import 'package:island/widgets/account/status.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:palette_generator/palette_generator.dart'; | ||||
| import 'package:riverpod_annotation/riverpod_annotation.dart'; | ||||
| import 'package:styled_widget/styled_widget.dart'; | ||||
|  | ||||
| @@ -17,6 +31,12 @@ part 'profile.g.dart'; | ||||
|  | ||||
| @riverpod | ||||
| Future<SnAccount> account(Ref ref, String uname) async { | ||||
|   if (uname == 'me') { | ||||
|     final userInfo = ref.watch(userInfoProvider); | ||||
|     if (userInfo.hasValue && userInfo.value != null) { | ||||
|       return userInfo.value!; | ||||
|     } | ||||
|   } | ||||
|   final apiClient = ref.watch(apiClientProvider); | ||||
|   final resp = await apiClient.get("/accounts/$uname"); | ||||
|   return SnAccount.fromJson(resp.data); | ||||
| @@ -31,6 +51,51 @@ Future<List<SnAccountBadge>> accountBadges(Ref ref, String uname) async { | ||||
|   ); | ||||
| } | ||||
|  | ||||
| @riverpod | ||||
| Future<Color?> accountAppbarForcegroundColor(Ref ref, String uname) async { | ||||
|   final account = await ref.watch(accountProvider(uname).future); | ||||
|   if (account.profile.background == null) return null; | ||||
|   final palette = await PaletteGenerator.fromImageProvider( | ||||
|     CloudImageWidget.provider( | ||||
|       fileId: account.profile.background!.id, | ||||
|       serverUrl: ref.watch(serverUrlProvider), | ||||
|     ), | ||||
|   ); | ||||
|   final dominantColor = palette.dominantColor?.color; | ||||
|   if (dominantColor == null) return null; | ||||
|   return dominantColor.computeLuminance() > 0.5 ? Colors.black : Colors.white; | ||||
| } | ||||
|  | ||||
| @riverpod | ||||
| Future<SnChatRoom?> accountDirectChat(Ref ref, String uname) async { | ||||
|   final account = await ref.watch(accountProvider(uname).future); | ||||
|   final apiClient = ref.watch(apiClientProvider); | ||||
|   try { | ||||
|     final resp = await apiClient.get("/chat/direct/${account.id}"); | ||||
|     return SnChatRoom.fromJson(resp.data); | ||||
|   } catch (err) { | ||||
|     if (err is DioException && err.response?.statusCode == 404) { | ||||
|       return null; | ||||
|     } | ||||
|     rethrow; | ||||
|   } | ||||
| } | ||||
|  | ||||
| @riverpod | ||||
| Future<SnRelationship?> accountRelationship(Ref ref, String uname) async { | ||||
|   final account = await ref.watch(accountProvider(uname).future); | ||||
|   final apiClient = ref.watch(apiClientProvider); | ||||
|   try { | ||||
|     final resp = await apiClient.get("/relationships/${account.id}"); | ||||
|     return SnRelationship.fromJson(resp.data); | ||||
|   } catch (err) { | ||||
|     if (err is DioException && err.response?.statusCode == 404) { | ||||
|       return null; | ||||
|     } | ||||
|     rethrow; | ||||
|   } | ||||
| } | ||||
|  | ||||
| @RoutePage() | ||||
| class AccountProfileScreen extends HookConsumerWidget { | ||||
|   final String name; | ||||
| @@ -41,13 +106,118 @@ class AccountProfileScreen extends HookConsumerWidget { | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context, WidgetRef ref) { | ||||
|     final account = ref.watch(accountProvider(name)); | ||||
|     final now = DateTime.now(); | ||||
|  | ||||
|     final iconShadow = Shadow( | ||||
|       color: Colors.black54, | ||||
|       blurRadius: 5.0, | ||||
|       offset: const Offset(1.0, 1.0), | ||||
|     final account = ref.watch(accountProvider(name)); | ||||
|     final accountEvents = ref.watch( | ||||
|       eventCalendarProvider( | ||||
|         EventCalendarQuery(uname: name, year: now.year, month: now.month), | ||||
|       ), | ||||
|     ); | ||||
|     final accountChat = ref.watch(accountDirectChatProvider(name)); | ||||
|     final accountRelationship = ref.watch(accountRelationshipProvider(name)); | ||||
|  | ||||
|     final appbarColor = ref.watch(accountAppbarForcegroundColorProvider(name)); | ||||
|  | ||||
|     final appbarShadow = Shadow( | ||||
|       color: appbarColor.value?.invert ?? Colors.transparent, | ||||
|       blurRadius: 5.0, | ||||
|       offset: Offset(1.0, 1.0), | ||||
|     ); | ||||
|  | ||||
|     Future<void> relationshipAction() async { | ||||
|       if (accountRelationship.value != null) return; | ||||
|       showLoadingModal(context); | ||||
|       try { | ||||
|         final client = ref.watch(apiClientProvider); | ||||
|         await client.post('/relationships/${account.value!.id}/friends'); | ||||
|         ref.invalidate(accountRelationshipProvider(name)); | ||||
|       } catch (err) { | ||||
|         showErrorAlert(err); | ||||
|       } finally { | ||||
|         if (context.mounted) hideLoadingModal(context); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     Future<void> directMessageAction() async { | ||||
|       if (!account.hasValue) return; | ||||
|       if (accountChat.value != null) { | ||||
|         context.router.pushPath('/chat/${accountChat.value!.id}'); | ||||
|         return; | ||||
|       } | ||||
|       showLoadingModal(context); | ||||
|       try { | ||||
|         final client = ref.watch(apiClientProvider); | ||||
|         final resp = await client.post( | ||||
|           '/chat/direct', | ||||
|           data: {'related_user_id': account.value!.id}, | ||||
|         ); | ||||
|         final chat = SnChatRoom.fromJson(resp.data); | ||||
|         if (context.mounted) context.router.pushPath('/chat/${chat.id}'); | ||||
|         ref.invalidate(accountDirectChatProvider(name)); | ||||
|       } catch (err) { | ||||
|         showErrorAlert(err); | ||||
|       } finally { | ||||
|         if (context.mounted) hideLoadingModal(context); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     List<Widget> buildSubcolumn(SnAccount data) { | ||||
|       return [ | ||||
|         if (data.profile.birthday != null) | ||||
|           Row( | ||||
|             spacing: 6, | ||||
|             children: [ | ||||
|               const Icon(Symbols.cake, size: 17, fill: 1), | ||||
|               Text(data.profile.birthday!.formatCustom('yyyy-MM-dd')), | ||||
|               Text('·').bold(), | ||||
|               Text( | ||||
|                 '${DateTime.now().difference(data.profile.birthday!).inDays ~/ 365} yrs old', | ||||
|               ), | ||||
|             ], | ||||
|           ), | ||||
|         if (data.profile.location.isNotEmpty) | ||||
|           Row( | ||||
|             spacing: 6, | ||||
|             children: [ | ||||
|               const Icon(Symbols.location_on, size: 17, fill: 1), | ||||
|               Text(data.profile.location), | ||||
|             ], | ||||
|           ), | ||||
|         if (data.profile.pronouns.isNotEmpty || data.profile.gender.isNotEmpty) | ||||
|           Row( | ||||
|             spacing: 6, | ||||
|             children: [ | ||||
|               const Icon(Symbols.person, size: 17, fill: 1), | ||||
|               Text( | ||||
|                 data.profile.gender.isEmpty | ||||
|                     ? 'unspecified'.tr() | ||||
|                     : data.profile.gender, | ||||
|               ), | ||||
|               Text('·').bold(), | ||||
|               Text( | ||||
|                 data.profile.pronouns.isEmpty | ||||
|                     ? 'unspecified'.tr() | ||||
|                     : data.profile.pronouns, | ||||
|               ), | ||||
|             ], | ||||
|           ), | ||||
|         if (data.profile.firstName.isNotEmpty || | ||||
|             data.profile.middleName.isNotEmpty || | ||||
|             data.profile.lastName.isNotEmpty) | ||||
|           Row( | ||||
|             spacing: 6, | ||||
|             children: [ | ||||
|               const Icon(Symbols.id_card, size: 17, fill: 1), | ||||
|               if (data.profile.firstName.isNotEmpty) | ||||
|                 Text(data.profile.firstName), | ||||
|               if (data.profile.middleName.isNotEmpty) | ||||
|                 Text(data.profile.middleName), | ||||
|               if (data.profile.lastName.isNotEmpty) Text(data.profile.lastName), | ||||
|             ], | ||||
|           ), | ||||
|       ]; | ||||
|     } | ||||
|  | ||||
|     return account.when( | ||||
|       data: | ||||
| @@ -55,26 +225,40 @@ class AccountProfileScreen extends HookConsumerWidget { | ||||
|             body: CustomScrollView( | ||||
|               slivers: [ | ||||
|                 SliverAppBar( | ||||
|                   foregroundColor: appbarColor.value, | ||||
|                   expandedHeight: 180, | ||||
|                   pinned: true, | ||||
|                   leading: PageBackButton(shadows: [iconShadow]), | ||||
|                   flexibleSpace: FlexibleSpaceBar( | ||||
|                     background: | ||||
|                         data.profile.backgroundId != null | ||||
|                             ? CloudImageWidget( | ||||
|                               fileId: data.profile.backgroundId!, | ||||
|                             ) | ||||
|                             : Container( | ||||
|                               color: | ||||
|                                   Theme.of(context).appBarTheme.backgroundColor, | ||||
|                             ), | ||||
|                     title: Text( | ||||
|                       data.nick, | ||||
|                       style: TextStyle( | ||||
|                         color: Theme.of(context).appBarTheme.foregroundColor, | ||||
|                         shadows: [iconShadow], | ||||
|                   leading: PageBackButton( | ||||
|                     color: appbarColor.value, | ||||
|                     shadows: [appbarShadow], | ||||
|                   ), | ||||
|                   flexibleSpace: Stack( | ||||
|                     children: [ | ||||
|                       Positioned.fill( | ||||
|                         child: | ||||
|                             data.profile.background?.id != null | ||||
|                                 ? CloudImageWidget( | ||||
|                                   file: data.profile.background, | ||||
|                                 ) | ||||
|                                 : Container( | ||||
|                                   color: | ||||
|                                       Theme.of( | ||||
|                                         context, | ||||
|                                       ).appBarTheme.backgroundColor, | ||||
|                                 ), | ||||
|                       ), | ||||
|                     ), | ||||
|                       FlexibleSpaceBar( | ||||
|                         title: Text( | ||||
|                           data.nick, | ||||
|                           style: TextStyle( | ||||
|                             color: | ||||
|                                 appbarColor.value ?? | ||||
|                                 Theme.of(context).appBarTheme.foregroundColor, | ||||
|                             shadows: [appbarShadow], | ||||
|                           ), | ||||
|                         ), | ||||
|                       ), | ||||
|                     ], | ||||
|                   ), | ||||
|                 ), | ||||
|                 SliverToBoxAdapter( | ||||
| @@ -84,7 +268,7 @@ class AccountProfileScreen extends HookConsumerWidget { | ||||
|                       crossAxisAlignment: CrossAxisAlignment.start, | ||||
|                       children: [ | ||||
|                         ProfilePictureWidget( | ||||
|                           fileId: data.profile.pictureId, | ||||
|                           file: data.profile.picture, | ||||
|                           radius: 32, | ||||
|                         ), | ||||
|                         const Gap(20), | ||||
| @@ -117,29 +301,144 @@ class AccountProfileScreen extends HookConsumerWidget { | ||||
|                     child: BadgeList( | ||||
|                       badges: data.badges, | ||||
|                     ).padding(horizontal: 24, bottom: 24), | ||||
|                   ) | ||||
|                 else | ||||
|                   const SliverGap(4), | ||||
|                 SliverToBoxAdapter( | ||||
|                   child: LevelingProgressCard( | ||||
|                     level: data.profile.level, | ||||
|                     experience: data.profile.experience, | ||||
|                     progress: data.profile.levelingProgress, | ||||
|                   ).padding(horizontal: 20, bottom: 24), | ||||
|                 ), | ||||
|                 SliverToBoxAdapter( | ||||
|                   child: const Divider(height: 1).padding(bottom: 24), | ||||
|                 ), | ||||
|                 if (data.profile.bio.isNotEmpty) | ||||
|                   SliverToBoxAdapter( | ||||
|                     child: Column( | ||||
|                       crossAxisAlignment: CrossAxisAlignment.stretch, | ||||
|                       children: [ | ||||
|                         Text('bio').tr().bold(), | ||||
|                         Text(data.profile.bio), | ||||
|                       ], | ||||
|                     ).padding(horizontal: 24), | ||||
|                   ), | ||||
|                 SliverToBoxAdapter( | ||||
|                   child: Column( | ||||
|                     spacing: 12, | ||||
|                     children: [ | ||||
|                       LevelingProgressCard( | ||||
|                         level: data.profile.level, | ||||
|                         experience: data.profile.experience, | ||||
|                         progress: data.profile.levelingProgress, | ||||
|                       ), | ||||
|                       if (data.profile.verification != null) | ||||
|                         VerificationStatusCard( | ||||
|                           mark: data.profile.verification!, | ||||
|                         ), | ||||
|                     ], | ||||
|                   ).padding(horizontal: 20), | ||||
|                 ), | ||||
|  | ||||
|                 SliverToBoxAdapter( | ||||
|                   child: const Divider(height: 1).padding(vertical: 24), | ||||
|                 ), | ||||
|                 SliverToBoxAdapter( | ||||
|                   child: Column( | ||||
|                     crossAxisAlignment: CrossAxisAlignment.stretch, | ||||
|                     spacing: 24, | ||||
|                     children: [ | ||||
|                       if (buildSubcolumn(data).isNotEmpty) | ||||
|                         Column( | ||||
|                           crossAxisAlignment: CrossAxisAlignment.start, | ||||
|                           spacing: 2, | ||||
|                           children: buildSubcolumn(data), | ||||
|                         ), | ||||
|                       Column( | ||||
|                         crossAxisAlignment: CrossAxisAlignment.start, | ||||
|                         children: [ | ||||
|                           Text('bio').tr().bold(), | ||||
|                           Text( | ||||
|                             data.profile.bio.isEmpty | ||||
|                                 ? 'descriptionNone'.tr() | ||||
|                                 : data.profile.bio, | ||||
|                           ), | ||||
|                         ], | ||||
|                       ), | ||||
|                       if (data.profile.timeZone.isNotEmpty) | ||||
|                         Column( | ||||
|                           crossAxisAlignment: CrossAxisAlignment.start, | ||||
|                           children: [ | ||||
|                             Text('timeZone').tr().bold(), | ||||
|                             Row( | ||||
|                               crossAxisAlignment: CrossAxisAlignment.baseline, | ||||
|                               textBaseline: TextBaseline.alphabetic, | ||||
|                               spacing: 6, | ||||
|                               children: [ | ||||
|                                 Text(data.profile.timeZone), | ||||
|                                 Text( | ||||
|                                   getTzInfo( | ||||
|                                     data.profile.timeZone, | ||||
|                                   ).$2.formatCustomGlobal('HH:mm'), | ||||
|                                 ), | ||||
|                                 Text( | ||||
|                                   getTzInfo( | ||||
|                                     data.profile.timeZone, | ||||
|                                   ).$1.formatOffsetLocal(), | ||||
|                                 ).fontSize(11), | ||||
|                                 Text( | ||||
|                                   'UTC${getTzInfo(data.profile.timeZone).$1.formatOffset()}', | ||||
|                                 ).fontSize(11).opacity(0.75), | ||||
|                               ], | ||||
|                             ), | ||||
|                           ], | ||||
|                         ), | ||||
|                     ], | ||||
|                   ).padding(horizontal: 24), | ||||
|                 ), | ||||
|  | ||||
|                 SliverToBoxAdapter( | ||||
|                   child: const Divider(height: 1).padding(top: 24, bottom: 12), | ||||
|                 ), | ||||
|                 SliverToBoxAdapter( | ||||
|                   child: Row( | ||||
|                     spacing: 8, | ||||
|                     children: [ | ||||
|                       Expanded( | ||||
|                         child: FilledButton.icon( | ||||
|                           style: ButtonStyle( | ||||
|                             backgroundColor: WidgetStatePropertyAll( | ||||
|                               accountRelationship.value == null | ||||
|                                   ? null | ||||
|                                   : Theme.of(context).colorScheme.secondary, | ||||
|                             ), | ||||
|                             foregroundColor: WidgetStatePropertyAll( | ||||
|                               accountRelationship.value == null | ||||
|                                   ? null | ||||
|                                   : Theme.of(context).colorScheme.onSecondary, | ||||
|                             ), | ||||
|                           ), | ||||
|                           onPressed: relationshipAction, | ||||
|                           label: | ||||
|                               Text( | ||||
|                                 accountRelationship.value == null | ||||
|                                     ? 'addFriendShort' | ||||
|                                     : 'added', | ||||
|                               ).tr(), | ||||
|                           icon: | ||||
|                               accountRelationship.value == null | ||||
|                                   ? const Icon(Symbols.person_add) | ||||
|                                   : const Icon(Symbols.person_check), | ||||
|                         ), | ||||
|                       ), | ||||
|                       Expanded( | ||||
|                         child: FilledButton.icon( | ||||
|                           onPressed: directMessageAction, | ||||
|                           icon: const Icon(Symbols.message), | ||||
|                           label: | ||||
|                               Text( | ||||
|                                 accountChat.value == null | ||||
|                                     ? 'createDirectMessage' | ||||
|                                     : 'gotoDirectMessage', | ||||
|                                 maxLines: 1, | ||||
|                               ).tr(), | ||||
|                         ), | ||||
|                       ), | ||||
|                     ], | ||||
|                   ).padding(horizontal: 16), | ||||
|                 ), | ||||
|                 SliverToBoxAdapter( | ||||
|                   child: const Divider(height: 1).padding(top: 12), | ||||
|                 ), | ||||
|                 SliverToBoxAdapter( | ||||
|                   child: Column( | ||||
|                     children: [ | ||||
|                       FortuneGraphWidget( | ||||
|                         events: accountEvents, | ||||
|                         eventCalanderUser: data.name, | ||||
|                       ), | ||||
|                     ], | ||||
|                   ).padding(all: 8), | ||||
|                 ), | ||||
|               ], | ||||
|             ), | ||||
|           ), | ||||
|   | ||||
| @@ -6,7 +6,7 @@ part of 'profile.dart'; | ||||
| // RiverpodGenerator | ||||
| // ************************************************************************** | ||||
|  | ||||
| String _$accountHash() => r'39003ef3250181b9290e0562329c7801d4841941'; | ||||
| String _$accountHash() => r'd2b0579617e6264452d98f47f695a9cdf45b24ec'; | ||||
|  | ||||
| /// Copied from Dart SDK | ||||
| class _SystemHash { | ||||
| @@ -267,5 +267,377 @@ class _AccountBadgesProviderElement | ||||
|   String get uname => (origin as AccountBadgesProvider).uname; | ||||
| } | ||||
|  | ||||
| String _$accountAppbarForcegroundColorHash() => | ||||
|     r'f654a7a5594eda1500906e9ad023c22772257a9b'; | ||||
|  | ||||
| /// See also [accountAppbarForcegroundColor]. | ||||
| @ProviderFor(accountAppbarForcegroundColor) | ||||
| const accountAppbarForcegroundColorProvider = | ||||
|     AccountAppbarForcegroundColorFamily(); | ||||
|  | ||||
| /// See also [accountAppbarForcegroundColor]. | ||||
| class AccountAppbarForcegroundColorFamily extends Family<AsyncValue<Color?>> { | ||||
|   /// See also [accountAppbarForcegroundColor]. | ||||
|   const AccountAppbarForcegroundColorFamily(); | ||||
|  | ||||
|   /// See also [accountAppbarForcegroundColor]. | ||||
|   AccountAppbarForcegroundColorProvider call(String uname) { | ||||
|     return AccountAppbarForcegroundColorProvider(uname); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   AccountAppbarForcegroundColorProvider getProviderOverride( | ||||
|     covariant AccountAppbarForcegroundColorProvider provider, | ||||
|   ) { | ||||
|     return call(provider.uname); | ||||
|   } | ||||
|  | ||||
|   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'accountAppbarForcegroundColorProvider'; | ||||
| } | ||||
|  | ||||
| /// See also [accountAppbarForcegroundColor]. | ||||
| class AccountAppbarForcegroundColorProvider | ||||
|     extends AutoDisposeFutureProvider<Color?> { | ||||
|   /// See also [accountAppbarForcegroundColor]. | ||||
|   AccountAppbarForcegroundColorProvider(String uname) | ||||
|     : this._internal( | ||||
|         (ref) => accountAppbarForcegroundColor( | ||||
|           ref as AccountAppbarForcegroundColorRef, | ||||
|           uname, | ||||
|         ), | ||||
|         from: accountAppbarForcegroundColorProvider, | ||||
|         name: r'accountAppbarForcegroundColorProvider', | ||||
|         debugGetCreateSourceHash: | ||||
|             const bool.fromEnvironment('dart.vm.product') | ||||
|                 ? null | ||||
|                 : _$accountAppbarForcegroundColorHash, | ||||
|         dependencies: AccountAppbarForcegroundColorFamily._dependencies, | ||||
|         allTransitiveDependencies: | ||||
|             AccountAppbarForcegroundColorFamily._allTransitiveDependencies, | ||||
|         uname: uname, | ||||
|       ); | ||||
|  | ||||
|   AccountAppbarForcegroundColorProvider._internal( | ||||
|     super._createNotifier, { | ||||
|     required super.name, | ||||
|     required super.dependencies, | ||||
|     required super.allTransitiveDependencies, | ||||
|     required super.debugGetCreateSourceHash, | ||||
|     required super.from, | ||||
|     required this.uname, | ||||
|   }) : super.internal(); | ||||
|  | ||||
|   final String uname; | ||||
|  | ||||
|   @override | ||||
|   Override overrideWith( | ||||
|     FutureOr<Color?> Function(AccountAppbarForcegroundColorRef provider) create, | ||||
|   ) { | ||||
|     return ProviderOverride( | ||||
|       origin: this, | ||||
|       override: AccountAppbarForcegroundColorProvider._internal( | ||||
|         (ref) => create(ref as AccountAppbarForcegroundColorRef), | ||||
|         from: from, | ||||
|         name: null, | ||||
|         dependencies: null, | ||||
|         allTransitiveDependencies: null, | ||||
|         debugGetCreateSourceHash: null, | ||||
|         uname: uname, | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   AutoDisposeFutureProviderElement<Color?> createElement() { | ||||
|     return _AccountAppbarForcegroundColorProviderElement(this); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   bool operator ==(Object other) { | ||||
|     return other is AccountAppbarForcegroundColorProvider && | ||||
|         other.uname == uname; | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   int get hashCode { | ||||
|     var hash = _SystemHash.combine(0, runtimeType.hashCode); | ||||
|     hash = _SystemHash.combine(hash, uname.hashCode); | ||||
|  | ||||
|     return _SystemHash.finish(hash); | ||||
|   } | ||||
| } | ||||
|  | ||||
| @Deprecated('Will be removed in 3.0. Use Ref instead') | ||||
| // ignore: unused_element | ||||
| mixin AccountAppbarForcegroundColorRef on AutoDisposeFutureProviderRef<Color?> { | ||||
|   /// The parameter `uname` of this provider. | ||||
|   String get uname; | ||||
| } | ||||
|  | ||||
| class _AccountAppbarForcegroundColorProviderElement | ||||
|     extends AutoDisposeFutureProviderElement<Color?> | ||||
|     with AccountAppbarForcegroundColorRef { | ||||
|   _AccountAppbarForcegroundColorProviderElement(super.provider); | ||||
|  | ||||
|   @override | ||||
|   String get uname => (origin as AccountAppbarForcegroundColorProvider).uname; | ||||
| } | ||||
|  | ||||
| String _$accountDirectChatHash() => r'60d0015fc2a3c8fc2190bb41d6818cf3027d9d0a'; | ||||
|  | ||||
| /// See also [accountDirectChat]. | ||||
| @ProviderFor(accountDirectChat) | ||||
| const accountDirectChatProvider = AccountDirectChatFamily(); | ||||
|  | ||||
| /// See also [accountDirectChat]. | ||||
| class AccountDirectChatFamily extends Family<AsyncValue<SnChatRoom?>> { | ||||
|   /// See also [accountDirectChat]. | ||||
|   const AccountDirectChatFamily(); | ||||
|  | ||||
|   /// See also [accountDirectChat]. | ||||
|   AccountDirectChatProvider call(String uname) { | ||||
|     return AccountDirectChatProvider(uname); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   AccountDirectChatProvider getProviderOverride( | ||||
|     covariant AccountDirectChatProvider provider, | ||||
|   ) { | ||||
|     return call(provider.uname); | ||||
|   } | ||||
|  | ||||
|   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'accountDirectChatProvider'; | ||||
| } | ||||
|  | ||||
| /// See also [accountDirectChat]. | ||||
| class AccountDirectChatProvider extends AutoDisposeFutureProvider<SnChatRoom?> { | ||||
|   /// See also [accountDirectChat]. | ||||
|   AccountDirectChatProvider(String uname) | ||||
|     : this._internal( | ||||
|         (ref) => accountDirectChat(ref as AccountDirectChatRef, uname), | ||||
|         from: accountDirectChatProvider, | ||||
|         name: r'accountDirectChatProvider', | ||||
|         debugGetCreateSourceHash: | ||||
|             const bool.fromEnvironment('dart.vm.product') | ||||
|                 ? null | ||||
|                 : _$accountDirectChatHash, | ||||
|         dependencies: AccountDirectChatFamily._dependencies, | ||||
|         allTransitiveDependencies: | ||||
|             AccountDirectChatFamily._allTransitiveDependencies, | ||||
|         uname: uname, | ||||
|       ); | ||||
|  | ||||
|   AccountDirectChatProvider._internal( | ||||
|     super._createNotifier, { | ||||
|     required super.name, | ||||
|     required super.dependencies, | ||||
|     required super.allTransitiveDependencies, | ||||
|     required super.debugGetCreateSourceHash, | ||||
|     required super.from, | ||||
|     required this.uname, | ||||
|   }) : super.internal(); | ||||
|  | ||||
|   final String uname; | ||||
|  | ||||
|   @override | ||||
|   Override overrideWith( | ||||
|     FutureOr<SnChatRoom?> Function(AccountDirectChatRef provider) create, | ||||
|   ) { | ||||
|     return ProviderOverride( | ||||
|       origin: this, | ||||
|       override: AccountDirectChatProvider._internal( | ||||
|         (ref) => create(ref as AccountDirectChatRef), | ||||
|         from: from, | ||||
|         name: null, | ||||
|         dependencies: null, | ||||
|         allTransitiveDependencies: null, | ||||
|         debugGetCreateSourceHash: null, | ||||
|         uname: uname, | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   AutoDisposeFutureProviderElement<SnChatRoom?> createElement() { | ||||
|     return _AccountDirectChatProviderElement(this); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   bool operator ==(Object other) { | ||||
|     return other is AccountDirectChatProvider && other.uname == uname; | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   int get hashCode { | ||||
|     var hash = _SystemHash.combine(0, runtimeType.hashCode); | ||||
|     hash = _SystemHash.combine(hash, uname.hashCode); | ||||
|  | ||||
|     return _SystemHash.finish(hash); | ||||
|   } | ||||
| } | ||||
|  | ||||
| @Deprecated('Will be removed in 3.0. Use Ref instead') | ||||
| // ignore: unused_element | ||||
| mixin AccountDirectChatRef on AutoDisposeFutureProviderRef<SnChatRoom?> { | ||||
|   /// The parameter `uname` of this provider. | ||||
|   String get uname; | ||||
| } | ||||
|  | ||||
| class _AccountDirectChatProviderElement | ||||
|     extends AutoDisposeFutureProviderElement<SnChatRoom?> | ||||
|     with AccountDirectChatRef { | ||||
|   _AccountDirectChatProviderElement(super.provider); | ||||
|  | ||||
|   @override | ||||
|   String get uname => (origin as AccountDirectChatProvider).uname; | ||||
| } | ||||
|  | ||||
| String _$accountRelationshipHash() => | ||||
|     r'cb7d0d3f8cd4f23ad9d2d529872c540dac483d4f'; | ||||
|  | ||||
| /// See also [accountRelationship]. | ||||
| @ProviderFor(accountRelationship) | ||||
| const accountRelationshipProvider = AccountRelationshipFamily(); | ||||
|  | ||||
| /// See also [accountRelationship]. | ||||
| class AccountRelationshipFamily extends Family<AsyncValue<SnRelationship?>> { | ||||
|   /// See also [accountRelationship]. | ||||
|   const AccountRelationshipFamily(); | ||||
|  | ||||
|   /// See also [accountRelationship]. | ||||
|   AccountRelationshipProvider call(String uname) { | ||||
|     return AccountRelationshipProvider(uname); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   AccountRelationshipProvider getProviderOverride( | ||||
|     covariant AccountRelationshipProvider provider, | ||||
|   ) { | ||||
|     return call(provider.uname); | ||||
|   } | ||||
|  | ||||
|   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'accountRelationshipProvider'; | ||||
| } | ||||
|  | ||||
| /// See also [accountRelationship]. | ||||
| class AccountRelationshipProvider | ||||
|     extends AutoDisposeFutureProvider<SnRelationship?> { | ||||
|   /// See also [accountRelationship]. | ||||
|   AccountRelationshipProvider(String uname) | ||||
|     : this._internal( | ||||
|         (ref) => accountRelationship(ref as AccountRelationshipRef, uname), | ||||
|         from: accountRelationshipProvider, | ||||
|         name: r'accountRelationshipProvider', | ||||
|         debugGetCreateSourceHash: | ||||
|             const bool.fromEnvironment('dart.vm.product') | ||||
|                 ? null | ||||
|                 : _$accountRelationshipHash, | ||||
|         dependencies: AccountRelationshipFamily._dependencies, | ||||
|         allTransitiveDependencies: | ||||
|             AccountRelationshipFamily._allTransitiveDependencies, | ||||
|         uname: uname, | ||||
|       ); | ||||
|  | ||||
|   AccountRelationshipProvider._internal( | ||||
|     super._createNotifier, { | ||||
|     required super.name, | ||||
|     required super.dependencies, | ||||
|     required super.allTransitiveDependencies, | ||||
|     required super.debugGetCreateSourceHash, | ||||
|     required super.from, | ||||
|     required this.uname, | ||||
|   }) : super.internal(); | ||||
|  | ||||
|   final String uname; | ||||
|  | ||||
|   @override | ||||
|   Override overrideWith( | ||||
|     FutureOr<SnRelationship?> Function(AccountRelationshipRef provider) create, | ||||
|   ) { | ||||
|     return ProviderOverride( | ||||
|       origin: this, | ||||
|       override: AccountRelationshipProvider._internal( | ||||
|         (ref) => create(ref as AccountRelationshipRef), | ||||
|         from: from, | ||||
|         name: null, | ||||
|         dependencies: null, | ||||
|         allTransitiveDependencies: null, | ||||
|         debugGetCreateSourceHash: null, | ||||
|         uname: uname, | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   AutoDisposeFutureProviderElement<SnRelationship?> createElement() { | ||||
|     return _AccountRelationshipProviderElement(this); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   bool operator ==(Object other) { | ||||
|     return other is AccountRelationshipProvider && other.uname == uname; | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   int get hashCode { | ||||
|     var hash = _SystemHash.combine(0, runtimeType.hashCode); | ||||
|     hash = _SystemHash.combine(hash, uname.hashCode); | ||||
|  | ||||
|     return _SystemHash.finish(hash); | ||||
|   } | ||||
| } | ||||
|  | ||||
| @Deprecated('Will be removed in 3.0. Use Ref instead') | ||||
| // ignore: unused_element | ||||
| mixin AccountRelationshipRef on AutoDisposeFutureProviderRef<SnRelationship?> { | ||||
|   /// The parameter `uname` of this provider. | ||||
|   String get uname; | ||||
| } | ||||
|  | ||||
| class _AccountRelationshipProviderElement | ||||
|     extends AutoDisposeFutureProviderElement<SnRelationship?> | ||||
|     with AccountRelationshipRef { | ||||
|   _AccountRelationshipProviderElement(super.provider); | ||||
|  | ||||
|   @override | ||||
|   String get uname => (origin as AccountRelationshipProvider).uname; | ||||
| } | ||||
|  | ||||
| // 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 | ||||
|   | ||||
| @@ -96,12 +96,11 @@ class RelationshipListTile extends StatelessWidget { | ||||
|         relationship.status == 0 && relationship.relatedId == currentUserId; | ||||
|     final isWaiting = | ||||
|         relationship.status == 0 && relationship.accountId == currentUserId; | ||||
|     final isEstablished = | ||||
|         relationship.status >= 100 || relationship.status <= -100; | ||||
|     final isEstablished = relationship.status == 1 || relationship.status == 2; | ||||
|  | ||||
|     return ListTile( | ||||
|       contentPadding: const EdgeInsets.only(left: 16, right: 12), | ||||
|       leading: ProfilePictureWidget(fileId: account.profile.pictureId), | ||||
|       leading: ProfilePictureWidget(fileId: account.profile.picture?.id), | ||||
|       title: Row( | ||||
|         spacing: 6, | ||||
|         children: [ | ||||
|   | ||||
| @@ -75,6 +75,7 @@ class CreateAccountScreen extends HookConsumerWidget { | ||||
|     } | ||||
|  | ||||
|     return AppScaffold( | ||||
|       noBackground: false, | ||||
|       appBar: AppBar( | ||||
|         leading: const PageBackButton(), | ||||
|         title: Text('createAccount').tr(), | ||||
|   | ||||
| @@ -1,9 +1,16 @@ | ||||
| import 'dart:convert'; | ||||
| import 'dart:io'; | ||||
| import 'dart:math' as math; | ||||
|  | ||||
| import 'package:animations/animations.dart'; | ||||
| import 'package:auto_route/auto_route.dart'; | ||||
| import 'package:device_info_plus/device_info_plus.dart'; | ||||
| import 'package:dio/dio.dart'; | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:flutter/foundation.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter_hooks/flutter_hooks.dart'; | ||||
| import 'package:flutter_otp_text_field/flutter_otp_text_field.dart'; | ||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||
| import 'package:gap/gap.dart'; | ||||
| import 'package:island/models/auth.dart'; | ||||
| @@ -11,23 +18,28 @@ import 'package:island/pods/config.dart'; | ||||
| import 'package:island/pods/network.dart'; | ||||
| import 'package:island/pods/userinfo.dart'; | ||||
| import 'package:island/pods/websocket.dart'; | ||||
| import 'package:island/screens/account/me/settings_connections.dart'; | ||||
| import 'package:island/screens/auth/oidc.dart'; | ||||
| import 'package:island/services/notify.dart'; | ||||
| import 'package:island/services/udid.dart'; | ||||
| import 'package:island/widgets/alert.dart'; | ||||
| import 'package:island/widgets/app_scaffold.dart'; | ||||
| import 'package:material_symbols_icons/symbols.dart'; | ||||
| import 'package:sign_in_with_apple/sign_in_with_apple.dart'; | ||||
| import 'package:styled_widget/styled_widget.dart'; | ||||
| import 'package:url_launcher/url_launcher_string.dart'; | ||||
|  | ||||
| import 'captcha.dart'; | ||||
|  | ||||
| final Map<int, (String, String, IconData)> kFactorTypes = { | ||||
|   0: ('authFactorPassword', 'authFactorPasswordDescription', Symbols.password), | ||||
|   1: ('authFactorEmail', 'authFactorEmailDescription', Symbols.email), | ||||
|   2: ('authFactorTOTP', 'authFactorTOTPDescription', Symbols.timer), | ||||
|   3: ( | ||||
|   2: ( | ||||
|     'authFactorInAppNotify', | ||||
|     'authFactorInAppNotifyDescription', | ||||
|     Symbols.notifications_active, | ||||
|   ), | ||||
|   3: ('authFactorTOTP', 'authFactorTOTPDescription', Symbols.timer), | ||||
| }; | ||||
|  | ||||
| @RoutePage() | ||||
| @@ -36,65 +48,102 @@ class LoginScreen extends HookConsumerWidget { | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context, WidgetRef ref) { | ||||
|     final isBusy = useState(false); | ||||
|  | ||||
|     final period = useState(0); | ||||
|     final currentTicket = useState<SnAuthChallenge?>(null); | ||||
|     final factors = useState<List<SnAuthFactor>>([]); | ||||
|     final factorPicked = useState<SnAuthFactor?>(null); | ||||
|  | ||||
|     return AppScaffold( | ||||
|       noBackground: false, | ||||
|       appBar: AppBar( | ||||
|         leading: const PageBackButton(), | ||||
|         title: Text('login').tr(), | ||||
|       ), | ||||
|       body: Theme( | ||||
|         data: Theme.of(context).copyWith(canvasColor: Colors.transparent), | ||||
|         child: | ||||
|             SingleChildScrollView( | ||||
|               child: PageTransitionSwitcher( | ||||
|                 transitionBuilder: ( | ||||
|                   Widget child, | ||||
|                   Animation<double> primaryAnimation, | ||||
|                   Animation<double> secondaryAnimation, | ||||
|                 ) { | ||||
|                   return SharedAxisTransition( | ||||
|                     animation: primaryAnimation, | ||||
|                     secondaryAnimation: secondaryAnimation, | ||||
|                     transitionType: SharedAxisTransitionType.horizontal, | ||||
|                     child: Container( | ||||
|                       constraints: BoxConstraints(maxWidth: 380), | ||||
|                       child: child, | ||||
|                     ), | ||||
|                   ); | ||||
|                 }, | ||||
|                 child: switch (period.value % 3) { | ||||
|                   1 => _LoginPickerScreen( | ||||
|                     key: const ValueKey(1), | ||||
|                     ticket: currentTicket.value, | ||||
|                     factors: factors.value, | ||||
|                     onChallenge: | ||||
|                         (SnAuthChallenge? p0) => currentTicket.value = p0, | ||||
|                     onPickFactor: (SnAuthFactor p0) => factorPicked.value = p0, | ||||
|                     onNext: () => period.value++, | ||||
|                   ), | ||||
|                   2 => _LoginCheckScreen( | ||||
|                     key: const ValueKey(2), | ||||
|                     challenge: currentTicket.value, | ||||
|                     factor: factorPicked.value, | ||||
|                     onChallenge: | ||||
|                         (SnAuthChallenge? p0) => currentTicket.value = p0, | ||||
|                     onNext: () => period.value++, | ||||
|                   ), | ||||
|                   _ => _LoginLookupScreen( | ||||
|                     key: const ValueKey(0), | ||||
|                     ticket: currentTicket.value, | ||||
|                     onChallenge: | ||||
|                         (SnAuthChallenge? p0) => currentTicket.value = p0, | ||||
|                     onFactor: | ||||
|                         (List<SnAuthFactor>? p0) => factors.value = p0 ?? [], | ||||
|                     onNext: () => period.value++, | ||||
|                   ), | ||||
|                 }, | ||||
|               ).padding(all: 24), | ||||
|             ).center(), | ||||
|         child: Column( | ||||
|           children: [ | ||||
|             if (isBusy.value) | ||||
|               LinearProgressIndicator( | ||||
|                 minHeight: 4, | ||||
|                 borderRadius: BorderRadius.zero, | ||||
|                 trackGap: 0, | ||||
|                 stopIndicatorRadius: 0, | ||||
|               ) | ||||
|             else if (currentTicket.value != null) | ||||
|               LinearProgressIndicator( | ||||
|                 minHeight: 4, | ||||
|                 borderRadius: BorderRadius.zero, | ||||
|                 trackGap: 0, | ||||
|                 stopIndicatorRadius: 0, | ||||
|                 value: | ||||
|                     1 - | ||||
|                     (currentTicket.value!.stepRemain / | ||||
|                         currentTicket.value!.stepTotal), | ||||
|               ) | ||||
|             else | ||||
|               const Gap(4), | ||||
|             Expanded( | ||||
|               child: | ||||
|                   SingleChildScrollView( | ||||
|                     child: PageTransitionSwitcher( | ||||
|                       transitionBuilder: ( | ||||
|                         Widget child, | ||||
|                         Animation<double> primaryAnimation, | ||||
|                         Animation<double> secondaryAnimation, | ||||
|                       ) { | ||||
|                         return SharedAxisTransition( | ||||
|                           animation: primaryAnimation, | ||||
|                           secondaryAnimation: secondaryAnimation, | ||||
|                           transitionType: SharedAxisTransitionType.horizontal, | ||||
|                           child: Container( | ||||
|                             constraints: BoxConstraints(maxWidth: 380), | ||||
|                             child: child, | ||||
|                           ), | ||||
|                         ); | ||||
|                       }, | ||||
|                       child: switch (period.value % 3) { | ||||
|                         1 => _LoginPickerScreen( | ||||
|                           key: const ValueKey(1), | ||||
|                           challenge: currentTicket.value, | ||||
|                           factors: factors.value, | ||||
|                           onChallenge: | ||||
|                               (SnAuthChallenge? p0) => currentTicket.value = p0, | ||||
|                           onPickFactor: | ||||
|                               (SnAuthFactor p0) => factorPicked.value = p0, | ||||
|                           onNext: () => period.value++, | ||||
|                           onBusy: (value) => isBusy.value = value, | ||||
|                         ), | ||||
|                         2 => _LoginCheckScreen( | ||||
|                           key: const ValueKey(2), | ||||
|                           challenge: currentTicket.value, | ||||
|                           factor: factorPicked.value, | ||||
|                           onChallenge: | ||||
|                               (SnAuthChallenge? p0) => currentTicket.value = p0, | ||||
|                           onNext: () => period.value = 1, | ||||
|                           onBusy: (value) => isBusy.value = value, | ||||
|                         ), | ||||
|                         _ => _LoginLookupScreen( | ||||
|                           key: const ValueKey(0), | ||||
|                           ticket: currentTicket.value, | ||||
|                           onChallenge: | ||||
|                               (SnAuthChallenge? p0) => currentTicket.value = p0, | ||||
|                           onFactor: | ||||
|                               (List<SnAuthFactor>? p0) => | ||||
|                                   factors.value = p0 ?? [], | ||||
|                           onNext: () => period.value++, | ||||
|                           onBusy: (value) => isBusy.value = value, | ||||
|                         ), | ||||
|                       }, | ||||
|                     ).padding(all: 24), | ||||
|                   ).center(), | ||||
|             ), | ||||
|  | ||||
|             const Gap(4), | ||||
|           ], | ||||
|         ), | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| @@ -104,7 +153,8 @@ class _LoginCheckScreen extends HookConsumerWidget { | ||||
|   final SnAuthChallenge? challenge; | ||||
|   final SnAuthFactor? factor; | ||||
|   final Function(SnAuthChallenge?) onChallenge; | ||||
|   final Function onNext; | ||||
|   final VoidCallback onNext; | ||||
|   final Function(bool) onBusy; | ||||
|  | ||||
|   const _LoginCheckScreen({ | ||||
|     super.key, | ||||
| @@ -112,6 +162,7 @@ class _LoginCheckScreen extends HookConsumerWidget { | ||||
|     required this.factor, | ||||
|     required this.onChallenge, | ||||
|     required this.onNext, | ||||
|     required this.onBusy, | ||||
|   }); | ||||
|  | ||||
|   @override | ||||
| @@ -119,11 +170,100 @@ class _LoginCheckScreen extends HookConsumerWidget { | ||||
|     final isBusy = useState(false); | ||||
|     final passwordController = useTextEditingController(); | ||||
|  | ||||
|     useEffect(() { | ||||
|       onBusy.call(isBusy.value); | ||||
|       return null; | ||||
|     }, [isBusy]); | ||||
|  | ||||
|     Future<void> getToken({String? code}) async { | ||||
|       // Get token if challenge is completed | ||||
|       final client = ref.watch(apiClientProvider); | ||||
|       final tokenResp = await client.post( | ||||
|         '/auth/token', | ||||
|         data: { | ||||
|           'grant_type': 'authorization_code', | ||||
|           'code': code ?? challenge!.id, | ||||
|         }, | ||||
|       ); | ||||
|       final token = tokenResp.data['token']; | ||||
|       setToken(ref.watch(sharedPreferencesProvider), token); | ||||
|       ref.invalidate(tokenProvider); | ||||
|       if (!context.mounted) return; | ||||
|  | ||||
|       // Do post login tasks | ||||
|       final userNotifier = ref.read(userInfoProvider.notifier); | ||||
|       userNotifier.fetchUser().then((_) { | ||||
|         final apiClient = ref.read(apiClientProvider); | ||||
|         subscribePushNotification(apiClient); | ||||
|         final wsNotifier = ref.read(websocketStateProvider.notifier); | ||||
|         wsNotifier.connect(); | ||||
|         if (context.mounted) Navigator.pop(context, true); | ||||
|       }); | ||||
|  | ||||
|       // Update the sessions' device name is available | ||||
|       if (!kIsWeb) { | ||||
|         String? name; | ||||
|         if (Platform.isIOS) { | ||||
|           final deviceInfo = await DeviceInfoPlugin().iosInfo; | ||||
|           name = deviceInfo.name; | ||||
|         } else if (Platform.isAndroid) { | ||||
|           final deviceInfo = await DeviceInfoPlugin().androidInfo; | ||||
|           name = deviceInfo.name; | ||||
|         } else if (Platform.isWindows) { | ||||
|           final deviceInfo = await DeviceInfoPlugin().windowsInfo; | ||||
|           name = deviceInfo.computerName; | ||||
|         } | ||||
|         if (name != null) { | ||||
|           final client = ref.watch(apiClientProvider); | ||||
|           await client.patch( | ||||
|             '/accounts/me/sessions/current/label', | ||||
|             data: jsonEncode(name), | ||||
|           ); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     useEffect(() { | ||||
|       if (challenge != null && challenge?.stepRemain == 0) { | ||||
|         Future(() { | ||||
|           isBusy.value = true; | ||||
|           getToken().catchError((err) { | ||||
|             showErrorAlert(err); | ||||
|             isBusy.value = false; | ||||
|           }); | ||||
|         }); | ||||
|       } | ||||
|       return null; | ||||
|     }, [challenge]); | ||||
|  | ||||
|     if (factor == null) { | ||||
|       // Logging in by third parties | ||||
|       return Column( | ||||
|         crossAxisAlignment: CrossAxisAlignment.start, | ||||
|         children: [ | ||||
|           Align( | ||||
|             alignment: Alignment.centerLeft, | ||||
|             child: CircleAvatar( | ||||
|               radius: 26, | ||||
|               child: const Icon(Symbols.asterisk, size: 28), | ||||
|             ).padding(bottom: 8), | ||||
|           ), | ||||
|           Text( | ||||
|             'loginInProgress'.tr(), | ||||
|             style: const TextStyle(fontSize: 28, fontWeight: FontWeight.w900), | ||||
|           ).padding(left: 4, bottom: 16), | ||||
|           const Gap(16), | ||||
|           CircularProgressIndicator().alignment(Alignment.centerLeft), | ||||
|         ], | ||||
|       ); | ||||
|     } | ||||
|  | ||||
|     Future<void> performCheckTicket() async { | ||||
|       final pwd = passwordController.value.text; | ||||
|       if (pwd.isEmpty) return; | ||||
|       isBusy.value = true; | ||||
|       try { | ||||
|         // Pass challenge | ||||
|         final client = ref.watch(apiClientProvider); | ||||
|         final resp = await client.patch( | ||||
|           '/auth/challenge/${challenge!.id}', | ||||
| @@ -135,23 +275,8 @@ class _LoginCheckScreen extends HookConsumerWidget { | ||||
|           onNext(); | ||||
|           return; | ||||
|         } | ||||
|         final tokenResp = await client.post( | ||||
|           '/auth/token', | ||||
|           data: {'grant_type': 'authorization_code', 'code': result.id}, | ||||
|         ); | ||||
|         final atk = tokenResp.data['access_token']; | ||||
|         final rtk = tokenResp.data['refresh_token']; | ||||
|         setTokenPair(ref.watch(sharedPreferencesProvider), atk, rtk); | ||||
|         ref.invalidate(tokenPairProvider); | ||||
|         if (!context.mounted) return; | ||||
|         final userNotifier = ref.read(userInfoProvider.notifier); | ||||
|         userNotifier.fetchUser().then((_) { | ||||
|           final apiClient = ref.read(apiClientProvider); | ||||
|           subscribePushNotification(apiClient); | ||||
|           final wsNotifier = ref.read(websocketStateProvider.notifier); | ||||
|           wsNotifier.connect(); | ||||
|           if (context.mounted) Navigator.pop(context, true); | ||||
|         }); | ||||
|  | ||||
|         await getToken(code: result.id); | ||||
|       } catch (err) { | ||||
|         showErrorAlert(err); | ||||
|         return; | ||||
| @@ -160,6 +285,8 @@ class _LoginCheckScreen extends HookConsumerWidget { | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     final width = math.min(380, MediaQuery.of(context).size.width); | ||||
|  | ||||
|     return Column( | ||||
|       crossAxisAlignment: CrossAxisAlignment.start, | ||||
|       children: [ | ||||
| @@ -174,24 +301,46 @@ class _LoginCheckScreen extends HookConsumerWidget { | ||||
|           'loginEnterPassword'.tr(), | ||||
|           style: const TextStyle(fontSize: 28, fontWeight: FontWeight.w900), | ||||
|         ).padding(left: 4, bottom: 16), | ||||
|         TextField( | ||||
|           autocorrect: false, | ||||
|           enableSuggestions: false, | ||||
|           controller: passwordController, | ||||
|           obscureText: true, | ||||
|           autofillHints: [ | ||||
|             factor!.type == 0 | ||||
|                 ? AutofillHints.password | ||||
|                 : AutofillHints.oneTimeCode, | ||||
|           ], | ||||
|           decoration: InputDecoration( | ||||
|             isDense: true, | ||||
|             border: const UnderlineInputBorder(), | ||||
|             labelText: 'password'.tr(), | ||||
|         if ([0].contains(factor!.type)) | ||||
|           TextField( | ||||
|             autocorrect: false, | ||||
|             enableSuggestions: false, | ||||
|             controller: passwordController, | ||||
|             obscureText: true, | ||||
|             autofillHints: [ | ||||
|               factor!.type == 0 | ||||
|                   ? AutofillHints.password | ||||
|                   : AutofillHints.oneTimeCode, | ||||
|             ], | ||||
|             decoration: InputDecoration( | ||||
|               isDense: true, | ||||
|               labelText: 'password'.tr(), | ||||
|             ), | ||||
|             onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), | ||||
|             onSubmitted: isBusy.value ? null : (_) => performCheckTicket(), | ||||
|           ).padding(horizontal: 7) | ||||
|         else | ||||
|           OtpTextField( | ||||
|             showCursor: false, | ||||
|             numberOfFields: 6, | ||||
|             obscureText: false, | ||||
|             showFieldAsBox: true, | ||||
|             focusedBorderColor: Theme.of(context).colorScheme.primary, | ||||
|             fieldWidth: (width / 6) - 10, | ||||
|             onSubmit: (value) { | ||||
|               passwordController.text = value; | ||||
|               performCheckTicket(); | ||||
|             }, | ||||
|             textStyle: Theme.of(context).textTheme.titleLarge!, | ||||
|           ), | ||||
|           onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), | ||||
|           onSubmitted: isBusy.value ? null : (_) => performCheckTicket(), | ||||
|         ).padding(horizontal: 7), | ||||
|         const Gap(12), | ||||
|         ListTile( | ||||
|           leading: Icon( | ||||
|             kFactorTypes[factor!.type]?.$3 ?? Symbols.question_mark, | ||||
|           ), | ||||
|           title: Text(kFactorTypes[factor!.type]?.$1 ?? 'unknown').tr(), | ||||
|           subtitle: Text(kFactorTypes[factor!.type]?.$2 ?? 'unknown').tr(), | ||||
|         ), | ||||
|         const Gap(12), | ||||
|         Row( | ||||
|           mainAxisAlignment: MainAxisAlignment.end, | ||||
| @@ -214,30 +363,48 @@ class _LoginCheckScreen extends HookConsumerWidget { | ||||
| } | ||||
|  | ||||
| class _LoginPickerScreen extends HookConsumerWidget { | ||||
|   final SnAuthChallenge? ticket; | ||||
|   final SnAuthChallenge? challenge; | ||||
|   final List<SnAuthFactor>? factors; | ||||
|   final Function(SnAuthChallenge?) onChallenge; | ||||
|   final Function(SnAuthFactor) onPickFactor; | ||||
|   final Function onNext; | ||||
|   final VoidCallback onNext; | ||||
|   final Function(bool) onBusy; | ||||
|  | ||||
|   const _LoginPickerScreen({ | ||||
|     super.key, | ||||
|     required this.ticket, | ||||
|     required this.challenge, | ||||
|     required this.factors, | ||||
|     required this.onChallenge, | ||||
|     required this.onPickFactor, | ||||
|     required this.onNext, | ||||
|     required this.onBusy, | ||||
|   }); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context, WidgetRef ref) { | ||||
|     final isBusy = useState(false); | ||||
|     final factorPicked = useState<String?>(null); | ||||
|     final factorPicked = useState<SnAuthFactor?>(null); | ||||
|  | ||||
|     useEffect(() { | ||||
|       onBusy.call(isBusy.value); | ||||
|       return null; | ||||
|     }, [isBusy]); | ||||
|  | ||||
|     useEffect(() { | ||||
|       if (challenge != null && challenge?.stepRemain == 0) { | ||||
|         Future(() { | ||||
|           onNext(); | ||||
|         }); | ||||
|       } | ||||
|       return null; | ||||
|     }, [challenge]); | ||||
|  | ||||
|     final unfocusColor = Theme.of( | ||||
|       context, | ||||
|     ).colorScheme.onSurface.withAlpha((255 * 0.75).round()); | ||||
|  | ||||
|     final hintController = useTextEditingController(); | ||||
|  | ||||
|     void performGetFactorCode() async { | ||||
|       if (factorPicked.value == null) return; | ||||
|  | ||||
| @@ -245,13 +412,24 @@ class _LoginPickerScreen extends HookConsumerWidget { | ||||
|       final client = ref.watch(apiClientProvider); | ||||
|  | ||||
|       try { | ||||
|         // Request one-time-password code | ||||
|         await client.post( | ||||
|           '/auth/challenge/${ticket!.id}/factors/${factorPicked.value}', | ||||
|           '/auth/challenge/${challenge!.id}/factors/${factorPicked.value!.id}', | ||||
|           data: | ||||
|               hintController.text.isNotEmpty | ||||
|                   ? jsonEncode(hintController.text) | ||||
|                   : null, | ||||
|         ); | ||||
|         onPickFactor(factors!.where((x) => x.id == factorPicked.value).first); | ||||
|         onPickFactor(factors!.where((x) => x == factorPicked.value).first); | ||||
|         onNext(); | ||||
|       } catch (err) { | ||||
|         if (err is DioException && err.response?.statusCode == 400) { | ||||
|           onPickFactor(factors!.where((x) => x == factorPicked.value).first); | ||||
|           onNext(); | ||||
|           if (context.mounted) { | ||||
|             showSnackBar(context, err.response!.data.toString()); | ||||
|           } | ||||
|           return; | ||||
|         } | ||||
|         showErrorAlert(err); | ||||
|         return; | ||||
|       } finally { | ||||
| @@ -289,11 +467,11 @@ class _LoginPickerScreen extends HookConsumerWidget { | ||||
|                           kFactorTypes[x.type]?.$3 ?? Symbols.question_mark, | ||||
|                         ), | ||||
|                         title: Text(kFactorTypes[x.type]?.$1 ?? 'unknown').tr(), | ||||
|                         enabled: !ticket!.blacklistFactors.contains(x.id), | ||||
|                         value: factorPicked.value == x.id, | ||||
|                         enabled: !challenge!.blacklistFactors.contains(x.id), | ||||
|                         value: factorPicked.value == x, | ||||
|                         onChanged: (value) { | ||||
|                           if (value == true) { | ||||
|                             factorPicked.value = x.id; | ||||
|                             factorPicked.value = x; | ||||
|                           } | ||||
|                         }, | ||||
|                       ), | ||||
| @@ -302,9 +480,19 @@ class _LoginPickerScreen extends HookConsumerWidget { | ||||
|                 List.empty(), | ||||
|           ), | ||||
|         ), | ||||
|         if ([1].contains(factorPicked.value?.type)) | ||||
|           TextField( | ||||
|             controller: hintController, | ||||
|             decoration: InputDecoration( | ||||
|               isDense: true, | ||||
|               border: const OutlineInputBorder(), | ||||
|               labelText: 'authFactorHint'.tr(), | ||||
|               helperText: 'authFactorHintHelper'.tr(), | ||||
|             ), | ||||
|           ).padding(top: 12, bottom: 4, horizontal: 4), | ||||
|         const Gap(8), | ||||
|         Text( | ||||
|           'loginMultiFactor'.plural(ticket!.stepRemain), | ||||
|           'loginMultiFactor'.plural(challenge!.stepRemain), | ||||
|           style: TextStyle(color: unfocusColor, fontSize: 13), | ||||
|         ).padding(horizontal: 16), | ||||
|         const Gap(12), | ||||
| @@ -332,7 +520,8 @@ class _LoginLookupScreen extends HookConsumerWidget { | ||||
|   final SnAuthChallenge? ticket; | ||||
|   final Function(SnAuthChallenge?) onChallenge; | ||||
|   final Function(List<SnAuthFactor>?) onFactor; | ||||
|   final Function onNext; | ||||
|   final VoidCallback onNext; | ||||
|   final Function(bool) onBusy; | ||||
|  | ||||
|   const _LoginLookupScreen({ | ||||
|     super.key, | ||||
| @@ -340,6 +529,7 @@ class _LoginLookupScreen extends HookConsumerWidget { | ||||
|     required this.onChallenge, | ||||
|     required this.onFactor, | ||||
|     required this.onNext, | ||||
|     required this.onBusy, | ||||
|   }); | ||||
|  | ||||
|   @override | ||||
| @@ -347,21 +537,29 @@ class _LoginLookupScreen extends HookConsumerWidget { | ||||
|     final isBusy = useState(false); | ||||
|     final usernameController = useTextEditingController(); | ||||
|  | ||||
|     useEffect(() { | ||||
|       onBusy.call(isBusy.value); | ||||
|       return null; | ||||
|     }, [isBusy]); | ||||
|  | ||||
|     Future<void> requestResetPassword() async { | ||||
|       final uname = usernameController.value.text; | ||||
|       if (uname.isEmpty) { | ||||
|         showErrorAlert('loginResetPasswordHint'.tr()); | ||||
|         return; | ||||
|       } | ||||
|       final captchaTk = await Navigator.of( | ||||
|         context, | ||||
|       ).push(MaterialPageRoute(builder: (context) => CaptchaScreen())); | ||||
|       if (captchaTk == null) return; | ||||
|       isBusy.value = true; | ||||
|       try { | ||||
|         final client = ref.watch(apiClientProvider); | ||||
|         final lookupResp = await client.get('/users/lookup?probe=$uname'); | ||||
|         await client.post( | ||||
|           '/users/me/password-reset', | ||||
|           data: {'user_id': lookupResp.data['id']}, | ||||
|           '/accounts/recovery/password', | ||||
|           data: {'account': uname, 'captcha_token': captchaTk}, | ||||
|         ); | ||||
|         showInfoAlert('done'.tr(), 'signinResetPasswordSent'.tr()); | ||||
|         showInfoAlert('loginResetPasswordSent'.tr(), 'done'.tr()); | ||||
|       } catch (err) { | ||||
|         showErrorAlert(err); | ||||
|       } finally { | ||||
| @@ -412,6 +610,72 @@ class _LoginLookupScreen extends HookConsumerWidget { | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     Future<void> withApple() async { | ||||
|       final client = ref.watch(apiClientProvider); | ||||
|       try { | ||||
|         final credential = await SignInWithApple.getAppleIDCredential( | ||||
|           scopes: [AppleIDAuthorizationScopes.email], | ||||
|           webAuthenticationOptions: WebAuthenticationOptions( | ||||
|             clientId: 'dev.solsynth.solarpass', | ||||
|             redirectUri: Uri.parse('https://nt.solian.app/auth/callback/apple'), | ||||
|           ), | ||||
|         ); | ||||
|  | ||||
|         if (context.mounted) showLoadingModal(context); | ||||
|         final resp = await client.post( | ||||
|           '/auth/login/apple/mobile', | ||||
|           data: { | ||||
|             'identity_token': credential.identityToken!, | ||||
|             'authorization_code': credential.authorizationCode, | ||||
|             'device_id': await getUdid(), | ||||
|           }, | ||||
|         ); | ||||
|  | ||||
|         final challenge = SnAuthChallenge.fromJson(resp.data); | ||||
|         onChallenge(challenge); | ||||
|         final factorResp = await client.get( | ||||
|           '/auth/challenge/${challenge.id}/factors', | ||||
|         ); | ||||
|         onFactor( | ||||
|           List<SnAuthFactor>.from( | ||||
|             factorResp.data.map((ele) => SnAuthFactor.fromJson(ele)), | ||||
|           ), | ||||
|         ); | ||||
|         onNext(); | ||||
|       } catch (err) { | ||||
|         if (err is SignInWithAppleAuthorizationException) return; | ||||
|         showErrorAlert(err); | ||||
|       } finally { | ||||
|         if (context.mounted) hideLoadingModal(context); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     Future<void> withOidc(String provider) async { | ||||
|       final challengeId = await Navigator.of(context).push( | ||||
|         MaterialPageRoute( | ||||
|           builder: (context) => OidcScreen(provider: provider.toLowerCase()), | ||||
|         ), | ||||
|       ); | ||||
|  | ||||
|       final client = ref.watch(apiClientProvider); | ||||
|       try { | ||||
|         final resp = await client.get('/auth/challenge/$challengeId'); | ||||
|         final challenge = SnAuthChallenge.fromJson(resp.data); | ||||
|         onChallenge(challenge); | ||||
|         final factorResp = await client.get( | ||||
|           '/auth/challenge/${challenge.id}/factors', | ||||
|         ); | ||||
|         onFactor( | ||||
|           List<SnAuthFactor>.from( | ||||
|             factorResp.data.map((ele) => SnAuthFactor.fromJson(ele)), | ||||
|           ), | ||||
|         ); | ||||
|         onNext(); | ||||
|       } catch (err) { | ||||
|         showErrorAlert(err); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     return Column( | ||||
|       crossAxisAlignment: CrossAxisAlignment.start, | ||||
|       children: [ | ||||
| @@ -440,7 +704,45 @@ class _LoginLookupScreen extends HookConsumerWidget { | ||||
|           onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), | ||||
|           onSubmitted: isBusy.value ? null : (_) => performNewTicket(), | ||||
|         ).padding(horizontal: 7), | ||||
|         const Gap(12), | ||||
|         Row( | ||||
|           spacing: 6, | ||||
|           crossAxisAlignment: CrossAxisAlignment.center, | ||||
|           children: <Widget>[ | ||||
|             Text("loginOr").tr().fontSize(11).opacity(0.85), | ||||
|             const Gap(8), | ||||
|             Spacer(), | ||||
|             IconButton.filledTonal( | ||||
|               onPressed: () => withOidc('github'), | ||||
|               padding: EdgeInsets.zero, | ||||
|               icon: getProviderIcon( | ||||
|                 "github", | ||||
|                 size: 16, | ||||
|                 color: Theme.of(context).colorScheme.onPrimaryContainer, | ||||
|               ), | ||||
|               tooltip: 'GitHub', | ||||
|             ), | ||||
|             IconButton.filledTonal( | ||||
|               onPressed: () => withOidc('google'), | ||||
|               padding: EdgeInsets.zero, | ||||
|               icon: getProviderIcon( | ||||
|                 "google", | ||||
|                 size: 16, | ||||
|                 color: Theme.of(context).colorScheme.onPrimaryContainer, | ||||
|               ), | ||||
|               tooltip: 'Google', | ||||
|             ), | ||||
|             IconButton.filledTonal( | ||||
|               onPressed: withApple, | ||||
|               padding: EdgeInsets.zero, | ||||
|               icon: getProviderIcon( | ||||
|                 "apple", | ||||
|                 size: 16, | ||||
|                 color: Theme.of(context).colorScheme.onPrimaryContainer, | ||||
|               ), | ||||
|               tooltip: 'Apple Account', | ||||
|             ), | ||||
|           ], | ||||
|         ).padding(horizontal: 8, vertical: 8), | ||||
|         Row( | ||||
|           mainAxisAlignment: MainAxisAlignment.spaceBetween, | ||||
|           children: [ | ||||
|   | ||||
							
								
								
									
										1
									
								
								lib/screens/auth/oidc.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								lib/screens/auth/oidc.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| export 'oidc.native.dart' if (dart.library.html) 'oidc.web.dart'; | ||||
							
								
								
									
										225
									
								
								lib/screens/auth/oidc.native.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										225
									
								
								lib/screens/auth/oidc.native.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,225 @@ | ||||
| import 'dart:io'; | ||||
|  | ||||
| import 'package:flutter/foundation.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter/services.dart'; | ||||
| import 'package:flutter_inappwebview/flutter_inappwebview.dart'; | ||||
| import 'package:flutter_riverpod/flutter_riverpod.dart'; | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:gap/gap.dart'; | ||||
| import 'package:island/pods/config.dart'; | ||||
| import 'package:island/pods/network.dart'; | ||||
| import 'package:island/services/udid.dart'; | ||||
| import 'package:island/widgets/app_scaffold.dart'; | ||||
| import 'package:styled_widget/styled_widget.dart'; | ||||
|  | ||||
| class OidcScreen extends ConsumerStatefulWidget { | ||||
|   final String provider; | ||||
|   final String? title; | ||||
|  | ||||
|   const OidcScreen({super.key, required this.provider, this.title}); | ||||
|  | ||||
|   @override | ||||
|   ConsumerState<OidcScreen> createState() => _OidcScreenState(); | ||||
| } | ||||
|  | ||||
| class _OidcScreenState extends ConsumerState<OidcScreen> { | ||||
|   String? authToken; | ||||
|   String? currentUrl; | ||||
|   final TextEditingController _urlController = TextEditingController(); | ||||
|   bool _isLoading = true; | ||||
|   late Future<String> _deviceIdFuture; | ||||
|  | ||||
|   @override | ||||
|   void initState() { | ||||
|     super.initState(); | ||||
|     _deviceIdFuture = getUdid(); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   void dispose() { | ||||
|     _urlController.dispose(); | ||||
|     super.dispose(); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     final serverUrl = ref.watch(serverUrlProvider); | ||||
|     final token = ref.watch(tokenProvider); | ||||
|  | ||||
|     return AppScaffold( | ||||
|       appBar: AppBar( | ||||
|         title: widget.title != null ? Text(widget.title!) : Text('login').tr(), | ||||
|       ), | ||||
|       body: FutureBuilder<String>( | ||||
|         future: _deviceIdFuture, | ||||
|         builder: (context, snapshot) { | ||||
|           if (snapshot.connectionState == ConnectionState.waiting) { | ||||
|             return const Center(child: CircularProgressIndicator()); | ||||
|           } | ||||
|  | ||||
|           if (snapshot.hasError) { | ||||
|             return Center(child: Text('somethingWentWrong').tr()); | ||||
|           } | ||||
|  | ||||
|           final deviceId = snapshot.data!; | ||||
|  | ||||
|           return Column( | ||||
|             children: [ | ||||
|               Expanded( | ||||
|                 child: InAppWebView( | ||||
|                   initialSettings: InAppWebViewSettings( | ||||
|                     userAgent: | ||||
|                         kIsWeb | ||||
|                             ? null | ||||
|                             : Platform.isIOS | ||||
|                             ? 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1' | ||||
|                             : Platform.isAndroid | ||||
|                             ? 'Mozilla/5.0 (Linux; Android 13) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Mobile Safari/537.36' | ||||
|                             : 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36', | ||||
|                   ), | ||||
|                   initialUrlRequest: URLRequest( | ||||
|                     url: WebUri('$serverUrl/auth/login/${widget.provider}'), | ||||
|                     headers: { | ||||
|                       if (token?.token.isNotEmpty ?? false) | ||||
|                         'Authorization': 'AtField ${token!.token}', | ||||
|                       'X-Device-Id': deviceId, | ||||
|                     }, | ||||
|                   ), | ||||
|                   onWebViewCreated: (controller) { | ||||
|                     // Register a handler to receive the token from JavaScript | ||||
|                     controller.addJavaScriptHandler( | ||||
|                       handlerName: 'tokenHandler', | ||||
|                       callback: (args) { | ||||
|                         // args[0] will be the token string | ||||
|                         if (args.isNotEmpty && args[0] is String) { | ||||
|                           setState(() { | ||||
|                             authToken = args[0]; | ||||
|                           }); | ||||
|  | ||||
|                           // Return the token and close the webview | ||||
|                           Navigator.of(context).pop(authToken); | ||||
|                         } | ||||
|                       }, | ||||
|                     ); | ||||
|                   }, | ||||
|                   shouldOverrideUrlLoading: ( | ||||
|                     controller, | ||||
|                     navigationAction, | ||||
|                   ) async { | ||||
|                     final url = navigationAction.request.url; | ||||
|                     if (url != null) { | ||||
|                       setState(() { | ||||
|                         currentUrl = url.toString(); | ||||
|                         _urlController.text = currentUrl ?? ''; | ||||
|                         _isLoading = true; | ||||
|                       }); | ||||
|  | ||||
|                       final path = url.path; | ||||
|                       final queryParams = url.queryParameters; | ||||
|  | ||||
|                       // Check if we're on the token page | ||||
|                       if (path.endsWith('/auth/callback')) { | ||||
|                         // Extract token from URL | ||||
|                         final challenge = queryParams['challenge']; | ||||
|                         // Return the token and close the webview | ||||
|                         Navigator.of(context).pop(challenge); | ||||
|                         return NavigationActionPolicy.CANCEL; | ||||
|                       } | ||||
|                     } | ||||
|                     return NavigationActionPolicy.ALLOW; | ||||
|                   }, | ||||
|                   onUpdateVisitedHistory: (controller, url, androidIsReload) { | ||||
|                     if (url != null) { | ||||
|                       setState(() { | ||||
|                         currentUrl = url.toString(); | ||||
|                         _urlController.text = currentUrl ?? ''; | ||||
|                       }); | ||||
|                     } | ||||
|                   }, | ||||
|                   onLoadStop: (controller, url) { | ||||
|                     setState(() { | ||||
|                       _isLoading = false; | ||||
|                     }); | ||||
|                   }, | ||||
|                   onLoadStart: (controller, url) { | ||||
|                     setState(() { | ||||
|                       _isLoading = true; | ||||
|                     }); | ||||
|                   }, | ||||
|                   onLoadError: (controller, url, code, message) { | ||||
|                     setState(() { | ||||
|                       _isLoading = false; | ||||
|                     }); | ||||
|                   }, | ||||
|                 ), | ||||
|               ), | ||||
|               // Loading progress indicator | ||||
|               if (_isLoading) | ||||
|                 LinearProgressIndicator( | ||||
|                   color: Theme.of(context).colorScheme.primary, | ||||
|                   backgroundColor: Theme.of(context).colorScheme.surfaceVariant, | ||||
|                   borderRadius: BorderRadius.zero, | ||||
|                   stopIndicatorRadius: 0, | ||||
|                   minHeight: 2, | ||||
|                 ) | ||||
|               else | ||||
|                 ColoredBox( | ||||
|                   color: Theme.of(context).colorScheme.surfaceVariant, | ||||
|                 ).height(2), | ||||
|               // Debug location bar (only visible in debug mode) | ||||
|               Container( | ||||
|                 padding: EdgeInsets.only( | ||||
|                   left: 16, | ||||
|                   right: 0, | ||||
|                   bottom: MediaQuery.of(context).padding.bottom + 8, | ||||
|                   top: 8, | ||||
|                 ), | ||||
|                 color: Theme.of(context).colorScheme.surface, | ||||
|                 child: Row( | ||||
|                   children: [ | ||||
|                     Expanded( | ||||
|                       child: TextField( | ||||
|                         controller: _urlController, | ||||
|                         decoration: InputDecoration( | ||||
|                           isDense: true, | ||||
|                           contentPadding: const EdgeInsets.symmetric( | ||||
|                             horizontal: 8, | ||||
|                             vertical: 8, | ||||
|                           ), | ||||
|                           border: OutlineInputBorder( | ||||
|                             borderRadius: BorderRadius.circular(4), | ||||
|                           ), | ||||
|                           hintText: 'URL', | ||||
|                         ), | ||||
|                         style: const TextStyle(fontSize: 12), | ||||
|                         readOnly: true, | ||||
|                       ), | ||||
|                     ), | ||||
|                     const Gap(4), | ||||
|                     IconButton( | ||||
|                       icon: const Icon(Icons.copy, size: 20), | ||||
|                       padding: const EdgeInsets.all(4), | ||||
|                       constraints: const BoxConstraints(), | ||||
|                       onPressed: () { | ||||
|                         if (currentUrl != null) { | ||||
|                           Clipboard.setData(ClipboardData(text: currentUrl!)); | ||||
|                           ScaffoldMessenger.of(context).showSnackBar( | ||||
|                             SnackBar( | ||||
|                               content: Text('copyToClipboard').tr(), | ||||
|                               duration: const Duration(seconds: 1), | ||||
|                             ), | ||||
|                           ); | ||||
|                         } | ||||
|                       }, | ||||
|                     ), | ||||
|                   ], | ||||
|                 ), | ||||
|               ), | ||||
|             ], | ||||
|           ); | ||||
|         }, | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										86
									
								
								lib/screens/auth/oidc.web.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								lib/screens/auth/oidc.web.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,86 @@ | ||||
| // ignore_for_file: invalid_runtime_check_with_js_interop_types | ||||
|  | ||||
| import 'dart:ui_web' as ui; | ||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:island/pods/config.dart'; | ||||
| import 'package:island/pods/network.dart'; | ||||
| import 'package:island/widgets/app_scaffold.dart'; | ||||
| import 'package:web/web.dart' as web; | ||||
| import 'package:flutter/material.dart'; | ||||
|  | ||||
| class OidcScreen extends ConsumerStatefulWidget { | ||||
|   final String provider; | ||||
|   final String? title; | ||||
|  | ||||
|   const OidcScreen({super.key, required this.provider, this.title}); | ||||
|  | ||||
|   @override | ||||
|   ConsumerState<OidcScreen> createState() => _OidcScreenState(); | ||||
| } | ||||
|  | ||||
| class _OidcScreenState extends ConsumerState<OidcScreen> { | ||||
|   bool _isInitialized = false; | ||||
|   final String _viewType = 'oidc-iframe'; | ||||
|  | ||||
|   void _setupWebListener(String serverUrl) { | ||||
|     // Listen for messages from the iframe | ||||
|     web.window.onMessage.listen((event) { | ||||
|       if (event.data != null && event.data is String) { | ||||
|         final message = event.data as String; | ||||
|         if (message.startsWith("token=")) { | ||||
|           String token = message.replaceFirst("token=", ""); | ||||
|           // Return the token and close the screen | ||||
|           if (mounted) Navigator.pop(context, token); | ||||
|         } | ||||
|       } | ||||
|     }); | ||||
|  | ||||
|     // Create the iframe for the OIDC login | ||||
|     final token = ref.watch(tokenProvider); | ||||
|     final iframe = | ||||
|         web.HTMLIFrameElement() | ||||
|           ..src = | ||||
|               (token?.token.isNotEmpty ?? false) | ||||
|                   ? '$serverUrl/auth/login/${widget.provider}?tk=${token!.token}' | ||||
|                   : '$serverUrl/auth/login/${widget.provider}' | ||||
|           ..style.border = 'none' | ||||
|           ..width = '100%' | ||||
|           ..height = '100%'; | ||||
|  | ||||
|     // Add the iframe to the document body | ||||
|     web.document.body!.append(iframe); | ||||
|  | ||||
|     // Register the iframe as a platform view | ||||
|     ui.platformViewRegistry.registerViewFactory( | ||||
|       _viewType, | ||||
|       (int viewId) => iframe, | ||||
|     ); | ||||
|  | ||||
|     setState(() { | ||||
|       _isInitialized = true; | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   void initState() { | ||||
|     super.initState(); | ||||
|     Future.delayed(Duration.zero, () { | ||||
|       final serverUrl = ref.watch(serverUrlProvider); | ||||
|       _setupWebListener(serverUrl); | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return AppScaffold( | ||||
|       appBar: AppBar( | ||||
|         title: widget.title != null ? Text(widget.title!) : Text('login').tr(), | ||||
|       ), | ||||
|       body: | ||||
|           _isInitialized | ||||
|               ? HtmlElementView(viewType: _viewType) | ||||
|               : Center(child: CircularProgressIndicator()), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| @@ -1,3 +1,5 @@ | ||||
| import 'dart:developer'; | ||||
|  | ||||
| import 'package:auto_route/auto_route.dart'; | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| @@ -17,6 +19,8 @@ class TabNavigationObserver extends AutoRouterObserver { | ||||
|  | ||||
|   @override | ||||
|   void didPush(Route route, Route? previousRoute) { | ||||
|     log('pushed ${previousRoute?.settings.name} -> ${route.settings.name}'); | ||||
|     if (route is DialogRoute) return; | ||||
|     Future(() { | ||||
|       onChange(route.settings.name); | ||||
|     }); | ||||
| @@ -24,6 +28,8 @@ class TabNavigationObserver extends AutoRouterObserver { | ||||
|  | ||||
|   @override | ||||
|   void didPop(Route route, Route? previousRoute) { | ||||
|     log('popped ${route.settings.name} -> ${previousRoute?.settings.name}'); | ||||
|     if (route is DialogRoute) return; | ||||
|     Future(() { | ||||
|       onChange(previousRoute?.settings.name); | ||||
|     }); | ||||
| @@ -43,7 +49,6 @@ class TabsNavigationWidget extends HookConsumerWidget { | ||||
|   @override | ||||
|   Widget build(BuildContext context, WidgetRef ref) { | ||||
|     final useHorizontalLayout = isWideScreen(context); | ||||
|     final useExpandableLayout = isWidestScreen(context); | ||||
|     final currentRoute = ref.watch(currentRouteProvider); | ||||
|  | ||||
|     final notificationUnreadCount = ref.watch( | ||||
| @@ -80,6 +85,7 @@ class TabsNavigationWidget extends HookConsumerWidget { | ||||
|     ]; | ||||
|     final routeNames = [ | ||||
|       ExploreRoute.name, | ||||
|       ExploreShellRoute.name, | ||||
|       ChatListRoute.name, | ||||
|       RealmListRoute.name, | ||||
|       AccountRoute.name, | ||||
| @@ -110,8 +116,6 @@ class TabsNavigationWidget extends HookConsumerWidget { | ||||
|                         Gap(MediaQuery.of(context).padding.top + 8), | ||||
|                         Expanded( | ||||
|                           child: NavigationRail( | ||||
|                             minExtendedWidth: 200, | ||||
|                             extended: useExpandableLayout, | ||||
|                             selectedIndex: activeIndex, | ||||
|                             onDestinationSelected: (index) { | ||||
|                               router.replace(routes[index]); | ||||
|   | ||||
							
								
								
									
										297
									
								
								lib/screens/chat/call.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										297
									
								
								lib/screens/chat/call.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,297 @@ | ||||
| import 'package:auto_route/annotations.dart'; | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter_hooks/flutter_hooks.dart'; | ||||
| import 'package:gap/gap.dart'; | ||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||
| import 'package:island/pods/call.dart'; | ||||
| import 'package:island/services/responsive.dart'; | ||||
| import 'package:island/widgets/app_scaffold.dart'; | ||||
| import 'package:island/widgets/chat/call_button.dart'; | ||||
| import 'package:island/widgets/chat/call_overlay.dart'; | ||||
| import 'package:island/widgets/chat/call_participant_tile.dart'; | ||||
| import 'package:livekit_client/livekit_client.dart'; | ||||
| import 'package:styled_widget/styled_widget.dart'; | ||||
|  | ||||
| @RoutePage() | ||||
| class CallScreen extends HookConsumerWidget { | ||||
|   final String roomId; | ||||
|   const CallScreen({super.key, @PathParam('id') required this.roomId}); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context, WidgetRef ref) { | ||||
|     final ongoingCall = ref.watch(ongoingCallProvider(roomId)); | ||||
|     final callState = ref.watch(callNotifierProvider); | ||||
|     final callNotifier = ref.read(callNotifierProvider.notifier); | ||||
|  | ||||
|     useEffect(() { | ||||
|       callNotifier.joinRoom(roomId); | ||||
|       return null; | ||||
|     }, []); | ||||
|  | ||||
|     final viewMode = useState<String>('grid'); | ||||
|  | ||||
|     return AppScaffold( | ||||
|       appBar: AppBar( | ||||
|         leading: PageBackButton( | ||||
|           onWillPop: () { | ||||
|             showDialog<void>( | ||||
|               context: context, | ||||
|               builder: (context) { | ||||
|                 return AlertDialog( | ||||
|                   content: const Text( | ||||
|                     'Do you want to leave the call or leave it in background?', | ||||
|                   ), | ||||
|                   actions: [ | ||||
|                     TextButton( | ||||
|                       onPressed: () { | ||||
|                         Navigator.of(context).pop(); | ||||
|                       }, | ||||
|                       child: const Text('In Background'), | ||||
|                     ), | ||||
|                     TextButton( | ||||
|                       onPressed: () async { | ||||
|                         Navigator.of(context).pop(); | ||||
|                         await callNotifier.disconnect(); | ||||
|                         callNotifier.dispose(); | ||||
|                       }, | ||||
|                       child: const Text('Leave'), | ||||
|                     ), | ||||
|                   ], | ||||
|                 ); | ||||
|               }, | ||||
|             ); | ||||
|           }, | ||||
|         ), | ||||
|         title: Column( | ||||
|           crossAxisAlignment: CrossAxisAlignment.center, | ||||
|           children: [ | ||||
|             Text( | ||||
|               ongoingCall.value?.room.name ?? 'call'.tr(), | ||||
|               style: const TextStyle(fontSize: 16), | ||||
|             ), | ||||
|             Text( | ||||
|               callState.isConnected | ||||
|                   ? formatDuration(callState.duration) | ||||
|                   : 'Connecting', | ||||
|               style: const TextStyle(fontSize: 14), | ||||
|             ), | ||||
|           ], | ||||
|         ), | ||||
|         actions: [ | ||||
|           Row( | ||||
|             mainAxisAlignment: MainAxisAlignment.end, | ||||
|             children: [ | ||||
|               IconButton( | ||||
|                 icon: Icon(Icons.grid_view), | ||||
|                 tooltip: 'Grid View', | ||||
|                 onPressed: () => viewMode.value = 'grid', | ||||
|                 color: | ||||
|                     viewMode.value == 'grid' | ||||
|                         ? Theme.of(context).colorScheme.primary | ||||
|                         : null, | ||||
|               ), | ||||
|               IconButton( | ||||
|                 icon: Icon(Icons.view_agenda), | ||||
|                 tooltip: 'Stage View', | ||||
|                 onPressed: () => viewMode.value = 'stage', | ||||
|                 color: | ||||
|                     viewMode.value == 'stage' | ||||
|                         ? Theme.of(context).colorScheme.primary | ||||
|                         : null, | ||||
|               ), | ||||
|             ], | ||||
|           ), | ||||
|           const Gap(8), | ||||
|         ], | ||||
|       ), | ||||
|       body: | ||||
|           callState.error != null | ||||
|               ? Center( | ||||
|                 child: Text( | ||||
|                   callState.error!, | ||||
|                   textAlign: TextAlign.center, | ||||
|                   style: const TextStyle(color: Colors.red), | ||||
|                 ), | ||||
|               ) | ||||
|               : Column( | ||||
|                 children: [ | ||||
|                   Expanded( | ||||
|                     child: Builder( | ||||
|                       builder: (context) { | ||||
|                         if (!callState.isConnected) { | ||||
|                           return const Center( | ||||
|                             child: CircularProgressIndicator(), | ||||
|                           ); | ||||
|                         } | ||||
|                         if (callNotifier.participants.isEmpty) { | ||||
|                           return const Center( | ||||
|                             child: Text('No participants in call'), | ||||
|                           ); | ||||
|                         } | ||||
|                         final participants = callNotifier.participants; | ||||
|                         final allAudioOnly = participants.every( | ||||
|                           (p) => | ||||
|                               !(p.hasVideo && | ||||
|                                   p.remoteParticipant.trackPublications.values | ||||
|                                       .any( | ||||
|                                         (pub) => | ||||
|                                             pub.track != null && | ||||
|                                             pub.kind == TrackType.VIDEO, | ||||
|                                       )), | ||||
|                         ); | ||||
|                         if (allAudioOnly) { | ||||
|                           // Audio-only: show avatars in a compact row | ||||
|                           return Center( | ||||
|                             child: SingleChildScrollView( | ||||
|                               scrollDirection: Axis.horizontal, | ||||
|                               child: Wrap( | ||||
|                                 crossAxisAlignment: WrapCrossAlignment.center, | ||||
|                                 alignment: WrapAlignment.center, | ||||
|                                 spacing: 8, | ||||
|                                 runSpacing: 8, | ||||
|                                 children: [ | ||||
|                                   for (final live in participants) | ||||
|                                     Padding( | ||||
|                                       padding: const EdgeInsets.symmetric( | ||||
|                                         horizontal: 8, | ||||
|                                       ), | ||||
|                                       child: SpeakingRippleAvatar( | ||||
|                                         isSpeaking: live.isSpeaking, | ||||
|                                         audioLevel: | ||||
|                                             live.remoteParticipant.audioLevel, | ||||
|                                         pictureId: | ||||
|                                             live | ||||
|                                                 .participant | ||||
|                                                 .profile | ||||
|                                                 ?.account | ||||
|                                                 .profile | ||||
|                                                 .picture | ||||
|                                                 ?.id, | ||||
|                                         size: 72, | ||||
|                                       ), | ||||
|                                     ), | ||||
|                                 ], | ||||
|                               ), | ||||
|                             ), | ||||
|                           ); | ||||
|                         } | ||||
|                         if (viewMode.value == 'stage') { | ||||
|                           // Stage view: show main speaker(s) large, others in row | ||||
|                           final mainSpeakers = | ||||
|                               participants | ||||
|                                   .where( | ||||
|                                     (p) => p | ||||
|                                         .remoteParticipant | ||||
|                                         .trackPublications | ||||
|                                         .values | ||||
|                                         .any( | ||||
|                                           (pub) => | ||||
|                                               pub.track != null && | ||||
|                                               pub.kind == TrackType.VIDEO, | ||||
|                                         ), | ||||
|                                   ) | ||||
|                                   .toList(); | ||||
|                           if (mainSpeakers.isEmpty && participants.isNotEmpty) { | ||||
|                             mainSpeakers.add(participants.first); | ||||
|                           } | ||||
|                           final others = | ||||
|                               participants | ||||
|                                   .where((p) => !mainSpeakers.contains(p)) | ||||
|                                   .toList(); | ||||
|                           return Column( | ||||
|                             children: [ | ||||
|                               Expanded( | ||||
|                                 child: Row( | ||||
|                                   mainAxisAlignment: MainAxisAlignment.center, | ||||
|                                   children: [ | ||||
|                                     for (final speaker in mainSpeakers) | ||||
|                                       Expanded( | ||||
|                                         child: | ||||
|                                             AspectRatio( | ||||
|                                               aspectRatio: 16 / 9, | ||||
|                                               child: Card( | ||||
|                                                 margin: EdgeInsets.zero, | ||||
|                                                 child: ClipRRect( | ||||
|                                                   borderRadius: | ||||
|                                                       BorderRadius.circular(8), | ||||
|                                                   child: Column( | ||||
|                                                     children: [ | ||||
|                                                       CallParticipantTile( | ||||
|                                                         live: speaker, | ||||
|                                                       ), | ||||
|                                                     ], | ||||
|                                                   ), | ||||
|                                                 ), | ||||
|                                               ), | ||||
|                                             ).center(), | ||||
|                                       ), | ||||
|                                   ], | ||||
|                                 ).padding(horizontal: 12), | ||||
|                               ), | ||||
|                               if (others.isNotEmpty) | ||||
|                                 SizedBox( | ||||
|                                   height: 100, | ||||
|                                   child: ListView( | ||||
|                                     scrollDirection: Axis.horizontal, | ||||
|                                     children: [ | ||||
|                                       for (final other in others) | ||||
|                                         Padding( | ||||
|                                           padding: const EdgeInsets.symmetric( | ||||
|                                             horizontal: 8, | ||||
|                                           ), | ||||
|                                           child: CallParticipantTile( | ||||
|                                             live: other, | ||||
|                                           ), | ||||
|                                         ), | ||||
|                                     ], | ||||
|                                   ), | ||||
|                                 ), | ||||
|                             ], | ||||
|                           ); | ||||
|                         } | ||||
|                         // Default: grid view | ||||
|                         return GridView.builder( | ||||
|                           padding: const EdgeInsets.symmetric( | ||||
|                             horizontal: 12, | ||||
|                             vertical: 8, | ||||
|                           ), | ||||
|                           gridDelegate: | ||||
|                               SliverGridDelegateWithFixedCrossAxisCount( | ||||
|                                 crossAxisCount: | ||||
|                                     isWidestScreen(context) | ||||
|                                         ? 4 | ||||
|                                         : isWiderScreen(context) | ||||
|                                         ? 3 | ||||
|                                         : 2, | ||||
|                                 childAspectRatio: 16 / 9, | ||||
|                                 crossAxisSpacing: 8, | ||||
|                                 mainAxisSpacing: 8, | ||||
|                               ), | ||||
|                           itemCount: participants.length, | ||||
|                           itemBuilder: (context, idx) { | ||||
|                             final live = participants[idx]; | ||||
|                             return AspectRatio( | ||||
|                               aspectRatio: 16 / 9, | ||||
|                               child: Card( | ||||
|                                 margin: EdgeInsets.zero, | ||||
|                                 child: ClipRRect( | ||||
|                                   borderRadius: BorderRadius.circular(8), | ||||
|                                   child: Column( | ||||
|                                     children: [CallParticipantTile(live: live)], | ||||
|                                   ), | ||||
|                                 ), | ||||
|                               ), | ||||
|                             ).center(); | ||||
|                           }, | ||||
|                         ); | ||||
|                       }, | ||||
|                     ), | ||||
|                   ), | ||||
|                   CallControlsBar(), | ||||
|                   Gap(MediaQuery.of(context).padding.bottom + 16), | ||||
|                 ], | ||||
|               ), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| @@ -11,6 +11,7 @@ import 'package:image_picker/image_picker.dart'; | ||||
| import 'package:island/models/chat.dart'; | ||||
| import 'package:island/models/file.dart'; | ||||
| import 'package:island/models/realm.dart'; | ||||
| import 'package:island/pods/call.dart'; | ||||
| import 'package:island/pods/chat_summary.dart'; | ||||
| import 'package:island/pods/config.dart'; | ||||
| import 'package:island/pods/network.dart'; | ||||
| @@ -21,7 +22,9 @@ import 'package:island/services/responsive.dart'; | ||||
| import 'package:island/widgets/account/account_picker.dart'; | ||||
| import 'package:island/widgets/alert.dart'; | ||||
| import 'package:island/widgets/app_scaffold.dart'; | ||||
| import 'package:island/widgets/chat/call_overlay.dart'; | ||||
| import 'package:island/widgets/content/cloud_files.dart'; | ||||
| import 'package:island/widgets/content/sheet.dart'; | ||||
| import 'package:island/widgets/realms/selection_dropdown.dart'; | ||||
| import 'package:island/widgets/response.dart'; | ||||
| import 'package:material_symbols_icons/symbols.dart'; | ||||
| @@ -107,7 +110,7 @@ class ChatRoomListTile extends HookConsumerWidget { | ||||
|         }, | ||||
|         loading: () => const SizedBox.shrink(), | ||||
|         error: | ||||
|             (_, __) => | ||||
|             (_, _) => | ||||
|                 isDirect && room.description == null | ||||
|                     ? Text( | ||||
|                       room.members!.map((e) => '@${e.account.name}').join(', '), | ||||
| @@ -125,19 +128,19 @@ class ChatRoomListTile extends HookConsumerWidget { | ||||
|         isLabelVisible: summary.when( | ||||
|           data: (data) => (data?.unreadCount ?? 0) > 0, | ||||
|           loading: () => false, | ||||
|           error: (_, __) => false, | ||||
|           error: (_, _) => false, | ||||
|         ), | ||||
|         child: | ||||
|             (isDirect && room.pictureId == null) | ||||
|             (isDirect && room.picture?.id == null) | ||||
|                 ? SplitAvatarWidget( | ||||
|                   filesId: | ||||
|                       room.members! | ||||
|                           .map((e) => e.account.profile.pictureId) | ||||
|                           .map((e) => e.account.profile.picture?.id) | ||||
|                           .toList(), | ||||
|                 ) | ||||
|                 : room.pictureId == null | ||||
|                 : room.picture?.id == null | ||||
|                 ? CircleAvatar(child: Text(room.name![0].toUpperCase())) | ||||
|                 : ProfilePictureWidget(fileId: room.pictureId), | ||||
|                 : ProfilePictureWidget(fileId: room.picture?.id), | ||||
|       ), | ||||
|       title: Text( | ||||
|         (isDirect && room.name == null) | ||||
| @@ -145,14 +148,14 @@ class ChatRoomListTile extends HookConsumerWidget { | ||||
|             : room.name ?? '', | ||||
|       ), | ||||
|       subtitle: buildSubtitle(), | ||||
|       trailing: trailing, // Add this line | ||||
|       onTap: () async { | ||||
|         // Clear unread count if there are unread messages | ||||
|         final summary = await ref.read(chatSummaryProvider.future); | ||||
|         if ((summary[room.id]?.unreadCount ?? 0) > 0) { | ||||
|           await ref | ||||
|               .read(chatSummaryProvider.notifier) | ||||
|               .clearUnreadCount(room.id); | ||||
|         } | ||||
|         ref.read(chatSummaryProvider.future).then((summary) { | ||||
|           if ((summary[room.id]?.unreadCount ?? 0) > 0) { | ||||
|             ref.read(chatSummaryProvider.notifier).clearUnreadCount(room.id); | ||||
|           } | ||||
|         }); | ||||
|         onTap?.call(); | ||||
|       }, | ||||
|     ); | ||||
| @@ -213,6 +216,8 @@ class ChatListScreen extends HookConsumerWidget { | ||||
|       0, | ||||
|     ); // 0 for All, 1 for Direct Messages, 2 for Group Chats | ||||
|  | ||||
|     final callState = ref.watch(callNotifierProvider); | ||||
|  | ||||
|     useEffect(() { | ||||
|       tabController.addListener(() { | ||||
|         selectedTab.value = tabController.index; | ||||
| @@ -241,9 +246,33 @@ class ChatListScreen extends HookConsumerWidget { | ||||
|         bottom: TabBar( | ||||
|           controller: tabController, | ||||
|           tabs: [ | ||||
|             Tab(text: 'chatTabAll'.tr()), | ||||
|             Tab(text: 'chatTabDirect'.tr()), | ||||
|             Tab(text: 'chatTabGroup'.tr()), | ||||
|             Tab( | ||||
|               child: Text( | ||||
|                 'chatTabAll'.tr(), | ||||
|                 textAlign: TextAlign.center, | ||||
|                 style: TextStyle( | ||||
|                   color: Theme.of(context).appBarTheme.foregroundColor!, | ||||
|                 ), | ||||
|               ), | ||||
|             ), | ||||
|             Tab( | ||||
|               child: Text( | ||||
|                 'chatTabDirect'.tr(), | ||||
|                 textAlign: TextAlign.center, | ||||
|                 style: TextStyle( | ||||
|                   color: Theme.of(context).appBarTheme.foregroundColor!, | ||||
|                 ), | ||||
|               ), | ||||
|             ), | ||||
|             Tab( | ||||
|               child: Text( | ||||
|                 'chatTabGroup'.tr(), | ||||
|                 textAlign: TextAlign.center, | ||||
|                 style: TextStyle( | ||||
|                   color: Theme.of(context).appBarTheme.foregroundColor!, | ||||
|                 ), | ||||
|               ), | ||||
|             ), | ||||
|           ], | ||||
|         ), | ||||
|         actions: [ | ||||
| @@ -252,13 +281,13 @@ class ChatListScreen extends HookConsumerWidget { | ||||
|               label: Text( | ||||
|                 chatInvites.when( | ||||
|                   data: (invites) => invites.length.toString(), | ||||
|                   error: (_, __) => '0', | ||||
|                   error: (_, _) => '0', | ||||
|                   loading: () => '0', | ||||
|                 ), | ||||
|               ), | ||||
|               isLabelVisible: chatInvites.when( | ||||
|                 data: (invites) => invites.isNotEmpty, | ||||
|                 error: (_, __) => false, | ||||
|                 error: (_, _) => false, | ||||
|                 loading: () => false, | ||||
|               ), | ||||
|               child: const Icon(Symbols.email), | ||||
| @@ -310,57 +339,99 @@ class ChatListScreen extends HookConsumerWidget { | ||||
|         }, | ||||
|         child: const Icon(Symbols.add), | ||||
|       ), | ||||
|       body: chats.when( | ||||
|         data: | ||||
|             (items) => RefreshIndicator( | ||||
|               onRefresh: | ||||
|                   () => Future.sync(() { | ||||
|                     ref.invalidate(chatroomsJoinedProvider); | ||||
|                   }), | ||||
|               child: ListView.builder( | ||||
|                 padding: EdgeInsets.zero, | ||||
|                 itemCount: | ||||
|                     items | ||||
|                         .where( | ||||
|                           (item) => | ||||
|                               selectedTab.value == 0 || | ||||
|                               (selectedTab.value == 1 && item.type == 1) || | ||||
|                               (selectedTab.value == 2 && item.type != 1), | ||||
|                         ) | ||||
|                         .length, | ||||
|                 itemBuilder: (context, index) { | ||||
|                   final filteredItems = | ||||
|                       items | ||||
|                           .where( | ||||
|                             (item) => | ||||
|                                 selectedTab.value == 0 || | ||||
|                                 (selectedTab.value == 1 && item.type == 1) || | ||||
|                                 (selectedTab.value == 2 && item.type != 1), | ||||
|                           ) | ||||
|                           .toList(); | ||||
|                   final item = filteredItems[index]; | ||||
|                   return ChatRoomListTile( | ||||
|                     room: item, | ||||
|                     isDirect: item.type == 1, | ||||
|                     onTap: () { | ||||
|                       if (context.router.topRoute.name == ChatRoomRoute.name) { | ||||
|                         context.router.replace(ChatRoomRoute(id: item.id)); | ||||
|                       } else { | ||||
|                         context.router.push(ChatRoomRoute(id: item.id)); | ||||
|                       } | ||||
|                     }, | ||||
|       body: Stack( | ||||
|         children: [ | ||||
|           Column( | ||||
|             children: [ | ||||
|               Consumer( | ||||
|                 builder: (context, ref, _) { | ||||
|                   final summaryState = ref.watch(chatSummaryProvider); | ||||
|                   return summaryState.maybeWhen( | ||||
|                     loading: | ||||
|                         () => const LinearProgressIndicator( | ||||
|                           minHeight: 2, | ||||
|                           borderRadius: BorderRadius.zero, | ||||
|                         ), | ||||
|                     orElse: () => const SizedBox.shrink(), | ||||
|                   ); | ||||
|                 }, | ||||
|               ), | ||||
|             ), | ||||
|         loading: () => const Center(child: CircularProgressIndicator()), | ||||
|         error: | ||||
|             (error, stack) => ResponseErrorWidget( | ||||
|               error: error, | ||||
|               onRetry: () { | ||||
|                 ref.invalidate(chatroomsJoinedProvider); | ||||
|               }, | ||||
|             ), | ||||
|               Expanded( | ||||
|                 child: chats.when( | ||||
|                   data: | ||||
|                       (items) => RefreshIndicator( | ||||
|                         onRefresh: | ||||
|                             () => Future.sync(() { | ||||
|                               ref.invalidate(chatroomsJoinedProvider); | ||||
|                             }), | ||||
|                         child: ListView.builder( | ||||
|                           padding: | ||||
|                               callState.isConnected | ||||
|                                   ? EdgeInsets.only(bottom: 96) | ||||
|                                   : EdgeInsets.zero, | ||||
|                           itemCount: | ||||
|                               items | ||||
|                                   .where( | ||||
|                                     (item) => | ||||
|                                         selectedTab.value == 0 || | ||||
|                                         (selectedTab.value == 1 && | ||||
|                                             item.type == 1) || | ||||
|                                         (selectedTab.value == 2 && | ||||
|                                             item.type != 1), | ||||
|                                   ) | ||||
|                                   .length, | ||||
|                           itemBuilder: (context, index) { | ||||
|                             final filteredItems = | ||||
|                                 items | ||||
|                                     .where( | ||||
|                                       (item) => | ||||
|                                           selectedTab.value == 0 || | ||||
|                                           (selectedTab.value == 1 && | ||||
|                                               item.type == 1) || | ||||
|                                           (selectedTab.value == 2 && | ||||
|                                               item.type != 1), | ||||
|                                     ) | ||||
|                                     .toList(); | ||||
|                             final item = filteredItems[index]; | ||||
|                             return ChatRoomListTile( | ||||
|                               room: item, | ||||
|                               isDirect: item.type == 1, | ||||
|                               onTap: () { | ||||
|                                 if (context.router.topRoute.name == | ||||
|                                     ChatRoomRoute.name) { | ||||
|                                   context.router.replace( | ||||
|                                     ChatRoomRoute(id: item.id), | ||||
|                                   ); | ||||
|                                 } else { | ||||
|                                   context.router.push( | ||||
|                                     ChatRoomRoute(id: item.id), | ||||
|                                   ); | ||||
|                                 } | ||||
|                               }, | ||||
|                             ); | ||||
|                           }, | ||||
|                         ), | ||||
|                       ), | ||||
|                   loading: | ||||
|                       () => const Center(child: CircularProgressIndicator()), | ||||
|                   error: | ||||
|                       (error, stack) => ResponseErrorWidget( | ||||
|                         error: error, | ||||
|                         onRetry: () { | ||||
|                           ref.invalidate(chatroomsJoinedProvider); | ||||
|                         }, | ||||
|                       ), | ||||
|                 ), | ||||
|               ), | ||||
|             ], | ||||
|           ), | ||||
|           Positioned( | ||||
|             left: 0, | ||||
|             right: 0, | ||||
|             bottom: 0, | ||||
|             child: const CallOverlayBar().padding(horizontal: 16, vertical: 12), | ||||
|           ), | ||||
|         ], | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| @@ -457,19 +528,15 @@ class EditChatScreen extends HookConsumerWidget { | ||||
|       submitting.value = true; | ||||
|       try { | ||||
|         final baseUrl = ref.watch(serverUrlProvider); | ||||
|         final atk = await getFreshAtk( | ||||
|           ref.watch(tokenPairProvider), | ||||
|           baseUrl, | ||||
|           onRefreshed: (atk, rtk) { | ||||
|             setTokenPair(ref.watch(sharedPreferencesProvider), atk, rtk); | ||||
|             ref.invalidate(tokenPairProvider); | ||||
|           }, | ||||
|         ); | ||||
|         if (atk == null) throw ArgumentError('Access token is null'); | ||||
|         final token = await getToken(ref.watch(tokenProvider)); | ||||
|         if (token == null) throw ArgumentError('Token is null'); | ||||
|         final cloudFile = | ||||
|             await putMediaToCloud( | ||||
|               fileData: result, | ||||
|               atk: atk, | ||||
|               fileData: UniversalFile( | ||||
|                 data: result, | ||||
|                 type: UniversalFileType.image, | ||||
|               ), | ||||
|               atk: token, | ||||
|               baseUrl: baseUrl, | ||||
|               filename: result.name, | ||||
|               mimetype: result.mimeType ?? 'image/jpeg', | ||||
| @@ -530,7 +597,7 @@ class EditChatScreen extends HookConsumerWidget { | ||||
|             realms: joinedRealms.when( | ||||
|               data: (realms) => realms, | ||||
|               loading: () => [], | ||||
|               error: (_, __) => [], | ||||
|               error: (_, _) => [], | ||||
|             ), | ||||
|             onChanged: (SnRealm? value) { | ||||
|               currentRealm.value = value; | ||||
| @@ -652,109 +719,77 @@ class _ChatInvitesSheet extends HookConsumerWidget { | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     return Container( | ||||
|       constraints: BoxConstraints( | ||||
|         maxHeight: MediaQuery.of(context).size.height * 0.8, | ||||
|       ), | ||||
|       child: Column( | ||||
|         mainAxisSize: MainAxisSize.min, | ||||
|         children: [ | ||||
|           Padding( | ||||
|             padding: EdgeInsets.only(top: 16, left: 20, right: 16, bottom: 12), | ||||
|             child: Row( | ||||
|               children: [ | ||||
|                 Text( | ||||
|                   'invites'.tr(), | ||||
|                   style: Theme.of(context).textTheme.headlineSmall?.copyWith( | ||||
|                     fontWeight: FontWeight.w600, | ||||
|                     letterSpacing: -0.5, | ||||
|                   ), | ||||
|                 ), | ||||
|                 const Spacer(), | ||||
|                 IconButton( | ||||
|                   icon: const Icon(Symbols.refresh), | ||||
|                   style: IconButton.styleFrom(minimumSize: const Size(36, 36)), | ||||
|                   onPressed: () { | ||||
|                     ref.invalidate(chatroomInvitesProvider); | ||||
|                   }, | ||||
|                 ), | ||||
|                 IconButton( | ||||
|                   icon: const Icon(Symbols.close), | ||||
|                   onPressed: () => Navigator.pop(context), | ||||
|                   style: IconButton.styleFrom(minimumSize: const Size(36, 36)), | ||||
|                 ), | ||||
|               ], | ||||
|             ), | ||||
|           ), | ||||
|           const Divider(height: 1), | ||||
|           Expanded( | ||||
|             child: invites.when( | ||||
|               data: | ||||
|                   (items) => | ||||
|                       items.isEmpty | ||||
|                           ? Center( | ||||
|                             child: | ||||
|                                 Text( | ||||
|                                   'invitesEmpty', | ||||
|                                   textAlign: TextAlign.center, | ||||
|                                 ).tr(), | ||||
|                           ) | ||||
|                           : ListView.builder( | ||||
|                             shrinkWrap: true, | ||||
|                             itemCount: items.length, | ||||
|                             itemBuilder: (context, index) { | ||||
|                               final invite = items[index]; | ||||
|                               return ChatRoomListTile( | ||||
|                                 room: invite.chatRoom!, | ||||
|                                 isDirect: invite.chatRoom!.type == 1, | ||||
|                                 subtitle: Row( | ||||
|                                   spacing: 6, | ||||
|                                   children: [ | ||||
|                                     Flexible( | ||||
|                                       child: | ||||
|                                           Text( | ||||
|                                             invite.role >= 100 | ||||
|                                                 ? 'permissionOwner' | ||||
|                                                 : invite.role >= 50 | ||||
|                                                 ? 'permissionModerator' | ||||
|                                                 : 'permissionMember', | ||||
|                                           ).tr(), | ||||
|                                     ), | ||||
|                                     if (invite.chatRoom!.type == 1) | ||||
|                                       Badge( | ||||
|                                         label: Text('directMessage').tr(), | ||||
|                                         backgroundColor: | ||||
|                                             Theme.of( | ||||
|                                               context, | ||||
|                                             ).colorScheme.primary, | ||||
|                                         textColor: | ||||
|                                             Theme.of( | ||||
|                                               context, | ||||
|                                             ).colorScheme.onPrimary, | ||||
|                                       ), | ||||
|                                   ], | ||||
|     return SheetScaffold( | ||||
|       titleText: 'invites'.tr(), | ||||
|       actions: [ | ||||
|         IconButton( | ||||
|           icon: const Icon(Symbols.refresh), | ||||
|           style: IconButton.styleFrom(minimumSize: const Size(36, 36)), | ||||
|           onPressed: () { | ||||
|             ref.invalidate(realmInvitesProvider); | ||||
|           }, | ||||
|         ), | ||||
|       ], | ||||
|       child: invites.when( | ||||
|         data: | ||||
|             (items) => | ||||
|                 items.isEmpty | ||||
|                     ? Center( | ||||
|                       child: | ||||
|                           Text( | ||||
|                             'invitesEmpty', | ||||
|                             textAlign: TextAlign.center, | ||||
|                           ).tr(), | ||||
|                     ) | ||||
|                     : ListView.builder( | ||||
|                       shrinkWrap: true, | ||||
|                       itemCount: items.length, | ||||
|                       itemBuilder: (context, index) { | ||||
|                         final invite = items[index]; | ||||
|                         return ChatRoomListTile( | ||||
|                           room: invite.chatRoom!, | ||||
|                           isDirect: invite.chatRoom!.type == 1, | ||||
|                           subtitle: Row( | ||||
|                             spacing: 6, | ||||
|                             children: [ | ||||
|                               Flexible( | ||||
|                                 child: | ||||
|                                     Text( | ||||
|                                       invite.role >= 100 | ||||
|                                           ? 'permissionOwner' | ||||
|                                           : invite.role >= 50 | ||||
|                                           ? 'permissionModerator' | ||||
|                                           : 'permissionMember', | ||||
|                                     ).tr(), | ||||
|                               ), | ||||
|                               if (invite.chatRoom!.type == 1) | ||||
|                                 Badge( | ||||
|                                   label: Text('directMessage').tr(), | ||||
|                                   backgroundColor: | ||||
|                                       Theme.of(context).colorScheme.primary, | ||||
|                                   textColor: | ||||
|                                       Theme.of(context).colorScheme.onPrimary, | ||||
|                                 ), | ||||
|                                 trailing: Row( | ||||
|                                   mainAxisSize: MainAxisSize.min, | ||||
|                                   children: [ | ||||
|                                     IconButton( | ||||
|                                       icon: const Icon(Symbols.check), | ||||
|                                       onPressed: () => acceptInvite(invite), | ||||
|                                     ), | ||||
|                                     IconButton( | ||||
|                                       icon: const Icon(Symbols.close), | ||||
|                                       onPressed: () => declineInvite(invite), | ||||
|                                     ), | ||||
|                                   ], | ||||
|                                 ), | ||||
|                               ); | ||||
|                             }, | ||||
|                             ], | ||||
|                           ), | ||||
|               loading: () => const Center(child: CircularProgressIndicator()), | ||||
|               error: (error, stack) => Center(child: Text('Error: $error')), | ||||
|             ), | ||||
|           ), | ||||
|         ], | ||||
|                           trailing: Row( | ||||
|                             mainAxisSize: MainAxisSize.min, | ||||
|                             children: [ | ||||
|                               IconButton( | ||||
|                                 icon: const Icon(Symbols.check), | ||||
|                                 onPressed: () => acceptInvite(invite), | ||||
|                               ), | ||||
|                               IconButton( | ||||
|                                 icon: const Icon(Symbols.close), | ||||
|                                 onPressed: () => declineInvite(invite), | ||||
|                               ), | ||||
|                             ], | ||||
|                           ), | ||||
|                         ); | ||||
|                       }, | ||||
|                     ), | ||||
|         loading: () => const Center(child: CircularProgressIndicator()), | ||||
|         error: (error, stack) => Center(child: Text('Error: $error')), | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
|   | ||||
| @@ -3,6 +3,7 @@ import 'dart:convert'; | ||||
| import 'dart:io'; | ||||
| import 'package:auto_route/auto_route.dart'; | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:flutter/foundation.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter/services.dart'; | ||||
| import 'package:flutter_hooks/flutter_hooks.dart'; | ||||
| @@ -18,21 +19,23 @@ import 'package:island/pods/database.dart'; | ||||
| import 'package:island/pods/network.dart'; | ||||
| import 'package:island/pods/websocket.dart'; | ||||
| import 'package:island/route.gr.dart'; | ||||
| import 'package:island/screens/posts/compose.dart'; | ||||
| import 'package:island/services/responsive.dart'; | ||||
| import 'package:island/widgets/alert.dart'; | ||||
| import 'package:island/widgets/app_scaffold.dart'; | ||||
| import 'package:island/widgets/chat/call_overlay.dart'; | ||||
| import 'package:island/widgets/chat/message_item.dart'; | ||||
| import 'package:island/widgets/content/attachment_preview.dart'; | ||||
| import 'package:island/widgets/content/cloud_files.dart'; | ||||
| import 'package:island/widgets/response.dart'; | ||||
| import 'package:material_symbols_icons/material_symbols_icons.dart'; | ||||
| import 'package:pasteboard/pasteboard.dart'; | ||||
| import 'package:styled_widget/styled_widget.dart'; | ||||
| import 'package:super_sliver_list/super_sliver_list.dart'; | ||||
| import 'package:uuid/uuid.dart'; | ||||
| import 'package:material_symbols_icons/symbols.dart'; | ||||
| import 'package:riverpod_annotation/riverpod_annotation.dart'; | ||||
| import 'package:super_clipboard/super_clipboard.dart'; | ||||
| import 'chat.dart'; | ||||
| import 'package:island/widgets/chat/call_button.dart'; | ||||
|  | ||||
| part 'room.g.dart'; | ||||
|  | ||||
| @@ -115,19 +118,12 @@ class MessagesNotifier extends _$MessagesNotifier { | ||||
|         messageRepositoryProvider(_roomId).future, | ||||
|       ); | ||||
|       final baseUrl = ref.read(serverUrlProvider); | ||||
|       final atk = await getFreshAtk( | ||||
|         ref.watch(tokenPairProvider), | ||||
|         baseUrl, | ||||
|         onRefreshed: (atk, rtk) { | ||||
|           setTokenPair(ref.watch(sharedPreferencesProvider), atk, rtk); | ||||
|           ref.invalidate(tokenPairProvider); | ||||
|         }, | ||||
|       ); | ||||
|       if (atk == null) throw ArgumentError('Access token is null'); | ||||
|       final token = await getToken(ref.watch(tokenProvider)); | ||||
|       if (token == null) throw ArgumentError('Access token is null'); | ||||
|  | ||||
|       final currentMessages = state.value ?? []; | ||||
|       await repository.sendMessage( | ||||
|         atk, | ||||
|         token, | ||||
|         baseUrl, | ||||
|         _roomId, | ||||
|         content, | ||||
| @@ -325,6 +321,46 @@ class ChatRoomScreen extends HookConsumerWidget { | ||||
|       ); | ||||
|     } | ||||
|  | ||||
|     // Members who are typing | ||||
|     final typingStatuses = useState<List<SnChatMember>>([]); | ||||
|     final typingDebouncer = useState<Timer?>(null); | ||||
|  | ||||
|     void sendTypingStatus() { | ||||
|       // Don't send if we're already in a cooldown period | ||||
|       if (typingDebouncer.value != null) return; | ||||
|  | ||||
|       // Send typing status immediately | ||||
|       final wsState = ref.read(websocketStateProvider.notifier); | ||||
|       wsState.sendMessage( | ||||
|         jsonEncode( | ||||
|           WebSocketPacket(type: 'messages.typing', data: {'chat_room_id': id}), | ||||
|         ), | ||||
|       ); | ||||
|  | ||||
|       typingDebouncer.value = Timer(const Duration(milliseconds: 850), () { | ||||
|         typingDebouncer.value = null; | ||||
|       }); | ||||
|     } | ||||
|  | ||||
|     // Add timer to remove typing status after inactivity | ||||
|     useEffect(() { | ||||
|       final removeTypingTimer = Timer.periodic(const Duration(seconds: 5), (_) { | ||||
|         if (typingStatuses.value.isNotEmpty) { | ||||
|           // Remove typing statuses older than 5 seconds | ||||
|           final now = DateTime.now(); | ||||
|           typingStatuses.value = | ||||
|               typingStatuses.value.where((member) { | ||||
|                 final lastTyped = | ||||
|                     member.lastTyped ?? | ||||
|                     DateTime.now().subtract(const Duration(milliseconds: 1350)); | ||||
|                 return now.difference(lastTyped).inSeconds < 5; | ||||
|               }).toList(); | ||||
|         } | ||||
|       }); | ||||
|  | ||||
|       return () => removeTypingTimer.cancel(); | ||||
|     }, []); | ||||
|  | ||||
|     var isLoading = false; | ||||
|  | ||||
|     // Add scroll listener for pagination | ||||
| @@ -347,10 +383,39 @@ class ChatRoomScreen extends HookConsumerWidget { | ||||
|       void onMessage(WebSocketPacket pkt) { | ||||
|         if (!pkt.type.startsWith('messages')) return; | ||||
|         if (['messages.read'].contains(pkt.type)) return; | ||||
|  | ||||
|         if (pkt.type == 'messages.typing' && pkt.data?['sender'] != null) { | ||||
|           if (pkt.data?['room_id'] != chatRoom.value?.id) return; | ||||
|           if (pkt.data?['sender_id'] == chatIdentity.value?.id) return; | ||||
|  | ||||
|           final sender = SnChatMember.fromJson( | ||||
|             pkt.data?['sender'], | ||||
|           ).copyWith(lastTyped: DateTime.now()); | ||||
|  | ||||
|           // Check if the sender is already in the typing list | ||||
|           final existingIndex = typingStatuses.value.indexWhere( | ||||
|             (member) => member.id == sender.id, | ||||
|           ); | ||||
|           if (existingIndex >= 0) { | ||||
|             // Update the existing entry with new timestamp | ||||
|             final updatedList = [...typingStatuses.value]; | ||||
|             updatedList[existingIndex] = sender; | ||||
|             typingStatuses.value = updatedList; | ||||
|           } else { | ||||
|             // Add new typing status | ||||
|             typingStatuses.value = [...typingStatuses.value, sender]; | ||||
|           } | ||||
|           return; | ||||
|         } | ||||
|  | ||||
|         final message = SnChatMessage.fromJson(pkt.data!); | ||||
|         if (message.chatRoomId != chatRoom.value?.id) return; | ||||
|         switch (pkt.type) { | ||||
|           case 'messages.new': | ||||
|             if (message.type.startsWith('call')) { | ||||
|               // Handle the ongoing call. | ||||
|               ref.invalidate(ongoingCallProvider(message.chatRoomId)); | ||||
|             } | ||||
|             messagesNotifier.receiveMessage(message); | ||||
|             // Send read receipt for new message | ||||
|             sendReadReceipt(); | ||||
| @@ -416,8 +481,22 @@ class ChatRoomScreen extends HookConsumerWidget { | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     // Add listener to message controller for typing status | ||||
|     useEffect(() { | ||||
|       void onTextChange() { | ||||
|         if (messageController.text.isNotEmpty) { | ||||
|           sendTypingStatus(); | ||||
|         } | ||||
|       } | ||||
|  | ||||
|       messageController.addListener(onTextChange); | ||||
|       return () => messageController.removeListener(onTextChange); | ||||
|     }, [messageController]); | ||||
|  | ||||
|     final compactHeader = isWideScreen(context); | ||||
|  | ||||
|     final listController = useMemoized(() => ListController(), []); | ||||
|  | ||||
|     return AppScaffold( | ||||
|       appBar: AppBar( | ||||
|         leading: !compactHeader ? const Center(child: PageBackButton()) : null, | ||||
| @@ -435,19 +514,23 @@ class ChatRoomScreen extends HookConsumerWidget { | ||||
|                             height: 26, | ||||
|                             width: 26, | ||||
|                             child: | ||||
|                                 (room!.type == 1 && room.pictureId == null) | ||||
|                                 (room!.type == 1 && room.picture?.id == null) | ||||
|                                     ? SplitAvatarWidget( | ||||
|                                       filesId: | ||||
|                                           room.members! | ||||
|                                               .map( | ||||
|                                                 (e) => | ||||
|                                                     e.account.profile.pictureId, | ||||
|                                                     e | ||||
|                                                         .account | ||||
|                                                         .profile | ||||
|                                                         .picture | ||||
|                                                         ?.id, | ||||
|                                               ) | ||||
|                                               .toList(), | ||||
|                                     ) | ||||
|                                     : room.pictureId != null | ||||
|                                     : room.picture?.id != null | ||||
|                                     ? ProfilePictureWidget( | ||||
|                                       fileId: room.pictureId, | ||||
|                                       fileId: room.picture?.id, | ||||
|                                       fallbackIcon: Symbols.chat, | ||||
|                                     ) | ||||
|                                     : CircleAvatar( | ||||
| @@ -475,19 +558,23 @@ class ChatRoomScreen extends HookConsumerWidget { | ||||
|                             height: 26, | ||||
|                             width: 26, | ||||
|                             child: | ||||
|                                 (room!.type == 1 && room.pictureId == null) | ||||
|                                 (room!.type == 1 && room.picture?.id == null) | ||||
|                                     ? SplitAvatarWidget( | ||||
|                                       filesId: | ||||
|                                           room.members! | ||||
|                                               .map( | ||||
|                                                 (e) => | ||||
|                                                     e.account.profile.pictureId, | ||||
|                                                     e | ||||
|                                                         .account | ||||
|                                                         .profile | ||||
|                                                         .picture | ||||
|                                                         ?.id, | ||||
|                                               ) | ||||
|                                               .toList(), | ||||
|                                     ) | ||||
|                                     : room.pictureId != null | ||||
|                                     : room.picture?.id != null | ||||
|                                     ? ProfilePictureWidget( | ||||
|                                       fileId: room.pictureId, | ||||
|                                       fileId: room.picture?.id, | ||||
|                                       fallbackIcon: Symbols.chat, | ||||
|                                     ) | ||||
|                                     : CircleAvatar( | ||||
| @@ -508,18 +595,13 @@ class ChatRoomScreen extends HookConsumerWidget { | ||||
|                       ), | ||||
|           loading: () => const Text('Loading...'), | ||||
|           error: | ||||
|               (err, __) => ResponseErrorWidget( | ||||
|               (err, _) => ResponseErrorWidget( | ||||
|                 error: err, | ||||
|                 onRetry: () => messagesNotifier.loadInitial(), | ||||
|               ), | ||||
|         ), | ||||
|         actions: [ | ||||
|           IconButton( | ||||
|             icon: const Icon(Symbols.video_call), | ||||
|             onPressed: () { | ||||
|               showInfoAlert('Oops', 'Not implemented yet...'); | ||||
|             }, | ||||
|           ), | ||||
|           AudioCallButton(roomId: id), | ||||
|           IconButton( | ||||
|             icon: const Icon(Icons.more_vert), | ||||
|             onPressed: () { | ||||
| @@ -529,152 +611,265 @@ class ChatRoomScreen extends HookConsumerWidget { | ||||
|           const Gap(8), | ||||
|         ], | ||||
|       ), | ||||
|       body: Column( | ||||
|       body: Stack( | ||||
|         children: [ | ||||
|           Expanded( | ||||
|             child: messages.when( | ||||
|               data: | ||||
|                   (messageList) => | ||||
|                       messageList.isEmpty | ||||
|                           ? Center(child: Text('No messages yet'.tr())) | ||||
|                           : SuperListView.builder( | ||||
|                             padding: EdgeInsets.symmetric(vertical: 16), | ||||
|                             controller: scrollController, | ||||
|                             reverse: true, // Show newest messages at the bottom | ||||
|                             itemCount: messageList.length, | ||||
|                             itemBuilder: (context, index) { | ||||
|                               final message = messageList[index]; | ||||
|                               final nextMessage = | ||||
|                                   index < messageList.length - 1 | ||||
|                                       ? messageList[index + 1] | ||||
|                                       : null; | ||||
|                               final isLastInGroup = | ||||
|                                   nextMessage == null || | ||||
|                                   nextMessage.senderId != message.senderId || | ||||
|                                   nextMessage.createdAt | ||||
|                                           .difference(message.createdAt) | ||||
|                                           .inMinutes | ||||
|                                           .abs() > | ||||
|                                       3; | ||||
|           Column( | ||||
|             children: [ | ||||
|               Expanded( | ||||
|                 child: messages.when( | ||||
|                   data: | ||||
|                       (messageList) => | ||||
|                           messageList.isEmpty | ||||
|                               ? Center(child: Text('No messages yet'.tr())) | ||||
|                               : SuperListView.builder( | ||||
|                                 listController: listController, | ||||
|                                 padding: EdgeInsets.symmetric(vertical: 16), | ||||
|                                 controller: scrollController, | ||||
|                                 reverse: | ||||
|                                     true, // Show newest messages at the bottom | ||||
|                                 itemCount: messageList.length, | ||||
|                                 findChildIndexCallback: (key) { | ||||
|                                   final valueKey = key as ValueKey; | ||||
|                                   final messageId = valueKey.value as String; | ||||
|                                   return messageList.indexWhere( | ||||
|                                     (m) => m.id == messageId, | ||||
|                                   ); | ||||
|                                 }, | ||||
|                                 itemBuilder: (context, index) { | ||||
|                                   final message = messageList[index]; | ||||
|                                   final nextMessage = | ||||
|                                       index < messageList.length - 1 | ||||
|                                           ? messageList[index + 1] | ||||
|                                           : null; | ||||
|                                   final isLastInGroup = | ||||
|                                       nextMessage == null || | ||||
|                                       nextMessage.senderId != | ||||
|                                           message.senderId || | ||||
|                                       nextMessage.createdAt | ||||
|                                               .difference(message.createdAt) | ||||
|                                               .inMinutes | ||||
|                                               .abs() > | ||||
|                                           3; | ||||
|  | ||||
|                               return chatIdentity.when( | ||||
|                                 skipError: true, | ||||
|                                 data: | ||||
|                                     (identity) => MessageItem( | ||||
|                                       message: message, | ||||
|                                       isCurrentUser: | ||||
|                                           identity?.id == message.senderId, | ||||
|                                       onAction: (action) { | ||||
|                                         switch (action) { | ||||
|                                           case MessageItemAction.delete: | ||||
|                                             messagesNotifier.deleteMessage( | ||||
|                                               message.id, | ||||
|                                   return chatIdentity.when( | ||||
|                                     skipError: true, | ||||
|                                     data: | ||||
|                                         (identity) => MessageItem( | ||||
|                                           message: message, | ||||
|                                           isCurrentUser: | ||||
|                                               identity?.id == message.senderId, | ||||
|                                           onAction: (action) { | ||||
|                                             switch (action) { | ||||
|                                               case MessageItemAction.delete: | ||||
|                                                 messagesNotifier.deleteMessage( | ||||
|                                                   message.id, | ||||
|                                                 ); | ||||
|                                               case MessageItemAction.edit: | ||||
|                                                 messageEditingTo.value = | ||||
|                                                     message.toRemoteMessage(); | ||||
|                                                 messageController.text = | ||||
|                                                     messageEditingTo | ||||
|                                                         .value | ||||
|                                                         ?.content ?? | ||||
|                                                     ''; | ||||
|                                                 attachments.value = | ||||
|                                                     messageEditingTo | ||||
|                                                         .value! | ||||
|                                                         .attachments | ||||
|                                                         .map( | ||||
|                                                           (e) => | ||||
|                                                               UniversalFile.fromAttachment( | ||||
|                                                                 e, | ||||
|                                                               ), | ||||
|                                                         ) | ||||
|                                                         .toList(); | ||||
|                                               case MessageItemAction.forward: | ||||
|                                                 messageForwardingTo.value = | ||||
|                                                     message.toRemoteMessage(); | ||||
|                                               case MessageItemAction.reply: | ||||
|                                                 messageReplyingTo.value = | ||||
|                                                     message.toRemoteMessage(); | ||||
|                                             } | ||||
|                                           }, | ||||
|                                           onJump: (messageId) { | ||||
|                                             final messageIndex = messageList | ||||
|                                                 .indexWhere( | ||||
|                                                   (m) => m.id == messageId, | ||||
|                                                 ); | ||||
|                                             listController.jumpToItem( | ||||
|                                               index: messageIndex, | ||||
|                                               scrollController: | ||||
|                                                   scrollController, | ||||
|                                               alignment: 0.5, | ||||
|                                             ); | ||||
|                                           case MessageItemAction.edit: | ||||
|                                             messageEditingTo.value = | ||||
|                                                 message.toRemoteMessage(); | ||||
|                                             messageController.text = | ||||
|                                                 messageEditingTo | ||||
|                                                     .value | ||||
|                                                     ?.content ?? | ||||
|                                                 ''; | ||||
|                                             attachments.value = | ||||
|                                                 messageEditingTo | ||||
|                                                     .value! | ||||
|                                                     .attachments | ||||
|                                                     .map( | ||||
|                                                       (e) => | ||||
|                                                           UniversalFile.fromAttachment( | ||||
|                                                             e, | ||||
|                                                           ), | ||||
|                                                     ) | ||||
|                                                     .toList(); | ||||
|                                           case MessageItemAction.forward: | ||||
|                                             messageForwardingTo.value = | ||||
|                                                 message.toRemoteMessage(); | ||||
|                                           case MessageItemAction.reply: | ||||
|                                             messageReplyingTo.value = | ||||
|                                                 message.toRemoteMessage(); | ||||
|                                         } | ||||
|                                       }, | ||||
|                                       progress: | ||||
|                                           attachmentProgress.value[message.id], | ||||
|                                       showAvatar: isLastInGroup, | ||||
|                                     ), | ||||
|                                 loading: | ||||
|                                     () => MessageItem( | ||||
|                                       message: message, | ||||
|                                       isCurrentUser: false, | ||||
|                                       onAction: null, | ||||
|                                       progress: null, | ||||
|                                       showAvatar: false, | ||||
|                                     ), | ||||
|                                 error: (_, __) => const SizedBox.shrink(), | ||||
|                               ); | ||||
|                             }, | ||||
|                           ), | ||||
|               loading: () => const Center(child: CircularProgressIndicator()), | ||||
|               error: | ||||
|                   (error, _) => ResponseErrorWidget( | ||||
|                     error: error, | ||||
|                     onRetry: () => messagesNotifier.loadInitial(), | ||||
|                   ), | ||||
|             ), | ||||
|           ), | ||||
|           chatRoom.when( | ||||
|             data: | ||||
|                 (room) => _ChatInput( | ||||
|                   messageController: messageController, | ||||
|                   chatRoom: room!, | ||||
|                   onSend: sendMessage, | ||||
|                   onClear: () { | ||||
|                     if (messageEditingTo.value != null) { | ||||
|                       attachments.value.clear(); | ||||
|                       messageController.clear(); | ||||
|                     } | ||||
|                     messageEditingTo.value = null; | ||||
|                     messageReplyingTo.value = null; | ||||
|                     messageForwardingTo.value = null; | ||||
|                   }, | ||||
|                   messageEditingTo: messageEditingTo.value, | ||||
|                   messageReplyingTo: messageReplyingTo.value, | ||||
|                   messageForwardingTo: messageForwardingTo.value, | ||||
|                   onPickFile: (bool isPhoto) { | ||||
|                     if (isPhoto) { | ||||
|                       pickPhotoMedia(); | ||||
|                     } else { | ||||
|                       pickVideoMedia(); | ||||
|                     } | ||||
|                   }, | ||||
|                   attachments: attachments.value, | ||||
|                   onUploadAttachment: (_) { | ||||
|                     // not going to do anything, only upload when send the message | ||||
|                   }, | ||||
|                   onDeleteAttachment: (index) async { | ||||
|                     final attachment = attachments.value[index]; | ||||
|                     if (attachment.isOnCloud) { | ||||
|                       final client = ref.watch(apiClientProvider); | ||||
|                       await client.delete('/files/${attachment.data.id}'); | ||||
|                     } | ||||
|                     final clone = List.of(attachments.value); | ||||
|                     clone.removeAt(index); | ||||
|                     attachments.value = clone; | ||||
|                   }, | ||||
|                   onMoveAttachment: (idx, delta) { | ||||
|                     if (idx + delta < 0 || | ||||
|                         idx + delta >= attachments.value.length) { | ||||
|                       return; | ||||
|                     } | ||||
|                     final clone = List.of(attachments.value); | ||||
|                     clone.insert(idx + delta, clone.removeAt(idx)); | ||||
|                     attachments.value = clone; | ||||
|                   }, | ||||
|                   onAttachmentsChanged: (newAttachments) { | ||||
|                     attachments.value = newAttachments; | ||||
|                   }, | ||||
|                                           }, | ||||
|                                           progress: | ||||
|                                               attachmentProgress.value[message | ||||
|                                                   .id], | ||||
|                                           showAvatar: isLastInGroup, | ||||
|                                         ), | ||||
|                                     loading: | ||||
|                                         () => MessageItem( | ||||
|                                           message: message, | ||||
|                                           isCurrentUser: false, | ||||
|                                           onAction: null, | ||||
|                                           progress: null, | ||||
|                                           showAvatar: false, | ||||
|                                           onJump: (_) {}, | ||||
|                                         ), | ||||
|                                     error: (_, _) => const SizedBox.shrink(), | ||||
|                                   ); | ||||
|                                 }, | ||||
|                               ), | ||||
|                   loading: | ||||
|                       () => const Center(child: CircularProgressIndicator()), | ||||
|                   error: | ||||
|                       (error, _) => ResponseErrorWidget( | ||||
|                         error: error, | ||||
|                         onRetry: () => messagesNotifier.loadInitial(), | ||||
|                       ), | ||||
|                 ), | ||||
|             error: (_, __) => const SizedBox.shrink(), | ||||
|             loading: () => const SizedBox.shrink(), | ||||
|               ), | ||||
|               chatRoom.when( | ||||
|                 data: | ||||
|                     (room) => Column( | ||||
|                       mainAxisSize: MainAxisSize.min, | ||||
|                       children: [ | ||||
|                         AnimatedSwitcher( | ||||
|                           duration: const Duration(milliseconds: 150), | ||||
|                           switchInCurve: Curves.fastEaseInToSlowEaseOut, | ||||
|                           switchOutCurve: Curves.fastEaseInToSlowEaseOut, | ||||
|                           transitionBuilder: ( | ||||
|                             Widget child, | ||||
|                             Animation<double> animation, | ||||
|                           ) { | ||||
|                             return SlideTransition( | ||||
|                               position: Tween<Offset>( | ||||
|                                 begin: const Offset(0, -0.3), | ||||
|                                 end: Offset.zero, | ||||
|                               ).animate( | ||||
|                                 CurvedAnimation( | ||||
|                                   parent: animation, | ||||
|                                   curve: Curves.easeOutCubic, | ||||
|                                 ), | ||||
|                               ), | ||||
|                               child: SizeTransition( | ||||
|                                 sizeFactor: animation, | ||||
|                                 axisAlignment: -1.0, | ||||
|                                 child: FadeTransition( | ||||
|                                   opacity: animation, | ||||
|                                   child: child, | ||||
|                                 ), | ||||
|                               ), | ||||
|                             ); | ||||
|                           }, | ||||
|                           child: | ||||
|                               typingStatuses.value.isNotEmpty | ||||
|                                   ? Container( | ||||
|                                     key: const ValueKey('typing-indicator'), | ||||
|                                     width: double.infinity, | ||||
|                                     padding: const EdgeInsets.symmetric( | ||||
|                                       horizontal: 16, | ||||
|                                       vertical: 4, | ||||
|                                     ), | ||||
|                                     child: Row( | ||||
|                                       children: [ | ||||
|                                         const Icon( | ||||
|                                           Symbols.more_horiz, | ||||
|                                           size: 16, | ||||
|                                         ).padding(horizontal: 8), | ||||
|                                         const Gap(8), | ||||
|                                         Expanded( | ||||
|                                           child: Text( | ||||
|                                             'typingHint'.plural( | ||||
|                                               typingStatuses.value.length, | ||||
|                                               args: [ | ||||
|                                                 typingStatuses.value | ||||
|                                                     .map( | ||||
|                                                       (x) => | ||||
|                                                           x.nick ?? | ||||
|                                                           x.account.nick, | ||||
|                                                     ) | ||||
|                                                     .join(', '), | ||||
|                                               ], | ||||
|                                             ), | ||||
|                                             style: | ||||
|                                                 Theme.of( | ||||
|                                                   context, | ||||
|                                                 ).textTheme.bodySmall, | ||||
|                                           ), | ||||
|                                         ), | ||||
|                                       ], | ||||
|                                     ), | ||||
|                                   ) | ||||
|                                   : const SizedBox.shrink( | ||||
|                                     key: ValueKey('typing-indicator-none'), | ||||
|                                   ), | ||||
|                         ), | ||||
|                         _ChatInput( | ||||
|                           messageController: messageController, | ||||
|                           chatRoom: room!, | ||||
|                           onSend: sendMessage, | ||||
|                           onClear: () { | ||||
|                             if (messageEditingTo.value != null) { | ||||
|                               attachments.value.clear(); | ||||
|                               messageController.clear(); | ||||
|                             } | ||||
|                             messageEditingTo.value = null; | ||||
|                             messageReplyingTo.value = null; | ||||
|                             messageForwardingTo.value = null; | ||||
|                           }, | ||||
|                           messageEditingTo: messageEditingTo.value, | ||||
|                           messageReplyingTo: messageReplyingTo.value, | ||||
|                           messageForwardingTo: messageForwardingTo.value, | ||||
|                           onPickFile: (bool isPhoto) { | ||||
|                             if (isPhoto) { | ||||
|                               pickPhotoMedia(); | ||||
|                             } else { | ||||
|                               pickVideoMedia(); | ||||
|                             } | ||||
|                           }, | ||||
|                           attachments: attachments.value, | ||||
|                           onUploadAttachment: (_) { | ||||
|                             // not going to do anything, only upload when send the message | ||||
|                           }, | ||||
|                           onDeleteAttachment: (index) async { | ||||
|                             final attachment = attachments.value[index]; | ||||
|                             if (attachment.isOnCloud) { | ||||
|                               final client = ref.watch(apiClientProvider); | ||||
|                               await client.delete( | ||||
|                                 '/files/${attachment.data.id}', | ||||
|                               ); | ||||
|                             } | ||||
|                             final clone = List.of(attachments.value); | ||||
|                             clone.removeAt(index); | ||||
|                             attachments.value = clone; | ||||
|                           }, | ||||
|                           onMoveAttachment: (idx, delta) { | ||||
|                             if (idx + delta < 0 || | ||||
|                                 idx + delta >= attachments.value.length) { | ||||
|                               return; | ||||
|                             } | ||||
|                             final clone = List.of(attachments.value); | ||||
|                             clone.insert(idx + delta, clone.removeAt(idx)); | ||||
|                             attachments.value = clone; | ||||
|                           }, | ||||
|                           onAttachmentsChanged: (newAttachments) { | ||||
|                             attachments.value = newAttachments; | ||||
|                           }, | ||||
|                         ), | ||||
|                       ], | ||||
|                     ), | ||||
|                 error: (_, _) => const SizedBox.shrink(), | ||||
|                 loading: () => const SizedBox.shrink(), | ||||
|               ), | ||||
|             ], | ||||
|           ), | ||||
|           Positioned( | ||||
|             left: 0, | ||||
|             right: 0, | ||||
|             top: 0, | ||||
|             child: CallOverlayBar().padding(horizontal: 8, top: 12), | ||||
|           ), | ||||
|         ], | ||||
|       ), | ||||
| @@ -682,7 +877,7 @@ class ChatRoomScreen extends HookConsumerWidget { | ||||
|   } | ||||
| } | ||||
|  | ||||
| class _ChatInput extends ConsumerWidget { | ||||
| class _ChatInput extends HookConsumerWidget { | ||||
|   final TextEditingController messageController; | ||||
|   final SnChatRoom chatRoom; | ||||
|   final VoidCallback onSend; | ||||
| @@ -713,62 +908,58 @@ class _ChatInput extends ConsumerWidget { | ||||
|     required this.onAttachmentsChanged, | ||||
|   }); | ||||
|  | ||||
|   void _handleKeyPress(BuildContext context, WidgetRef ref, RawKeyEvent event) { | ||||
|     if (event is! RawKeyDownEvent) return; | ||||
|  | ||||
|     final isPaste = event.logicalKey == LogicalKeyboardKey.keyV; | ||||
|     final isModifierPressed = event.isMetaPressed || event.isControlPressed; | ||||
|  | ||||
|     if (isPaste && isModifierPressed) { | ||||
|       _handlePaste(); | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     final enterToSend = ref.read(appSettingsProvider).enterToSend; | ||||
|     final isEnter = event.logicalKey == LogicalKeyboardKey.enter; | ||||
|  | ||||
|     if (isEnter) { | ||||
|       if (enterToSend && !isModifierPressed) { | ||||
|         onSend(); | ||||
|       } else if (!enterToSend && isModifierPressed) { | ||||
|         onSend(); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   Future<void> _handlePaste() async { | ||||
|     final clipboard = SystemClipboard.instance; | ||||
|     if (clipboard == null) return; | ||||
|  | ||||
|     final reader = await clipboard.read(); | ||||
|     if (reader.canProvide(Formats.png)) { | ||||
|       reader.getFile(Formats.png, (file) async { | ||||
|         final stream = file.getStream(); | ||||
|         final bytes = await stream.toList(); | ||||
|         final imageBytes = bytes.expand((e) => e).toList(); | ||||
|  | ||||
|         // Create a temporary file to store the image | ||||
|         final tempDir = Directory.systemTemp; | ||||
|         final tempFile = File( | ||||
|           '${tempDir.path}/pasted_image_${DateTime.now().millisecondsSinceEpoch}.png', | ||||
|         ); | ||||
|         await tempFile.writeAsBytes(imageBytes); | ||||
|  | ||||
|         // Add the file to attachments | ||||
|         onAttachmentsChanged([ | ||||
|           ...attachments, | ||||
|           UniversalFile( | ||||
|             data: XFile(tempFile.path), | ||||
|             type: UniversalFileType.image, | ||||
|           ), | ||||
|         ]); | ||||
|       }); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context, WidgetRef ref) { | ||||
|     final enterToSend = ref.watch(appSettingsProvider).enterToSend; | ||||
|     final inputFocusNode = useFocusNode(); | ||||
|  | ||||
|     final enterToSend = ref.watch(appSettingsNotifierProvider).enterToSend; | ||||
|  | ||||
|     final isMobile = !kIsWeb && (Platform.isAndroid || Platform.isIOS); | ||||
|  | ||||
|     void send() { | ||||
|       inputFocusNode.requestFocus(); | ||||
|       onSend.call(); | ||||
|     } | ||||
|  | ||||
|     Future<void> handlePaste() async { | ||||
|       final clipboard = await Pasteboard.image; | ||||
|       if (clipboard == null) return; | ||||
|  | ||||
|       onAttachmentsChanged([ | ||||
|         ...attachments, | ||||
|         UniversalFile( | ||||
|           data: XFile.fromData(clipboard, mimeType: "image/jpeg"), | ||||
|           type: UniversalFileType.image, | ||||
|         ), | ||||
|       ]); | ||||
|     } | ||||
|  | ||||
|     void handleKeyPress( | ||||
|       BuildContext context, | ||||
|       WidgetRef ref, | ||||
|       RawKeyEvent event, | ||||
|     ) { | ||||
|       if (event is! RawKeyDownEvent) return; | ||||
|  | ||||
|       final isPaste = event.logicalKey == LogicalKeyboardKey.keyV; | ||||
|       final isModifierPressed = event.isMetaPressed || event.isControlPressed; | ||||
|  | ||||
|       if (isPaste && isModifierPressed) { | ||||
|         handlePaste(); | ||||
|         return; | ||||
|       } | ||||
|  | ||||
|       final enterToSend = ref.read(appSettingsNotifierProvider).enterToSend; | ||||
|       final isEnter = event.logicalKey == LogicalKeyboardKey.enter; | ||||
|  | ||||
|       if (isEnter) { | ||||
|         if (enterToSend && !isModifierPressed) { | ||||
|           send(); | ||||
|         } else if (!enterToSend && isModifierPressed) { | ||||
|           send(); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     return Material( | ||||
|       elevation: 8, | ||||
| @@ -790,7 +981,7 @@ class _ChatInput extends ConsumerWidget { | ||||
|                     onMove: (delta) => onMoveAttachment(idx, delta), | ||||
|                   ); | ||||
|                 }, | ||||
|                 separatorBuilder: (_, __) => const Gap(8), | ||||
|                 separatorBuilder: (_, _) => const Gap(8), | ||||
|               ), | ||||
|             ).padding(top: 12), | ||||
|           if (messageReplyingTo != null || | ||||
| @@ -871,11 +1062,23 @@ class _ChatInput extends ConsumerWidget { | ||||
|                 Expanded( | ||||
|                   child: RawKeyboardListener( | ||||
|                     focusNode: FocusNode(), | ||||
|                     onKey: (event) => _handleKeyPress(context, ref, event), | ||||
|                     onKey: (event) => handleKeyPress(context, ref, event), | ||||
|                     child: TextField( | ||||
|                       focusNode: inputFocusNode, | ||||
|                       controller: messageController, | ||||
|                       onSubmitted: | ||||
|                           (enterToSend && isMobile) | ||||
|                               ? (_) { | ||||
|                                 send(); | ||||
|                               } | ||||
|                               : null, | ||||
|                       keyboardType: | ||||
|                           (enterToSend && isMobile) | ||||
|                               ? TextInputType.text | ||||
|                               : TextInputType.multiline, | ||||
|                       textInputAction: TextInputAction.send, | ||||
|                       inputFormatters: [ | ||||
|                         if (enterToSend) | ||||
|                         if (enterToSend && !isMobile) | ||||
|                           TextInputFormatter.withFunction((oldValue, newValue) { | ||||
|                             if (newValue.text.endsWith('\n')) { | ||||
|                               return oldValue; | ||||
| @@ -910,7 +1113,7 @@ class _ChatInput extends ConsumerWidget { | ||||
|                 IconButton( | ||||
|                   icon: const Icon(Icons.send), | ||||
|                   color: Theme.of(context).colorScheme.primary, | ||||
|                   onPressed: onSend, | ||||
|                   onPressed: send, | ||||
|                 ), | ||||
|               ], | ||||
|             ).padding(bottom: MediaQuery.of(context).padding.bottom), | ||||
|   | ||||
| @@ -6,7 +6,7 @@ part of 'room.dart'; | ||||
| // RiverpodGenerator | ||||
| // ************************************************************************** | ||||
|  | ||||
| String _$messagesNotifierHash() => r'71a9fc1c6d024f6203f06225384c19335b9b6f2c'; | ||||
| String _$messagesNotifierHash() => r'afc4d43f4948ec571118cef0321838a6cefc89c0'; | ||||
|  | ||||
| /// Copied from Dart SDK | ||||
| class _SystemHash { | ||||
|   | ||||
| @@ -14,10 +14,14 @@ import 'package:island/widgets/account/account_picker.dart'; | ||||
| import 'package:island/widgets/alert.dart'; | ||||
| import 'package:island/widgets/app_scaffold.dart'; | ||||
| import 'package:island/widgets/content/cloud_files.dart'; | ||||
| import 'package:island/widgets/content/sheet.dart'; | ||||
| import 'package:material_symbols_icons/symbols.dart'; | ||||
| import 'package:riverpod_annotation/riverpod_annotation.dart'; | ||||
| import 'package:riverpod_paging_utils/riverpod_paging_utils.dart'; | ||||
| import 'package:styled_widget/styled_widget.dart'; | ||||
|  | ||||
| part 'room_detail.freezed.dart'; | ||||
| part 'room_detail.g.dart'; | ||||
|  | ||||
| @RoutePage() | ||||
| class ChatDetailScreen extends HookConsumerWidget { | ||||
| @@ -27,6 +31,206 @@ class ChatDetailScreen extends HookConsumerWidget { | ||||
|   @override | ||||
|   Widget build(BuildContext context, WidgetRef ref) { | ||||
|     final roomState = ref.watch(chatroomProvider(id)); | ||||
|     final roomIdentity = ref.watch(chatroomIdentityProvider(id)); | ||||
|  | ||||
|     const kNotifyLevelText = [ | ||||
|       'chatNotifyLevelAll', | ||||
|       'chatNotifyLevelMention', | ||||
|       'chatNotifyLevelNone', | ||||
|     ]; | ||||
|  | ||||
|     void setNotifyLevel(int level) async { | ||||
|       try { | ||||
|         final client = ref.watch(apiClientProvider); | ||||
|         await client.patch( | ||||
|           '/chat/$id/members/me/notify', | ||||
|           data: {'notify_level': level}, | ||||
|         ); | ||||
|         ref.invalidate(chatroomIdentityProvider(id)); | ||||
|         if (context.mounted) { | ||||
|           showSnackBar( | ||||
|             context, | ||||
|             'chatNotifyLevelUpdated'.tr(args: [kNotifyLevelText[level].tr()]), | ||||
|           ); | ||||
|         } | ||||
|       } catch (err) { | ||||
|         showErrorAlert(err); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     void setChatBreak(DateTime until) async { | ||||
|       try { | ||||
|         final client = ref.watch(apiClientProvider); | ||||
|         await client.patch( | ||||
|           '/chat/$id/members/me/notify', | ||||
|           data: {'break_until': until.toUtc().toIso8601String()}, | ||||
|         ); | ||||
|         ref.invalidate(chatroomProvider(id)); | ||||
|       } catch (err) { | ||||
|         showErrorAlert(err); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     void showNotifyLevelBottomSheet(SnChatMember identity) { | ||||
|       showModalBottomSheet( | ||||
|         isScrollControlled: true, | ||||
|         context: context, | ||||
|         builder: | ||||
|             (context) => SheetScaffold( | ||||
|               height: 320, | ||||
|               titleText: 'chatNotifyLevel'.tr(), | ||||
|               child: Column( | ||||
|                 mainAxisSize: MainAxisSize.min, | ||||
|                 children: [ | ||||
|                   ListTile( | ||||
|                     title: const Text('chatNotifyLevelAll').tr(), | ||||
|                     subtitle: const Text('chatNotifyLevelDescription').tr(), | ||||
|                     leading: const Icon(Icons.notifications_active), | ||||
|                     selected: identity.notify == 0, | ||||
|                     onTap: () { | ||||
|                       setNotifyLevel(0); | ||||
|                       Navigator.pop(context); | ||||
|                     }, | ||||
|                   ), | ||||
|                   ListTile( | ||||
|                     title: const Text('chatNotifyLevelMention').tr(), | ||||
|                     subtitle: const Text('chatNotifyLevelDescription').tr(), | ||||
|                     leading: const Icon(Icons.alternate_email), | ||||
|                     selected: identity.notify == 1, | ||||
|                     onTap: () { | ||||
|                       setNotifyLevel(1); | ||||
|                       Navigator.pop(context); | ||||
|                     }, | ||||
|                   ), | ||||
|                   ListTile( | ||||
|                     title: const Text('chatNotifyLevelNone').tr(), | ||||
|                     subtitle: const Text('chatNotifyLevelDescription').tr(), | ||||
|                     leading: const Icon(Icons.notifications_off), | ||||
|                     selected: identity.notify == 2, | ||||
|                     onTap: () { | ||||
|                       setNotifyLevel(2); | ||||
|                       Navigator.pop(context); | ||||
|                     }, | ||||
|                   ), | ||||
|                 ], | ||||
|               ), | ||||
|             ), | ||||
|       ); | ||||
|     } | ||||
|  | ||||
|     void showChatBreakDialog() { | ||||
|       final now = DateTime.now(); | ||||
|       final durationController = TextEditingController(); | ||||
|  | ||||
|       showDialog( | ||||
|         context: context, | ||||
|         builder: | ||||
|             (context) => AlertDialog( | ||||
|               title: const Text('chatBreak').tr(), | ||||
|               content: Column( | ||||
|                 mainAxisSize: MainAxisSize.min, | ||||
|                 children: [ | ||||
|                   const Text('chatBreakDescription').tr(), | ||||
|                   const Gap(16), | ||||
|                   ListTile( | ||||
|                     title: const Text('Clear').tr(), | ||||
|                     subtitle: const Text('chatBreakClear').tr(), | ||||
|                     leading: const Icon(Icons.notifications_active), | ||||
|                     onTap: () { | ||||
|                       setChatBreak(now); | ||||
|                       Navigator.pop(context); | ||||
|                       if (context.mounted) { | ||||
|                         showSnackBar(context, 'chatBreakCleared'.tr()); | ||||
|                       } | ||||
|                     }, | ||||
|                   ), | ||||
|                   ListTile( | ||||
|                     title: const Text('5m'), | ||||
|                     subtitle: const Text('chatBreakHour').tr(args: ['5m']), | ||||
|                     leading: const Icon(Symbols.circle), | ||||
|                     onTap: () { | ||||
|                       setChatBreak(now.add(const Duration(minutes: 5))); | ||||
|                       Navigator.pop(context); | ||||
|                       if (context.mounted) { | ||||
|                         showSnackBar(context, 'chatBreakSet'.tr(args: ['5m'])); | ||||
|                       } | ||||
|                     }, | ||||
|                   ), | ||||
|                   ListTile( | ||||
|                     title: const Text('10m'), | ||||
|                     subtitle: const Text('chatBreakHour').tr(args: ['10m']), | ||||
|                     leading: const Icon(Symbols.circle), | ||||
|                     onTap: () { | ||||
|                       setChatBreak(now.add(const Duration(minutes: 10))); | ||||
|                       Navigator.pop(context); | ||||
|                       if (context.mounted) { | ||||
|                         showSnackBar(context, 'chatBreakSet'.tr(args: ['10m'])); | ||||
|                       } | ||||
|                     }, | ||||
|                   ), | ||||
|                   ListTile( | ||||
|                     title: const Text('15m'), | ||||
|                     subtitle: const Text('chatBreakHour').tr(args: ['15m']), | ||||
|                     leading: const Icon(Symbols.timer_3), | ||||
|                     onTap: () { | ||||
|                       setChatBreak(now.add(const Duration(minutes: 15))); | ||||
|                       Navigator.pop(context); | ||||
|                       if (context.mounted) { | ||||
|                         showSnackBar(context, 'chatBreakSet'.tr(args: ['15m'])); | ||||
|                       } | ||||
|                     }, | ||||
|                   ), | ||||
|                   ListTile( | ||||
|                     title: const Text('30m'), | ||||
|                     subtitle: const Text('chatBreakHour').tr(args: ['30m']), | ||||
|                     leading: const Icon(Symbols.timer), | ||||
|                     onTap: () { | ||||
|                       setChatBreak(now.add(const Duration(minutes: 30))); | ||||
|                       Navigator.pop(context); | ||||
|                       if (context.mounted) { | ||||
|                         showSnackBar(context, 'chatBreakSet'.tr(args: ['30m'])); | ||||
|                       } | ||||
|                     }, | ||||
|                   ), | ||||
|                   const Gap(8), | ||||
|                   TextField( | ||||
|                     controller: durationController, | ||||
|                     decoration: InputDecoration( | ||||
|                       labelText: 'Custom (minutes)'.tr(), | ||||
|                       hintText: 'Enter minutes'.tr(), | ||||
|                       border: const OutlineInputBorder(), | ||||
|                       suffixIcon: IconButton( | ||||
|                         icon: const Icon(Icons.check), | ||||
|                         onPressed: () { | ||||
|                           final minutes = int.tryParse(durationController.text); | ||||
|                           if (minutes != null && minutes > 0) { | ||||
|                             setChatBreak(now.add(Duration(minutes: minutes))); | ||||
|                             Navigator.pop(context); | ||||
|                             if (context.mounted) { | ||||
|                               showSnackBar( | ||||
|                                 context, | ||||
|                                 'chatBreakSet'.tr(args: ['${minutes}m']), | ||||
|                               ); | ||||
|                             } | ||||
|                           } | ||||
|                         }, | ||||
|                       ), | ||||
|                     ), | ||||
|                     keyboardType: TextInputType.number, | ||||
|                     onTapOutside: | ||||
|                         (_) => FocusManager.instance.primaryFocus?.unfocus(), | ||||
|                   ), | ||||
|                 ], | ||||
|               ), | ||||
|               actions: [ | ||||
|                 TextButton( | ||||
|                   onPressed: () => Navigator.pop(context), | ||||
|                   child: const Text('cancel').tr(), | ||||
|                 ), | ||||
|               ], | ||||
|             ), | ||||
|       ); | ||||
|     } | ||||
|  | ||||
|     const iconShadow = Shadow( | ||||
|       color: Colors.black54, | ||||
| @@ -48,9 +252,9 @@ class ChatDetailScreen extends HookConsumerWidget { | ||||
|                   flexibleSpace: FlexibleSpaceBar( | ||||
|                     background: | ||||
|                         (currentRoom!.type == 1 && | ||||
|                                 currentRoom.backgroundId != null) | ||||
|                                 currentRoom.background?.id != null) | ||||
|                             ? CloudImageWidget( | ||||
|                               fileId: currentRoom.backgroundId!, | ||||
|                               fileId: currentRoom.background!.id, | ||||
|                             ) | ||||
|                             : (currentRoom.type == 1 && | ||||
|                                 currentRoom.members!.length == 1 && | ||||
| @@ -59,7 +263,8 @@ class ChatDetailScreen extends HookConsumerWidget { | ||||
|                                         .first | ||||
|                                         .account | ||||
|                                         .profile | ||||
|                                         .backgroundId != | ||||
|                                         .background | ||||
|                                         ?.id != | ||||
|                                     null) | ||||
|                             ? CloudImageWidget( | ||||
|                               fileId: | ||||
| @@ -68,11 +273,12 @@ class ChatDetailScreen extends HookConsumerWidget { | ||||
|                                       .first | ||||
|                                       .account | ||||
|                                       .profile | ||||
|                                       .backgroundId!, | ||||
|                                       .background! | ||||
|                                       .id, | ||||
|                             ) | ||||
|                             : currentRoom.backgroundId != null | ||||
|                             : currentRoom.background?.id != null | ||||
|                             ? CloudImageWidget( | ||||
|                               fileId: currentRoom.backgroundId!, | ||||
|                               fileId: currentRoom.background!.id, | ||||
|                               fit: BoxFit.cover, | ||||
|                             ) | ||||
|                             : Container( | ||||
| @@ -108,17 +314,59 @@ class ChatDetailScreen extends HookConsumerWidget { | ||||
|                   ], | ||||
|                 ), | ||||
|                 SliverToBoxAdapter( | ||||
|                   child: Padding( | ||||
|                     padding: const EdgeInsets.all(16.0), | ||||
|                     child: Column( | ||||
|                       crossAxisAlignment: CrossAxisAlignment.start, | ||||
|                       children: [ | ||||
|                         Text( | ||||
|                           currentRoom.description ?? 'descriptionNone'.tr(), | ||||
|                           style: const TextStyle(fontSize: 16), | ||||
|                         ), | ||||
|                       ], | ||||
|                     ), | ||||
|                   child: Column( | ||||
|                     crossAxisAlignment: CrossAxisAlignment.start, | ||||
|                     children: [ | ||||
|                       Text( | ||||
|                         currentRoom.description ?? 'descriptionNone'.tr(), | ||||
|                         style: const TextStyle(fontSize: 16), | ||||
|                       ).padding(all: 24), | ||||
|                       const Divider(height: 1), | ||||
|                       roomIdentity.when( | ||||
|                         data: | ||||
|                             (identity) => Column( | ||||
|                               crossAxisAlignment: CrossAxisAlignment.start, | ||||
|                               children: [ | ||||
|                                 ListTile( | ||||
|                                   contentPadding: EdgeInsets.symmetric( | ||||
|                                     horizontal: 24, | ||||
|                                   ), | ||||
|                                   leading: const Icon(Symbols.notifications), | ||||
|                                   trailing: const Icon(Symbols.chevron_right), | ||||
|                                   title: const Text('chatNotifyLevel').tr(), | ||||
|                                   subtitle: Text( | ||||
|                                     kNotifyLevelText[identity!.notify].tr(), | ||||
|                                   ), | ||||
|                                   onTap: | ||||
|                                       () => | ||||
|                                           showNotifyLevelBottomSheet(identity), | ||||
|                                 ), | ||||
|                                 ListTile( | ||||
|                                   contentPadding: EdgeInsets.symmetric( | ||||
|                                     horizontal: 24, | ||||
|                                   ), | ||||
|                                   leading: const Icon(Icons.timer), | ||||
|                                   trailing: const Icon(Symbols.chevron_right), | ||||
|                                   title: const Text('chatBreak').tr(), | ||||
|                                   subtitle: | ||||
|                                       identity.breakUntil != null && | ||||
|                                               identity.breakUntil!.isAfter( | ||||
|                                                 DateTime.now(), | ||||
|                                               ) | ||||
|                                           ? Text( | ||||
|                                             DateFormat( | ||||
|                                               'yyyy-MM-dd HH:mm', | ||||
|                                             ).format(identity.breakUntil!), | ||||
|                                           ) | ||||
|                                           : const Text('chatBreakNone').tr(), | ||||
|                                   onTap: () => showChatBreakDialog(), | ||||
|                                 ), | ||||
|                               ], | ||||
|                             ), | ||||
|                         error: (_, _) => const SizedBox.shrink(), | ||||
|                         loading: () => const SizedBox.shrink(), | ||||
|                       ), | ||||
|                     ], | ||||
|                   ), | ||||
|                 ), | ||||
|               ], | ||||
| @@ -229,7 +477,7 @@ class _ChatRoomActionMenu extends HookConsumerWidget { | ||||
| } | ||||
|  | ||||
| @freezed | ||||
| abstract class ChatRoomMemberState with _$ChatRoomMemberState { | ||||
| sealed class ChatRoomMemberState with _$ChatRoomMemberState { | ||||
|   const factory ChatRoomMemberState({ | ||||
|     required List<SnChatMember> members, | ||||
|     required bool isLoading, | ||||
| @@ -285,12 +533,51 @@ class ChatMemberNotifier extends StateNotifier<ChatRoomMemberState> { | ||||
|   } | ||||
| } | ||||
|  | ||||
| @riverpod | ||||
| class ChatMemberListNotifier extends _$ChatMemberListNotifier | ||||
|     with CursorPagingNotifierMixin<SnChatMember> { | ||||
|   @override | ||||
|   Future<CursorPagingData<SnChatMember>> build(String roomId) { | ||||
|     return fetch(); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Future<CursorPagingData<SnChatMember>> fetch({String? cursor}) async { | ||||
|     final offset = cursor == null ? 0 : int.parse(cursor); | ||||
|     final take = 20; | ||||
|  | ||||
|     final apiClient = ref.watch(apiClientProvider); | ||||
|     final response = await apiClient.get( | ||||
|       '/chat/$roomId/members', | ||||
|       queryParameters: {'offset': offset, 'take': take}, | ||||
|     ); | ||||
|  | ||||
|     final total = int.parse(response.headers.value('X-Total') ?? '0'); | ||||
|     final List<dynamic> data = response.data; | ||||
|     final members = data.map((e) => SnChatMember.fromJson(e)).toList(); | ||||
|  | ||||
|     // Calculate next cursor based on total count | ||||
|     final nextOffset = offset + members.length; | ||||
|     final String? nextCursor = | ||||
|         nextOffset < total ? nextOffset.toString() : null; | ||||
|  | ||||
|     return CursorPagingData( | ||||
|       items: members, | ||||
|       nextCursor: nextCursor, | ||||
|       hasMore: members.length < total, | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|  | ||||
| class _ChatMemberListSheet extends HookConsumerWidget { | ||||
|   final String roomId; | ||||
|   const _ChatMemberListSheet({required this.roomId}); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context, WidgetRef ref) { | ||||
|     final memberListProvider = chatMemberListNotifierProvider(roomId); | ||||
|  | ||||
|     // For backward compatibility and to show total count in the header | ||||
|     final memberState = ref.watch(chatMemberStateProvider(roomId)); | ||||
|     final memberNotifier = ref.read(chatMemberStateProvider(roomId).notifier); | ||||
|  | ||||
| @@ -316,8 +603,10 @@ class _ChatMemberListSheet extends HookConsumerWidget { | ||||
|           '/chat/invites/$roomId', | ||||
|           data: {'related_user_id': result.id, 'role': 0}, | ||||
|         ); | ||||
|         // Refresh both providers | ||||
|         memberNotifier.reset(); | ||||
|         await memberNotifier.loadMore(); | ||||
|         ref.invalidate(memberListProvider); | ||||
|       } catch (err) { | ||||
|         showErrorAlert(err); | ||||
|       } | ||||
| @@ -349,8 +638,10 @@ class _ChatMemberListSheet extends HookConsumerWidget { | ||||
|                 IconButton( | ||||
|                   icon: const Icon(Symbols.refresh), | ||||
|                   onPressed: () { | ||||
|                     // Refresh both providers | ||||
|                     memberNotifier.reset(); | ||||
|                     memberNotifier.loadMore(); | ||||
|                     ref.invalidate(memberListProvider); | ||||
|                   }, | ||||
|                 ), | ||||
|                 IconButton( | ||||
| @@ -363,108 +654,103 @@ class _ChatMemberListSheet extends HookConsumerWidget { | ||||
|           ), | ||||
|           const Divider(height: 1), | ||||
|           Expanded( | ||||
|             child: | ||||
|                 memberState.error != null | ||||
|                     ? Center(child: Text(memberState.error!)) | ||||
|                     : ListView.builder( | ||||
|                       itemCount: memberState.members.length + 1, | ||||
|                       itemBuilder: (context, index) { | ||||
|                         if (index == memberState.members.length) { | ||||
|                           if (memberState.isLoading) { | ||||
|                             return const Center( | ||||
|                               child: Padding( | ||||
|                                 padding: EdgeInsets.all(16.0), | ||||
|                                 child: CircularProgressIndicator(), | ||||
|                               ), | ||||
|                             ); | ||||
|                           } | ||||
|                           if (memberState.members.length < memberState.total) { | ||||
|                             memberNotifier.loadMore( | ||||
|                               offset: memberState.members.length, | ||||
|                             ); | ||||
|                           } | ||||
|                           return const SizedBox.shrink(); | ||||
|                         } | ||||
|             child: PagingHelperView( | ||||
|               provider: memberListProvider, | ||||
|               futureRefreshable: memberListProvider.future, | ||||
|               notifierRefreshable: memberListProvider.notifier, | ||||
|               contentBuilder: (data, widgetCount, endItemView) { | ||||
|                 return ListView.builder( | ||||
|                   itemCount: widgetCount, | ||||
|                   itemBuilder: (context, index) { | ||||
|                     if (index == data.items.length) { | ||||
|                       return endItemView; | ||||
|                     } | ||||
|  | ||||
|                         final member = memberState.members[index]; | ||||
|                         return ListTile( | ||||
|                           contentPadding: EdgeInsets.only(left: 16, right: 12), | ||||
|                           leading: ProfilePictureWidget( | ||||
|                             fileId: member.account.profile.pictureId, | ||||
|                           ), | ||||
|                           title: Row( | ||||
|                             spacing: 6, | ||||
|                             children: [ | ||||
|                               Flexible(child: Text(member.account.nick)), | ||||
|                               if (member.joinedAt == null) | ||||
|                                 const Icon(Symbols.pending_actions, size: 20), | ||||
|                             ], | ||||
|                           ), | ||||
|                           subtitle: Row( | ||||
|                             children: [ | ||||
|                               Text( | ||||
|                                 member.role >= 100 | ||||
|                                     ? 'permissionOwner' | ||||
|                                     : member.role >= 50 | ||||
|                                     ? 'permissionModerator' | ||||
|                                     : 'permissionMember', | ||||
|                               ).tr(), | ||||
|                               Text('·').bold().padding(horizontal: 6), | ||||
|                               Expanded(child: Text("@${member.account.name}")), | ||||
|                             ], | ||||
|                           ), | ||||
|                           trailing: Row( | ||||
|                             mainAxisSize: MainAxisSize.min, | ||||
|                             children: [ | ||||
|                               if ((roomIdentity.value?.role ?? 0) >= 50) | ||||
|                                 IconButton( | ||||
|                                   icon: const Icon(Symbols.edit), | ||||
|                                   onPressed: () { | ||||
|                                     showModalBottomSheet( | ||||
|                                       isScrollControlled: true, | ||||
|                                       context: context, | ||||
|                                       builder: | ||||
|                                           (context) => _ChatMemberRoleSheet( | ||||
|                                             roomId: roomId, | ||||
|                                             member: member, | ||||
|                                           ), | ||||
|                                     ).then((value) { | ||||
|                                       if (value != null) { | ||||
|                                         memberNotifier.reset(); | ||||
|                                         memberNotifier.loadMore(); | ||||
|                                       } | ||||
|                                     }); | ||||
|                                   }, | ||||
|                                 ), | ||||
|                               if ((roomIdentity.value?.role ?? 0) >= 50) | ||||
|                                 IconButton( | ||||
|                                   icon: const Icon(Symbols.delete), | ||||
|                                   onPressed: () { | ||||
|                                     showConfirmAlert( | ||||
|                                       'removeChatMemberHint'.tr(), | ||||
|                                       'removeChatMember'.tr(), | ||||
|                                     ).then((confirm) async { | ||||
|                                       if (confirm != true) return; | ||||
|                                       try { | ||||
|                                         final apiClient = ref.watch( | ||||
|                                           apiClientProvider, | ||||
|                                         ); | ||||
|                                         await apiClient.delete( | ||||
|                                           '/chat/$roomId/members/${member.accountId}', | ||||
|                                         ); | ||||
|                                         memberNotifier.reset(); | ||||
|                                         memberNotifier.loadMore(); | ||||
|                                       } catch (err) { | ||||
|                                         showErrorAlert(err); | ||||
|                                       } | ||||
|                                     }); | ||||
|                                   }, | ||||
|                                 ), | ||||
|                             ], | ||||
|                           ), | ||||
|                         ); | ||||
|                       }, | ||||
|                     ), | ||||
|                     final member = data.items[index]; | ||||
|                     return ListTile( | ||||
|                       contentPadding: EdgeInsets.only(left: 16, right: 12), | ||||
|                       leading: ProfilePictureWidget( | ||||
|                         fileId: member.account.profile.picture?.id, | ||||
|                       ), | ||||
|                       title: Row( | ||||
|                         spacing: 6, | ||||
|                         children: [ | ||||
|                           Flexible(child: Text(member.account.nick)), | ||||
|                           if (member.joinedAt == null) | ||||
|                             const Icon(Symbols.pending_actions, size: 20), | ||||
|                         ], | ||||
|                       ), | ||||
|                       subtitle: Row( | ||||
|                         children: [ | ||||
|                           Text( | ||||
|                             member.role >= 100 | ||||
|                                 ? 'permissionOwner' | ||||
|                                 : member.role >= 50 | ||||
|                                 ? 'permissionModerator' | ||||
|                                 : 'permissionMember', | ||||
|                           ).tr(), | ||||
|                           Text('·').bold().padding(horizontal: 6), | ||||
|                           Expanded(child: Text("@${member.account.name}")), | ||||
|                         ], | ||||
|                       ), | ||||
|                       trailing: Row( | ||||
|                         mainAxisSize: MainAxisSize.min, | ||||
|                         children: [ | ||||
|                           if ((roomIdentity.value?.role ?? 0) >= 50) | ||||
|                             IconButton( | ||||
|                               icon: const Icon(Symbols.edit), | ||||
|                               onPressed: () { | ||||
|                                 showModalBottomSheet( | ||||
|                                   isScrollControlled: true, | ||||
|                                   context: context, | ||||
|                                   builder: | ||||
|                                       (context) => _ChatMemberRoleSheet( | ||||
|                                         roomId: roomId, | ||||
|                                         member: member, | ||||
|                                       ), | ||||
|                                 ).then((value) { | ||||
|                                   if (value != null) { | ||||
|                                     // Refresh both providers | ||||
|                                     memberNotifier.reset(); | ||||
|                                     memberNotifier.loadMore(); | ||||
|                                     ref.invalidate(memberListProvider); | ||||
|                                   } | ||||
|                                 }); | ||||
|                               }, | ||||
|                             ), | ||||
|                           if ((roomIdentity.value?.role ?? 0) >= 50) | ||||
|                             IconButton( | ||||
|                               icon: const Icon(Symbols.delete), | ||||
|                               onPressed: () { | ||||
|                                 showConfirmAlert( | ||||
|                                   'removeChatMemberHint'.tr(), | ||||
|                                   'removeChatMember'.tr(), | ||||
|                                 ).then((confirm) async { | ||||
|                                   if (confirm != true) return; | ||||
|                                   try { | ||||
|                                     final apiClient = ref.watch( | ||||
|                                       apiClientProvider, | ||||
|                                     ); | ||||
|                                     await apiClient.delete( | ||||
|                                       '/chat/$roomId/members/${member.accountId}', | ||||
|                                     ); | ||||
|                                     // Refresh both providers | ||||
|                                     memberNotifier.reset(); | ||||
|                                     memberNotifier.loadMore(); | ||||
|                                     ref.invalidate(memberListProvider); | ||||
|                                   } catch (err) { | ||||
|                                     showErrorAlert(err); | ||||
|                                   } | ||||
|                                 }); | ||||
|                               }, | ||||
|                             ), | ||||
|                         ], | ||||
|                       ), | ||||
|                     ); | ||||
|                   }, | ||||
|                 ); | ||||
|               }, | ||||
|             ), | ||||
|           ), | ||||
|         ], | ||||
|       ), | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user