Compare commits
67 Commits
Author | SHA1 | Date | |
---|---|---|---|
4728df93e2 | |||
3fd9cd4547 | |||
beda47ff7e | |||
d1506f10ef | |||
006841cf82 | |||
f73cf10a54 | |||
b39248cc58 | |||
4ba809a8d6 | |||
a6d1ca57d7 | |||
fbbe373ce8 | |||
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 |
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@ -72,7 +72,7 @@ jobs:
|
|||||||
mkdir Solian.AppDir
|
mkdir Solian.AppDir
|
||||||
cp -r build/linux/x64/release/bundle/* Solian.AppDir
|
cp -r build/linux/x64/release/bundle/* Solian.AppDir
|
||||||
cp -r buildtools/appimage_config/* Solian.AppDir
|
cp -r buildtools/appimage_config/* Solian.AppDir
|
||||||
cp assets/icon/icon-light-radius.png Solian.AppDir
|
cp assets/icons/icon-padded.png Solian.AppDir
|
||||||
sudo chmod +x buildtools/appimagetool-x86_64.AppImage
|
sudo chmod +x buildtools/appimagetool-x86_64.AppImage
|
||||||
sudo chmod +x Solian.AppDir/AppRun
|
sudo chmod +x Solian.AppDir/AppRun
|
||||||
./buildtools/appimagetool-x86_64.AppImage Solian.AppDir
|
./buildtools/appimagetool-x86_64.AppImage Solian.AppDir
|
||||||
|
@ -18,9 +18,7 @@ android {
|
|||||||
targetCompatibility = JavaVersion.VERSION_17
|
targetCompatibility = JavaVersion.VERSION_17
|
||||||
}
|
}
|
||||||
|
|
||||||
kotlinOptions {
|
kotlinOptions { jvmTarget = JavaVersion.VERSION_17.toString() }
|
||||||
jvmTarget = JavaVersion.VERSION_17.toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId = "dev.solsynth.solian"
|
applicationId = "dev.solsynth.solian"
|
||||||
@ -32,11 +30,20 @@ android {
|
|||||||
versionName = flutter.versionName
|
versionName = flutter.versionName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
signingConfigs {
|
||||||
|
release {
|
||||||
|
keyAlias = keystoreProperties['keyAlias']
|
||||||
|
keyPassword = keystoreProperties['keyPassword']
|
||||||
|
storeFile = keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
|
||||||
|
storePassword = keystoreProperties['storePassword']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
// TODO: Add your own signing config for the release build.
|
signingConfig = signingConfigs.getByName("release")
|
||||||
// Signing with the debug keys for now, so `flutter run --release` works.
|
minifyEnabled = true
|
||||||
signingConfig = signingConfigs.getByName("debug")
|
shrinkResources = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,6 +42,22 @@
|
|||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
<!-- Sign in with Apple -->
|
||||||
|
<activity
|
||||||
|
android:name="com.aboutyou.dart_packages.sign_in_with_apple.SignInWithAppleCallback"
|
||||||
|
android:exported="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
|
<provider
|
||||||
android:name="androidx.core.content.FileProvider"
|
android:name="androidx.core.content.FileProvider"
|
||||||
android:authorities="dev.solsynth.solian.provider"
|
android:authorities="dev.solsynth.solian.provider"
|
||||||
|
@ -10,6 +10,8 @@
|
|||||||
"loginEnterPassword": "Enter the code",
|
"loginEnterPassword": "Enter the code",
|
||||||
"loginSuccess": "Logged in as {}",
|
"loginSuccess": "Logged in as {}",
|
||||||
"loginGreeting": "Welcome back!",
|
"loginGreeting": "Welcome back!",
|
||||||
|
"loginOr": "Or login with\nthird parties",
|
||||||
|
"loginInProgress": "Logging you in...",
|
||||||
"username": "Username",
|
"username": "Username",
|
||||||
"usernameCannotChangeHint": "Username cannot be updated after created.",
|
"usernameCannotChangeHint": "Username cannot be updated after created.",
|
||||||
"usernameLookupHint": "We also take your email address.",
|
"usernameLookupHint": "We also take your email address.",
|
||||||
@ -27,7 +29,7 @@
|
|||||||
"fieldCannotBeEmpty": "This field cannot be empty.",
|
"fieldCannotBeEmpty": "This field cannot be empty.",
|
||||||
"fieldEmailAddressMustBeValid": "The email address must be valid.",
|
"fieldEmailAddressMustBeValid": "The email address must be valid.",
|
||||||
"logout": "Logout",
|
"logout": "Logout",
|
||||||
"updateYourProfile": "Edit Profile",
|
"updateYourProfile": "Profile Settings",
|
||||||
"accountBasicInfo": "Basic Info",
|
"accountBasicInfo": "Basic Info",
|
||||||
"accountProfile": "Your Profile",
|
"accountProfile": "Your Profile",
|
||||||
"saveChanges": "Save Changes",
|
"saveChanges": "Save Changes",
|
||||||
@ -63,6 +65,8 @@
|
|||||||
"authFactorTOTPDescription": "A one-time code generated by a TOTP authenticator such as Google Authenticator or Authy.",
|
"authFactorTOTPDescription": "A one-time code generated by a TOTP authenticator such as Google Authenticator or Authy.",
|
||||||
"authFactorInAppNotify": "In-app notification",
|
"authFactorInAppNotify": "In-app notification",
|
||||||
"authFactorInAppNotifyDescription": "A one-time code sent via in-app notification.",
|
"authFactorInAppNotifyDescription": "A one-time code sent via in-app notification.",
|
||||||
|
"authFactorPin": "Pin Code",
|
||||||
|
"authFactorPinDescription": "It consists of 6 digits. It cannot be used to log in. When performing some dangerous operations, the system will ask you to enter this PIN for confirmation.",
|
||||||
"realms": "Realms",
|
"realms": "Realms",
|
||||||
"createRealm": "Create a Realm",
|
"createRealm": "Create a Realm",
|
||||||
"createRealmHint": "Meet friends with same interests, build communities, and more.",
|
"createRealmHint": "Meet friends with same interests, build communities, and more.",
|
||||||
@ -70,9 +74,10 @@
|
|||||||
"deleteRealm": "Delete Realm",
|
"deleteRealm": "Delete Realm",
|
||||||
"deleteRealmHint": "Are you sure to delete this realm? This will also deleted all the channels, publishers, and posts under this realm.",
|
"deleteRealmHint": "Are you sure to delete this realm? This will also deleted all the channels, publishers, and posts under this realm.",
|
||||||
"explore": "Explore",
|
"explore": "Explore",
|
||||||
|
"exploreFilterSubscriptions": "Subscriptions",
|
||||||
|
"exploreFilterFriends": "Friends",
|
||||||
"account": "Account",
|
"account": "Account",
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
"description": "Description",
|
|
||||||
"slug": "Slug",
|
"slug": "Slug",
|
||||||
"slugHint": "The slug will be used in the URL to access this resource, it should be unique and URL safe.",
|
"slugHint": "The slug will be used in the URL to access this resource, it should be unique and URL safe.",
|
||||||
"createChatRoom": "Create a Room",
|
"createChatRoom": "Create a Room",
|
||||||
@ -98,6 +103,11 @@
|
|||||||
"permissionModerator": "Moderator",
|
"permissionModerator": "Moderator",
|
||||||
"permissionMember": "Member",
|
"permissionMember": "Member",
|
||||||
"reply": "Reply",
|
"reply": "Reply",
|
||||||
|
"repliesCount": {
|
||||||
|
"zero": "No reply",
|
||||||
|
"one": "{} reply",
|
||||||
|
"other": "{} replies"
|
||||||
|
},
|
||||||
"forward": "Forward",
|
"forward": "Forward",
|
||||||
"repliedTo": "Replied to",
|
"repliedTo": "Replied to",
|
||||||
"forwarded": "Forwarded",
|
"forwarded": "Forwarded",
|
||||||
@ -113,7 +123,8 @@
|
|||||||
"addVideo": "Add video",
|
"addVideo": "Add video",
|
||||||
"addPhoto": "Add photo",
|
"addPhoto": "Add photo",
|
||||||
"addFile": "Add file",
|
"addFile": "Add file",
|
||||||
"createDirectMessage": "New direct message",
|
"createDirectMessage": "Send new DM",
|
||||||
|
"gotoDirectMessage": "Go to DM",
|
||||||
"react": "React",
|
"react": "React",
|
||||||
"reactions": {
|
"reactions": {
|
||||||
"zero": "Reactions",
|
"zero": "Reactions",
|
||||||
@ -126,6 +137,25 @@
|
|||||||
"connectionConnected": "Connected",
|
"connectionConnected": "Connected",
|
||||||
"connectionDisconnected": "Disconnected",
|
"connectionDisconnected": "Disconnected",
|
||||||
"connectionReconnecting": "Reconnecting",
|
"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",
|
||||||
|
"accountConnectionProviderAfdian": "Afdian",
|
||||||
"checkIn": "Check In",
|
"checkIn": "Check In",
|
||||||
"checkInNone": "Not checked-in yet",
|
"checkInNone": "Not checked-in yet",
|
||||||
"checkInNoneHint": "Get your fortune tips and daily rewards by checking in.",
|
"checkInNoneHint": "Get your fortune tips and daily rewards by checking in.",
|
||||||
@ -134,14 +164,11 @@
|
|||||||
"checkInResultLevel2": "A Normal Day",
|
"checkInResultLevel2": "A Normal Day",
|
||||||
"checkInResultLevel3": "Good Luck",
|
"checkInResultLevel3": "Good Luck",
|
||||||
"checkInResultLevel4": "Best Luck",
|
"checkInResultLevel4": "Best Luck",
|
||||||
"checkInResultLevelShort0": "Wrost",
|
|
||||||
"checkInResultLevelShort1": "Bad",
|
|
||||||
"checkInResultLevelShort2": "Normal",
|
|
||||||
"checkInResultLevelShort3": "Good",
|
|
||||||
"checkInResultLevelShort4": "Best",
|
|
||||||
"checkInActivityTitle": "{} checked in on {} and got a {}",
|
"checkInActivityTitle": "{} checked in on {} and got a {}",
|
||||||
"eventCalander": "Event Calander",
|
"eventCalander": "Event Calander",
|
||||||
"eventCalanderEmpty": "No events on that day.",
|
"eventCalanderEmpty": "No events on that day.",
|
||||||
|
"fortuneGraph": "Fortune Trend",
|
||||||
|
"noFortuneData": "No fortune data available for this month.",
|
||||||
"creatorHub": "Creator Hub",
|
"creatorHub": "Creator Hub",
|
||||||
"creatorHubDescription": "Manage posts, analytics, and more.",
|
"creatorHubDescription": "Manage posts, analytics, and more.",
|
||||||
"developerPortal": "Developer Portal",
|
"developerPortal": "Developer Portal",
|
||||||
@ -165,7 +192,7 @@
|
|||||||
"status": "Status",
|
"status": "Status",
|
||||||
"statusActivityTitle": "{} is {} {}",
|
"statusActivityTitle": "{} is {} {}",
|
||||||
"statusActivityEndedTitle": "{} is {} {} until {}",
|
"statusActivityEndedTitle": "{} is {} {} until {}",
|
||||||
"appSettings": "App settings",
|
"appSettings": "App Settings",
|
||||||
"accountSettings": "Account Settings",
|
"accountSettings": "Account Settings",
|
||||||
"settings": "Settings",
|
"settings": "Settings",
|
||||||
"language": "Language",
|
"language": "Language",
|
||||||
@ -233,6 +260,7 @@
|
|||||||
"creatorHubUnselectedHint": "Pick / create a publisher to get started.",
|
"creatorHubUnselectedHint": "Pick / create a publisher to get started.",
|
||||||
"relationships": "Relationships",
|
"relationships": "Relationships",
|
||||||
"addFriend": "Send a Friend Request",
|
"addFriend": "Send a Friend Request",
|
||||||
|
"addFriendShort": "Add as Friend",
|
||||||
"addFriendHint": "Add a friend to your relationship list.",
|
"addFriendHint": "Add a friend to your relationship list.",
|
||||||
"pendingRequest": "Pending",
|
"pendingRequest": "Pending",
|
||||||
"waitingRequest": "Waiting",
|
"waitingRequest": "Waiting",
|
||||||
@ -277,11 +305,13 @@
|
|||||||
"posts": "Posts",
|
"posts": "Posts",
|
||||||
"settingsBackgroundImage": "Background Image",
|
"settingsBackgroundImage": "Background Image",
|
||||||
"settingsBackgroundImageClear": "Clear Background Image",
|
"settingsBackgroundImageClear": "Clear Background Image",
|
||||||
|
"settingsBackgroundGenerateColor": "Generate color scheme from Bacground Image",
|
||||||
"messageNone": "No content to display",
|
"messageNone": "No content to display",
|
||||||
"unreadMessages": {
|
"unreadMessages": {
|
||||||
"one": "{} unread message",
|
"one": "{} unread message",
|
||||||
"other": "{} unread messages"
|
"other": "{} unread messages"
|
||||||
},
|
},
|
||||||
|
"chatBreakNone": "None",
|
||||||
"settingsRealmCompactView": "Compact Realm View",
|
"settingsRealmCompactView": "Compact Realm View",
|
||||||
"settingsMixedFeed": "Mixed Feed",
|
"settingsMixedFeed": "Mixed Feed",
|
||||||
"settingsAutoTranslate": "Auto Translate",
|
"settingsAutoTranslate": "Auto Translate",
|
||||||
@ -302,33 +332,160 @@
|
|||||||
"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.",
|
"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.",
|
"accountDeletionSent": "Account deletion confirmation email sent, please check your email inbox.",
|
||||||
"accountSecurityTitle": "Security",
|
"accountSecurityTitle": "Security",
|
||||||
"accountPrivacyTitle": "Privacy",
|
|
||||||
"accountDangerZoneTitle": "Danger Zone",
|
"accountDangerZoneTitle": "Danger Zone",
|
||||||
"accountPassword": "Password",
|
"accountPassword": "Password",
|
||||||
"accountPasswordDescription": "Change your account password",
|
"accountPasswordDescription": "Change your account password",
|
||||||
"accountPasswordChange": "Change Password",
|
"accountPasswordChange": "Change Password",
|
||||||
"accountPasswordChangeSent": "Password reset link sent, please check your email inbox.",
|
"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.",
|
"accountPasswordChangeDescription": "We will send an email to your primary email address to reset your password.",
|
||||||
"accountTwoFactor": "Two-Factor Authentication",
|
"accountAuthFactor": "Auth factors",
|
||||||
"accountTwoFactorDescription": "Add an extra layer of security to your account",
|
"accountAuthFactorDescription": "Multi-factor authentication to ensure safety and convience",
|
||||||
"accountTwoFactorSetup": "Set Up 2FA",
|
|
||||||
"accountTwoFactorSetupDescription": "Two-factor authentication adds an additional layer of security to your account by requiring more than just a password to sign in.",
|
|
||||||
"accountPrivacy": "Privacy Settings",
|
|
||||||
"accountPrivacyDescription": "Control who can see your profile and content",
|
|
||||||
"accountDataExport": "Export Your Data",
|
|
||||||
"accountDataExportDescription": "Download a copy of your data",
|
|
||||||
"accountDataExportConfirmation": "We'll prepare an export of your data which may take some time. You'll receive an email when it's ready to download.",
|
|
||||||
"accountDataExportConfirm": "Request Export",
|
|
||||||
"accountDataExportRequested": "Data export requested. You'll receive an email when it's ready.",
|
|
||||||
"accountDeletionDescription": "Permanently delete your account and all your data",
|
"accountDeletionDescription": "Permanently delete your account and all your data",
|
||||||
"accountSettingsHelp": "Account Settings Help",
|
"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.",
|
"accountSettingsHelpContent": "This page allows you to manage your account security, privacy, and other settings. If you need assistance, please contact support.",
|
||||||
"unauthorized": "Unauthorized",
|
"unauthorized": "Unauthorized",
|
||||||
"unauthorizedHint": "You're not signed in or session expired, please sign in again.",
|
"unauthorizedHint": "You're not signed in or session expired, please sign in again.",
|
||||||
"publisherVisitAccountPage": "Visit the profile of {}",
|
"publisherBelongsTo": "Belongs to {}",
|
||||||
|
"postContent": "Content",
|
||||||
|
"postSettings": "Settings",
|
||||||
|
"postPublisherUnselected": "Publisher Unspecified",
|
||||||
"postVisibility": "Visibility",
|
"postVisibility": "Visibility",
|
||||||
"postVisibilityPublic": "Public",
|
"postVisibilityPublic": "Public",
|
||||||
"postVisibilityFriends": "Friends Only",
|
"postVisibilityFriends": "Friends Only",
|
||||||
"postVisibilityUnlisted": "Unlisted",
|
"postVisibilityUnlisted": "Unlisted",
|
||||||
"postVisibilityPrivate": "Private"
|
"postVisibilityPrivate": "Private",
|
||||||
|
"postTruncated": "Content truncated, tap to view full post",
|
||||||
|
"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",
|
||||||
|
"subscribe": "Subscribe",
|
||||||
|
"unsubscribe": "Unsubscribe",
|
||||||
|
"paymentVerification": "Payment Verification",
|
||||||
|
"paymentSummary": "Payment Summary",
|
||||||
|
"amount": "Amount",
|
||||||
|
"description": "Description",
|
||||||
|
"pinCode": "PIN Code",
|
||||||
|
"biometric": "Biometric",
|
||||||
|
"enterPinToConfirm": "Enter your 6-digit PIN to confirm payment",
|
||||||
|
"clearPin": "Clear PIN",
|
||||||
|
"useBiometricToConfirm": "Use biometric authentication to confirm payment",
|
||||||
|
"touchSensorToAuthenticate": "Touch the sensor to authenticate",
|
||||||
|
"authenticating": "Authenticating...",
|
||||||
|
"authenticateNow": "Authenticate Now",
|
||||||
|
"processing": "Processing...",
|
||||||
|
"processingPayment": "Processing Payment...",
|
||||||
|
"pleaseWait": "Please wait",
|
||||||
|
"paymentFailed": "Payment failed. Please try again.",
|
||||||
|
"invalidPin": "Invalid PIN. Please try again.",
|
||||||
|
"biometricAuthFailed": "Biometric authentication failed. Please try again.",
|
||||||
|
"paymentSuccess": "Payment completed successfully!",
|
||||||
|
"membershipPurchaseSuccess": "Membership purchased successfully!",
|
||||||
|
"paymentError": "Payment failed: {error}"
|
||||||
}
|
}
|
||||||
|
@ -291,5 +291,33 @@
|
|||||||
"postVisibilityPublic": "公开",
|
"postVisibilityPublic": "公开",
|
||||||
"postVisibilityFriends": "仅好友可见",
|
"postVisibilityFriends": "仅好友可见",
|
||||||
"postVisibilityUnlisted": "不公开",
|
"postVisibilityUnlisted": "不公开",
|
||||||
"postVisibilityPrivate": "私密"
|
"postVisibilityPrivate": "私密",
|
||||||
|
"postTruncated": "内容已截断,点击查看完整帖子",
|
||||||
|
"chatNotifyLevel": "通知级别",
|
||||||
|
"chatNotifyLevelDescription": "决定您将收到多少通知。",
|
||||||
|
"chatNotifyLevelAll": "全部",
|
||||||
|
"chatNotifyLevelMention": "提及",
|
||||||
|
"chatNotifyLevelNone": "无",
|
||||||
|
"chatNotifyLevelUpdated": "通知级别已更新为 {}。",
|
||||||
|
"chatBreak": "暂停聊天",
|
||||||
|
"chatBreakDescription": "设置一个时间,在该时间之前,您的通知级别将仅为提及,以暂时休息当前讨论的话题。",
|
||||||
|
"chatBreakClear": "清除暂停时间",
|
||||||
|
"chatBreakHour": "暂停 {} 分钟",
|
||||||
|
"chatBreakDay": "暂停 {} 天",
|
||||||
|
"chatBreakSet": "已设置暂停 {}",
|
||||||
|
"chatBreakCleared": "聊天暂停已清除。",
|
||||||
|
"chatBreakCustom": "自定义时长",
|
||||||
|
"chatBreakEnterMinutes": "输入分钟数",
|
||||||
|
"chatBreakNone": "无",
|
||||||
|
"checkInResultT0": "大凶",
|
||||||
|
"checkInResultT1": "凶",
|
||||||
|
"checkInResultT2": "中平",
|
||||||
|
"checkInResultT3": "吉",
|
||||||
|
"checkInResultT4": "大吉",
|
||||||
|
"authenticating": "认证中...",
|
||||||
|
"processing": "处理中...",
|
||||||
|
"processingPayment": "处理付款中...",
|
||||||
|
"pleaseWait": "请稍候",
|
||||||
|
"paymentFailed": "付款失败,请重试。",
|
||||||
|
"paymentSuccess": "付款成功完成!"
|
||||||
}
|
}
|
@ -68,7 +68,7 @@
|
|||||||
"createRealmHint": "結識志同道合的朋友、建立社群等等。",
|
"createRealmHint": "結識志同道合的朋友、建立社群等等。",
|
||||||
"editRealm": "編輯領域",
|
"editRealm": "編輯領域",
|
||||||
"deleteRealm": "刪除領域",
|
"deleteRealm": "刪除領域",
|
||||||
"deleteRealmHint": "確定要刪除此領域嗎?這也將刪除此領域下的所有頻道、發佈者和貼文。",
|
"deleteRealmHint": "確定要刪除此領域嗎?這也將刪除該領域下的所有頻道、發佈者和貼文。",
|
||||||
"explore": "探索",
|
"explore": "探索",
|
||||||
"account": "帳號",
|
"account": "帳號",
|
||||||
"name": "名稱",
|
"name": "名稱",
|
||||||
@ -291,5 +291,21 @@
|
|||||||
"postVisibilityPublic": "公開",
|
"postVisibilityPublic": "公開",
|
||||||
"postVisibilityFriends": "僅好友可見",
|
"postVisibilityFriends": "僅好友可見",
|
||||||
"postVisibilityUnlisted": "不公開",
|
"postVisibilityUnlisted": "不公開",
|
||||||
"postVisibilityPrivate": "私密"
|
"postVisibilityPrivate": "私密",
|
||||||
|
"chatNotifyLevel": "通知等級",
|
||||||
|
"chatNotifyLevelDescription": "決定您將收到多少通知。",
|
||||||
|
"chatNotifyLevelAll": "全部",
|
||||||
|
"chatNotifyLevelMention": "提及",
|
||||||
|
"chatNotifyLevelNone": "無",
|
||||||
|
"chatNotifyLevelUpdated": "通知等級已更新為 {}。",
|
||||||
|
"chatBreak": "暫停聊天",
|
||||||
|
"chatBreakDescription": "設定一個時間,在該時間之前,您的通知等級將僅為提及,以暫時休息當前討論的話題。",
|
||||||
|
"chatBreakClear": "清除暫停時間",
|
||||||
|
"chatBreakHour": "暫停 {} 分鐘",
|
||||||
|
"chatBreakDay": "暫停 {} 天",
|
||||||
|
"chatBreakSet": "已設定暫停 {}",
|
||||||
|
"chatBreakCleared": "聊天暫停已清除。",
|
||||||
|
"chatBreakCustom": "自訂時長",
|
||||||
|
"chatBreakEnterMinutes": "輸入分鐘數",
|
||||||
|
"chatBreakNone": "無"
|
||||||
}
|
}
|
10
assets/images/oidc/afdian.svg
Normal file
10
assets/images/oidc/afdian.svg
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<svg
|
||||||
|
viewBox="0 0 160 160"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
d="M134.614 98.3714C133.294 97.5334 131.909 97.1697 130.563 97.02C133.724 89.3002 135.736 79.1949 128.887 69.1574C118.406 53.7998 103.38 45.8198 84.2346 45.4382C78.7809 45.3312 72.3517 45.5844 65.5487 45.8554C57.6493 46.1692 47.1369 46.5793 39.9921 45.9873C41.4161 45.2136 42.9326 44.4719 44.2462 43.8336C49.2728 41.384 53.2314 39.4763 51.9214 36.0925C51.2343 34.117 49.1874 33.0794 45.8233 33.0045C38.7426 32.8441 23.4421 36.9447 20.6903 43.8586C19.1418 47.7524 18.8854 55.2689 34.5668 61.9119C41.0174 64.6503 59.237 67.9879 66.2678 68.6867C68.2542 68.8793 69.7743 69.2822 70.9277 69.7101C69.3151 70.7727 67.6597 71.8888 65.9972 73.0298C63.1102 71.3824 58.3897 69.4391 54.8654 71.846C53.502 72.7695 52.7259 74.1316 52.6903 75.6827C52.6405 77.6117 53.8081 79.498 55.1217 81.017C49.9314 85.1639 45.7343 89.1825 44.2462 92.2811C42.5873 96.0893 41.9109 102.322 45.008 108.402C48.9382 116.118 57.6279 121.499 70.8423 124.394C88.1114 128.17 103.027 124.768 112.895 119.566C118.388 116.671 122.286 113.215 124.18 110.131C124.768 110.317 125.355 110.506 125.96 110.695C126.804 110.951 127.648 111.208 128.438 111.49C131.051 112.395 133.942 112.274 136.167 111.151C136.206 111.133 136.248 111.108 136.291 111.087C137.968 110.202 139.175 108.783 139.705 107.072C141.129 102.458 137.064 99.9082 134.614 98.3714ZM64.9999 90.6681C63.4307 90.6681 62.1621 91.9382 62.1621 93.5091C62.1621 95.0836 63.4307 96.3537 64.9999 96.3537C66.5691 96.3537 67.8378 95.0836 67.8378 93.5091C67.8378 91.9382 66.5691 90.6681 64.9999 90.6681ZM91.7568 99.1965C90.1876 99.1965 88.9189 100.467 88.9189 102.038C88.9189 103.612 90.1876 104.882 91.7568 104.882C93.326 104.882 94.5946 103.612 94.5946 102.038C94.5946 100.467 93.326 99.1965 91.7568 99.1965Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.8 KiB |
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.
134
ios/Podfile.lock
134
ios/Podfile.lock
@ -40,37 +40,37 @@ PODS:
|
|||||||
- file_picker (0.0.1):
|
- file_picker (0.0.1):
|
||||||
- DKImagePickerController/PhotoGallery
|
- DKImagePickerController/PhotoGallery
|
||||||
- Flutter
|
- Flutter
|
||||||
- Firebase/CoreOnly (11.10.0):
|
- Firebase/CoreOnly (11.13.0):
|
||||||
- FirebaseCore (~> 11.10.0)
|
- FirebaseCore (~> 11.13.0)
|
||||||
- Firebase/Messaging (11.10.0):
|
- Firebase/Messaging (11.13.0):
|
||||||
- Firebase/CoreOnly
|
- Firebase/CoreOnly
|
||||||
- FirebaseMessaging (~> 11.10.0)
|
- FirebaseMessaging (~> 11.13.0)
|
||||||
- firebase_core (3.13.1):
|
- firebase_core (3.14.0):
|
||||||
- Firebase/CoreOnly (= 11.10.0)
|
- Firebase/CoreOnly (= 11.13.0)
|
||||||
- Flutter
|
- Flutter
|
||||||
- firebase_messaging (15.2.6):
|
- firebase_messaging (15.2.7):
|
||||||
- Firebase/Messaging (= 11.10.0)
|
- Firebase/Messaging (= 11.13.0)
|
||||||
- firebase_core
|
- firebase_core
|
||||||
- Flutter
|
- Flutter
|
||||||
- FirebaseCore (11.10.0):
|
- FirebaseCore (11.13.0):
|
||||||
- FirebaseCoreInternal (~> 11.10.0)
|
- FirebaseCoreInternal (~> 11.13.0)
|
||||||
- GoogleUtilities/Environment (~> 8.0)
|
- GoogleUtilities/Environment (~> 8.1)
|
||||||
- GoogleUtilities/Logger (~> 8.0)
|
- GoogleUtilities/Logger (~> 8.1)
|
||||||
- FirebaseCoreInternal (11.10.0):
|
- FirebaseCoreInternal (11.13.0):
|
||||||
- "GoogleUtilities/NSData+zlib (~> 8.0)"
|
- "GoogleUtilities/NSData+zlib (~> 8.1)"
|
||||||
- FirebaseInstallations (11.10.0):
|
- FirebaseInstallations (11.13.0):
|
||||||
- FirebaseCore (~> 11.10.0)
|
- FirebaseCore (~> 11.13.0)
|
||||||
- GoogleUtilities/Environment (~> 8.0)
|
- GoogleUtilities/Environment (~> 8.1)
|
||||||
- GoogleUtilities/UserDefaults (~> 8.0)
|
- GoogleUtilities/UserDefaults (~> 8.1)
|
||||||
- PromisesObjC (~> 2.4)
|
- PromisesObjC (~> 2.4)
|
||||||
- FirebaseMessaging (11.10.0):
|
- FirebaseMessaging (11.13.0):
|
||||||
- FirebaseCore (~> 11.10.0)
|
- FirebaseCore (~> 11.13.0)
|
||||||
- FirebaseInstallations (~> 11.0)
|
- FirebaseInstallations (~> 11.0)
|
||||||
- GoogleDataTransport (~> 10.0)
|
- GoogleDataTransport (~> 10.0)
|
||||||
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
|
- GoogleUtilities/AppDelegateSwizzler (~> 8.1)
|
||||||
- GoogleUtilities/Environment (~> 8.0)
|
- GoogleUtilities/Environment (~> 8.1)
|
||||||
- GoogleUtilities/Reachability (~> 8.0)
|
- GoogleUtilities/Reachability (~> 8.1)
|
||||||
- GoogleUtilities/UserDefaults (~> 8.0)
|
- GoogleUtilities/UserDefaults (~> 8.1)
|
||||||
- nanopb (~> 3.30910.0)
|
- nanopb (~> 3.30910.0)
|
||||||
- Flutter (1.0.0)
|
- Flutter (1.0.0)
|
||||||
- flutter_inappwebview_ios (0.0.1):
|
- flutter_inappwebview_ios (0.0.1):
|
||||||
@ -84,6 +84,10 @@ PODS:
|
|||||||
- Flutter
|
- Flutter
|
||||||
- flutter_platform_alert (0.0.1):
|
- flutter_platform_alert (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
|
- flutter_secure_storage (3.3.1):
|
||||||
|
- Flutter
|
||||||
|
- flutter_timezone (0.0.1):
|
||||||
|
- Flutter
|
||||||
- flutter_udid (0.0.1):
|
- flutter_udid (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- SAMKeychain
|
- SAMKeychain
|
||||||
@ -92,6 +96,7 @@ PODS:
|
|||||||
- WebRTC-SDK (= 125.6422.07)
|
- WebRTC-SDK (= 125.6422.07)
|
||||||
- gal (1.0.0):
|
- gal (1.0.0):
|
||||||
- Flutter
|
- Flutter
|
||||||
|
- FlutterMacOS
|
||||||
- GoogleDataTransport (10.1.0):
|
- GoogleDataTransport (10.1.0):
|
||||||
- nanopb (~> 3.30910.0)
|
- nanopb (~> 3.30910.0)
|
||||||
- PromisesObjC (~> 2.4)
|
- PromisesObjC (~> 2.4)
|
||||||
@ -124,10 +129,13 @@ PODS:
|
|||||||
- irondash_engine_context (0.0.1):
|
- irondash_engine_context (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- Kingfisher (8.3.2)
|
- Kingfisher (8.3.2)
|
||||||
- livekit_client (2.4.7):
|
- livekit_client (2.4.8):
|
||||||
- Flutter
|
- Flutter
|
||||||
- flutter_webrtc
|
- flutter_webrtc
|
||||||
- WebRTC-SDK (= 125.6422.07)
|
- WebRTC-SDK (= 125.6422.07)
|
||||||
|
- local_auth_darwin (0.0.1):
|
||||||
|
- Flutter
|
||||||
|
- FlutterMacOS
|
||||||
- media_kit_libs_ios_video (1.0.4):
|
- media_kit_libs_ios_video (1.0.4):
|
||||||
- Flutter
|
- Flutter
|
||||||
- media_kit_video (0.0.1):
|
- media_kit_video (0.0.1):
|
||||||
@ -137,6 +145,8 @@ PODS:
|
|||||||
- nanopb/encode (= 3.30910.0)
|
- nanopb/encode (= 3.30910.0)
|
||||||
- nanopb/decode (3.30910.0)
|
- nanopb/decode (3.30910.0)
|
||||||
- nanopb/encode (3.30910.0)
|
- nanopb/encode (3.30910.0)
|
||||||
|
- native_exif (0.0.1):
|
||||||
|
- Flutter
|
||||||
- OrderedSet (6.0.3)
|
- OrderedSet (6.0.3)
|
||||||
- package_info_plus (0.4.5):
|
- package_info_plus (0.4.5):
|
||||||
- Flutter
|
- Flutter
|
||||||
@ -149,32 +159,34 @@ PODS:
|
|||||||
- record_ios (1.0.0):
|
- record_ios (1.0.0):
|
||||||
- Flutter
|
- Flutter
|
||||||
- SAMKeychain (1.5.3)
|
- SAMKeychain (1.5.3)
|
||||||
- SDWebImage (5.21.0):
|
- SDWebImage (5.21.1):
|
||||||
- SDWebImage/Core (= 5.21.0)
|
- SDWebImage/Core (= 5.21.1)
|
||||||
- SDWebImage/Core (5.21.0)
|
- SDWebImage/Core (5.21.1)
|
||||||
- shared_preferences_foundation (0.0.1):
|
- shared_preferences_foundation (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
|
- sign_in_with_apple (0.0.1):
|
||||||
|
- Flutter
|
||||||
- sqflite_darwin (0.0.4):
|
- sqflite_darwin (0.0.4):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- sqlite3 (3.49.2):
|
- sqlite3 (3.50.1):
|
||||||
- sqlite3/common (= 3.49.2)
|
- sqlite3/common (= 3.50.1)
|
||||||
- sqlite3/common (3.49.2)
|
- sqlite3/common (3.50.1)
|
||||||
- sqlite3/dbstatvtab (3.49.2):
|
- sqlite3/dbstatvtab (3.50.1):
|
||||||
- sqlite3/common
|
- sqlite3/common
|
||||||
- sqlite3/fts5 (3.49.2):
|
- sqlite3/fts5 (3.50.1):
|
||||||
- sqlite3/common
|
- sqlite3/common
|
||||||
- sqlite3/math (3.49.2):
|
- sqlite3/math (3.50.1):
|
||||||
- sqlite3/common
|
- sqlite3/common
|
||||||
- sqlite3/perf-threadsafe (3.49.2):
|
- sqlite3/perf-threadsafe (3.50.1):
|
||||||
- sqlite3/common
|
- sqlite3/common
|
||||||
- sqlite3/rtree (3.49.2):
|
- sqlite3/rtree (3.50.1):
|
||||||
- sqlite3/common
|
- sqlite3/common
|
||||||
- sqlite3_flutter_libs (0.0.1):
|
- sqlite3_flutter_libs (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- sqlite3 (~> 3.49.2)
|
- sqlite3 (~> 3.50.1)
|
||||||
- sqlite3/dbstatvtab
|
- sqlite3/dbstatvtab
|
||||||
- sqlite3/fts5
|
- sqlite3/fts5
|
||||||
- sqlite3/math
|
- sqlite3/math
|
||||||
@ -203,20 +215,25 @@ DEPENDENCIES:
|
|||||||
- flutter_inappwebview_ios (from `.symlinks/plugins/flutter_inappwebview_ios/ios`)
|
- flutter_inappwebview_ios (from `.symlinks/plugins/flutter_inappwebview_ios/ios`)
|
||||||
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
|
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
|
||||||
- flutter_platform_alert (from `.symlinks/plugins/flutter_platform_alert/ios`)
|
- flutter_platform_alert (from `.symlinks/plugins/flutter_platform_alert/ios`)
|
||||||
|
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
|
||||||
|
- flutter_timezone (from `.symlinks/plugins/flutter_timezone/ios`)
|
||||||
- flutter_udid (from `.symlinks/plugins/flutter_udid/ios`)
|
- flutter_udid (from `.symlinks/plugins/flutter_udid/ios`)
|
||||||
- flutter_webrtc (from `.symlinks/plugins/flutter_webrtc/ios`)
|
- flutter_webrtc (from `.symlinks/plugins/flutter_webrtc/ios`)
|
||||||
- gal (from `.symlinks/plugins/gal/ios`)
|
- gal (from `.symlinks/plugins/gal/darwin`)
|
||||||
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
|
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
|
||||||
- irondash_engine_context (from `.symlinks/plugins/irondash_engine_context/ios`)
|
- irondash_engine_context (from `.symlinks/plugins/irondash_engine_context/ios`)
|
||||||
- Kingfisher (~> 8.0)
|
- Kingfisher (~> 8.0)
|
||||||
- livekit_client (from `.symlinks/plugins/livekit_client/ios`)
|
- livekit_client (from `.symlinks/plugins/livekit_client/ios`)
|
||||||
|
- local_auth_darwin (from `.symlinks/plugins/local_auth_darwin/darwin`)
|
||||||
- media_kit_libs_ios_video (from `.symlinks/plugins/media_kit_libs_ios_video/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`)
|
- 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`)
|
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
||||||
- pasteboard (from `.symlinks/plugins/pasteboard/ios`)
|
- pasteboard (from `.symlinks/plugins/pasteboard/ios`)
|
||||||
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
||||||
- record_ios (from `.symlinks/plugins/record_ios/ios`)
|
- record_ios (from `.symlinks/plugins/record_ios/ios`)
|
||||||
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
- 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`)
|
- sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`)
|
||||||
- sqlite3_flutter_libs (from `.symlinks/plugins/sqlite3_flutter_libs/darwin`)
|
- sqlite3_flutter_libs (from `.symlinks/plugins/sqlite3_flutter_libs/darwin`)
|
||||||
- super_native_extensions (from `.symlinks/plugins/super_native_extensions/ios`)
|
- super_native_extensions (from `.symlinks/plugins/super_native_extensions/ios`)
|
||||||
@ -267,22 +284,30 @@ EXTERNAL SOURCES:
|
|||||||
:path: ".symlinks/plugins/flutter_native_splash/ios"
|
:path: ".symlinks/plugins/flutter_native_splash/ios"
|
||||||
flutter_platform_alert:
|
flutter_platform_alert:
|
||||||
:path: ".symlinks/plugins/flutter_platform_alert/ios"
|
:path: ".symlinks/plugins/flutter_platform_alert/ios"
|
||||||
|
flutter_secure_storage:
|
||||||
|
:path: ".symlinks/plugins/flutter_secure_storage/ios"
|
||||||
|
flutter_timezone:
|
||||||
|
:path: ".symlinks/plugins/flutter_timezone/ios"
|
||||||
flutter_udid:
|
flutter_udid:
|
||||||
:path: ".symlinks/plugins/flutter_udid/ios"
|
:path: ".symlinks/plugins/flutter_udid/ios"
|
||||||
flutter_webrtc:
|
flutter_webrtc:
|
||||||
:path: ".symlinks/plugins/flutter_webrtc/ios"
|
:path: ".symlinks/plugins/flutter_webrtc/ios"
|
||||||
gal:
|
gal:
|
||||||
:path: ".symlinks/plugins/gal/ios"
|
:path: ".symlinks/plugins/gal/darwin"
|
||||||
image_picker_ios:
|
image_picker_ios:
|
||||||
:path: ".symlinks/plugins/image_picker_ios/ios"
|
:path: ".symlinks/plugins/image_picker_ios/ios"
|
||||||
irondash_engine_context:
|
irondash_engine_context:
|
||||||
:path: ".symlinks/plugins/irondash_engine_context/ios"
|
:path: ".symlinks/plugins/irondash_engine_context/ios"
|
||||||
livekit_client:
|
livekit_client:
|
||||||
:path: ".symlinks/plugins/livekit_client/ios"
|
:path: ".symlinks/plugins/livekit_client/ios"
|
||||||
|
local_auth_darwin:
|
||||||
|
:path: ".symlinks/plugins/local_auth_darwin/darwin"
|
||||||
media_kit_libs_ios_video:
|
media_kit_libs_ios_video:
|
||||||
:path: ".symlinks/plugins/media_kit_libs_ios_video/ios"
|
:path: ".symlinks/plugins/media_kit_libs_ios_video/ios"
|
||||||
media_kit_video:
|
media_kit_video:
|
||||||
:path: ".symlinks/plugins/media_kit_video/ios"
|
:path: ".symlinks/plugins/media_kit_video/ios"
|
||||||
|
native_exif:
|
||||||
|
:path: ".symlinks/plugins/native_exif/ios"
|
||||||
package_info_plus:
|
package_info_plus:
|
||||||
:path: ".symlinks/plugins/package_info_plus/ios"
|
:path: ".symlinks/plugins/package_info_plus/ios"
|
||||||
pasteboard:
|
pasteboard:
|
||||||
@ -293,6 +318,8 @@ EXTERNAL SOURCES:
|
|||||||
:path: ".symlinks/plugins/record_ios/ios"
|
:path: ".symlinks/plugins/record_ios/ios"
|
||||||
shared_preferences_foundation:
|
shared_preferences_foundation:
|
||||||
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
|
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
|
||||||
|
sign_in_with_apple:
|
||||||
|
:path: ".symlinks/plugins/sign_in_with_apple/ios"
|
||||||
sqflite_darwin:
|
sqflite_darwin:
|
||||||
:path: ".symlinks/plugins/sqflite_darwin/darwin"
|
:path: ".symlinks/plugins/sqflite_darwin/darwin"
|
||||||
sqlite3_flutter_libs:
|
sqlite3_flutter_libs:
|
||||||
@ -314,29 +341,33 @@ SPEC CHECKSUMS:
|
|||||||
DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
|
DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
|
||||||
DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
|
DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
|
||||||
file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be
|
file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be
|
||||||
Firebase: 1fe1c0a7d9aaea32efe01fbea5f0ebd8d70e53a2
|
Firebase: 3435bc66b4d494c2f22c79fd3aae4c1db6662327
|
||||||
firebase_core: ba71b44041571da878cb624ce0d80250bcbe58ad
|
firebase_core: 700bac7ed92bb754fd70fbf01d72b36ecdd6d450
|
||||||
firebase_messaging: 13129fe2ca166d1ed2d095062d76cee88943d067
|
firebase_messaging: 860c017fcfbb5e27c163062d1d3135388f3ef954
|
||||||
FirebaseCore: 8344daef5e2661eb004b177488d6f9f0f24251b7
|
FirebaseCore: c692c7f1c75305ab6aff2b367f25e11d73aa8bd0
|
||||||
FirebaseCoreInternal: ef4505d2afb1d0ebbc33162cb3795382904b5679
|
FirebaseCoreInternal: 29d7b3af4aaf0b8f3ed20b568c13df399b06f68c
|
||||||
FirebaseInstallations: 9980995bdd06ec8081dfb6ab364162bdd64245c3
|
FirebaseInstallations: 0ee9074f2c1e86561ace168ee1470dc67aabaf02
|
||||||
FirebaseMessaging: 2b9f56aa4ed286e1f0ce2ee1d413aabb8f9f5cb9
|
FirebaseMessaging: 195bbdb73e6ca1dbc76cd46e73f3552c084ef6e4
|
||||||
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
||||||
flutter_inappwebview_ios: b89ba3482b96fb25e00c967aae065701b66e9b99
|
flutter_inappwebview_ios: b89ba3482b96fb25e00c967aae065701b66e9b99
|
||||||
flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf
|
flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf
|
||||||
flutter_platform_alert: bf3b5fcd4ac14bd637e20527e9c471633071afd3
|
flutter_platform_alert: bf3b5fcd4ac14bd637e20527e9c471633071afd3
|
||||||
|
flutter_secure_storage: 50035aef357c5a8bdd67fd6bc81370d46efc4d16
|
||||||
|
flutter_timezone: 7c838e17ffd4645d261e87037e5bebf6d38fe544
|
||||||
flutter_udid: f7c3884e6ec2951efe4f9de082257fc77c4d15e9
|
flutter_udid: f7c3884e6ec2951efe4f9de082257fc77c4d15e9
|
||||||
flutter_webrtc: fd0d3bdef8766a0736dbbe2e5b7e85f1f3c52117
|
flutter_webrtc: fd0d3bdef8766a0736dbbe2e5b7e85f1f3c52117
|
||||||
gal: 29e711cd17bccb47f839d9b30afe9bc9750950b2
|
gal: baecd024ebfd13c441269ca7404792a7152fde89
|
||||||
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
|
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
|
||||||
GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
|
GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
|
||||||
image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a
|
image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a
|
||||||
irondash_engine_context: 8e58ca8e0212ee9d1c7dc6a42121849986c88486
|
irondash_engine_context: 8e58ca8e0212ee9d1c7dc6a42121849986c88486
|
||||||
Kingfisher: 0621d0ac0c78fecb19f6dc5303bde2b52abaf2f5
|
Kingfisher: 0621d0ac0c78fecb19f6dc5303bde2b52abaf2f5
|
||||||
livekit_client: c30950bf36aa4c0244dd5551b1818cd15f90ba32
|
livekit_client: 9e901890552514206e5ff828903ed271531da264
|
||||||
|
local_auth_darwin: 553ce4f9b16d3fdfeafce9cf042e7c9f77c1c391
|
||||||
media_kit_libs_ios_video: 5a18affdb97d1f5d466dc79988b13eff6c5e2854
|
media_kit_libs_ios_video: 5a18affdb97d1f5d466dc79988b13eff6c5e2854
|
||||||
media_kit_video: 1746e198cb697d1ffb734b1d05ec429d1fcd1474
|
media_kit_video: 1746e198cb697d1ffb734b1d05ec429d1fcd1474
|
||||||
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
|
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
|
||||||
|
native_exif: 0eb73d3d5b3ca892719228df8d2d1b13d1ae396c
|
||||||
OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94
|
OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94
|
||||||
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
|
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
|
||||||
pasteboard: 49088aeb6119d51f976a421db60d8e1ab079b63c
|
pasteboard: 49088aeb6119d51f976a421db60d8e1ab079b63c
|
||||||
@ -344,11 +375,12 @@ SPEC CHECKSUMS:
|
|||||||
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
|
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
|
||||||
record_ios: fee1c924aa4879b882ebca2b4bce6011bcfc3d8b
|
record_ios: fee1c924aa4879b882ebca2b4bce6011bcfc3d8b
|
||||||
SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c
|
SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c
|
||||||
SDWebImage: f84b0feeb08d2d11e6a9b843cb06d75ebf5b8868
|
SDWebImage: f29024626962457f3470184232766516dee8dfea
|
||||||
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
|
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
|
||||||
|
sign_in_with_apple: c5dcc141574c8c54d5ac99dd2163c0c72ad22418
|
||||||
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
|
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
|
||||||
sqlite3: 3c950dc86011117c307eb0b28c4a7bb449dce9f1
|
sqlite3: 1d85290c3321153511f6e900ede7a1608718bbd5
|
||||||
sqlite3_flutter_libs: 74334e3ef2dbdb7d37e50859bb45da43935779c4
|
sqlite3_flutter_libs: e7fc8c9ea2200ff3271f08f127842131746b70e2
|
||||||
super_native_extensions: b763c02dc3a8fd078389f410bf15149179020cb4
|
super_native_extensions: b763c02dc3a8fd078389f410bf15149179020cb4
|
||||||
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
|
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
|
||||||
url_launcher_ios: 694010445543906933d732453a59da0a173ae33d
|
url_launcher_ios: 694010445543906933d732453a59da0a173ae33d
|
||||||
|
@ -2,6 +2,14 @@
|
|||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
|
<key>CLIENT_ID</key>
|
||||||
|
<string>961776991058-stt7et4qvn3cpscl4r61gl1hnlatqkig.apps.googleusercontent.com</string>
|
||||||
|
<key>REVERSED_CLIENT_ID</key>
|
||||||
|
<string>com.googleusercontent.apps.961776991058-stt7et4qvn3cpscl4r61gl1hnlatqkig</string>
|
||||||
|
<key>PLIST_VERSION</key>
|
||||||
|
<string>1</string>
|
||||||
|
<key>BUNDLE_ID</key>
|
||||||
|
<string>dev.solsynth.solian</string>
|
||||||
<key>ITSAppUsesNonExemptEncryption</key>
|
<key>ITSAppUsesNonExemptEncryption</key>
|
||||||
<false/>
|
<false/>
|
||||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||||
|
@ -4,6 +4,16 @@
|
|||||||
<dict>
|
<dict>
|
||||||
<key>aps-environment</key>
|
<key>aps-environment</key>
|
||||||
<string>development</string>
|
<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>
|
<key>com.apple.developer.usernotifications.communication</key>
|
||||||
<true/>
|
<true/>
|
||||||
</dict>
|
</dict>
|
||||||
|
@ -299,7 +299,7 @@ class MessageRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<LocalChatMessage> retryMessage(String pendingMessageId) async {
|
Future<LocalChatMessage> retryMessage(String pendingMessageId) async {
|
||||||
final message = pendingMessages[pendingMessageId];
|
final message = await getMessageById(pendingMessageId);
|
||||||
if (message == null) {
|
if (message == null) {
|
||||||
throw Exception('Message not found');
|
throw Exception('Message not found');
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import 'dart:io';
|
|||||||
import 'package:croppy/croppy.dart';
|
import 'package:croppy/croppy.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart' hide TextDirection;
|
import 'package:easy_localization/easy_localization.dart' hide TextDirection;
|
||||||
import 'package:firebase_core/firebase_core.dart';
|
import 'package:firebase_core/firebase_core.dart';
|
||||||
|
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
@ -17,14 +18,15 @@ import 'package:bitsdojo_window/bitsdojo_window.dart';
|
|||||||
import 'package:island/pods/userinfo.dart';
|
import 'package:island/pods/userinfo.dart';
|
||||||
import 'package:island/pods/websocket.dart';
|
import 'package:island/pods/websocket.dart';
|
||||||
import 'package:island/route.dart';
|
import 'package:island/route.dart';
|
||||||
import 'package:island/screens/auth/tabs.dart';
|
|
||||||
import 'package:island/services/notify.dart';
|
import 'package:island/services/notify.dart';
|
||||||
|
import 'package:island/services/timezone.dart';
|
||||||
import 'package:island/widgets/alert.dart';
|
import 'package:island/widgets/alert.dart';
|
||||||
import 'package:island/widgets/app_scaffold.dart';
|
import 'package:island/widgets/app_scaffold.dart';
|
||||||
import 'package:relative_time/relative_time.dart';
|
import 'package:relative_time/relative_time.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import 'package:image_picker_platform_interface/image_picker_platform_interface.dart';
|
import 'package:image_picker_platform_interface/image_picker_platform_interface.dart';
|
||||||
import 'package:flutter_native_splash/flutter_native_splash.dart';
|
import 'package:flutter_native_splash/flutter_native_splash.dart';
|
||||||
|
import 'package:url_launcher/url_launcher_string.dart';
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
final widgetsBinding = WidgetsFlutterBinding.ensureInitialized();
|
final widgetsBinding = WidgetsFlutterBinding.ensureInitialized();
|
||||||
@ -45,6 +47,14 @@ void main() async {
|
|||||||
showErrorAlert(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();
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
|
||||||
if (!kIsWeb && (Platform.isMacOS || Platform.isLinux || Platform.isWindows)) {
|
if (!kIsWeb && (Platform.isMacOS || Platform.isLinux || Platform.isWindows)) {
|
||||||
@ -93,7 +103,7 @@ void main() async {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final _appRouter = AppRouter();
|
final appRouter = AppRouter();
|
||||||
|
|
||||||
class IslandApp extends HookConsumerWidget {
|
class IslandApp extends HookConsumerWidget {
|
||||||
const IslandApp({super.key});
|
const IslandApp({super.key});
|
||||||
@ -102,6 +112,33 @@ class IslandApp extends HookConsumerWidget {
|
|||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final theme = ref.watch(themeProvider);
|
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(() {
|
useEffect(() {
|
||||||
// Load userinfo
|
// Load userinfo
|
||||||
final userNotifier = ref.read(userInfoProvider.notifier);
|
final userNotifier = ref.read(userInfoProvider.notifier);
|
||||||
@ -126,22 +163,13 @@ class IslandApp extends HookConsumerWidget {
|
|||||||
theme: theme?.light,
|
theme: theme?.light,
|
||||||
darkTheme: theme?.dark,
|
darkTheme: theme?.dark,
|
||||||
themeMode: ThemeMode.system,
|
themeMode: ThemeMode.system,
|
||||||
routerConfig: _appRouter.config(
|
routerConfig: appRouter.config(),
|
||||||
navigatorObservers:
|
|
||||||
() => [
|
|
||||||
TabNavigationObserver(
|
|
||||||
onChange: (route) {
|
|
||||||
ref.read(currentRouteProvider.notifier).state = route;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
supportedLocales: context.supportedLocales,
|
supportedLocales: context.supportedLocales,
|
||||||
localizationsDelegates: [
|
localizationsDelegates: [
|
||||||
...context.localizationDelegates,
|
...context.localizationDelegates,
|
||||||
CroppyLocalizations.delegate,
|
CroppyLocalizations.delegate,
|
||||||
RelativeTimeLocalizations.delegate,
|
RelativeTimeLocalizations.delegate,
|
||||||
], // this contains the cupertino one
|
],
|
||||||
locale: context.locale,
|
locale: context.locale,
|
||||||
builder: (context, child) {
|
builder: (context, child) {
|
||||||
return Overlay(
|
return Overlay(
|
||||||
@ -149,11 +177,8 @@ class IslandApp extends HookConsumerWidget {
|
|||||||
OverlayEntry(
|
OverlayEntry(
|
||||||
builder:
|
builder:
|
||||||
(_) => WindowScaffold(
|
(_) => WindowScaffold(
|
||||||
router: _appRouter,
|
router: appRouter,
|
||||||
child: TabsNavigationWidget(
|
child: child ?? const SizedBox.shrink(),
|
||||||
router: _appRouter,
|
|
||||||
child: child ?? const SizedBox.shrink(),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -10,13 +10,10 @@ sealed class SnActivity with _$SnActivity {
|
|||||||
required String id,
|
required String id,
|
||||||
required String type,
|
required String type,
|
||||||
required String resourceIdentifier,
|
required String resourceIdentifier,
|
||||||
required int visibility,
|
|
||||||
required String accountId,
|
|
||||||
required SnAccount account,
|
|
||||||
required dynamic data,
|
required dynamic data,
|
||||||
required DateTime createdAt,
|
required DateTime createdAt,
|
||||||
required DateTime updatedAt,
|
required DateTime updatedAt,
|
||||||
required dynamic deletedAt,
|
required DateTime? deletedAt,
|
||||||
}) = _SnActivity;
|
}) = _SnActivity;
|
||||||
|
|
||||||
factory SnActivity.fromJson(Map<String, dynamic> json) =>
|
factory SnActivity.fromJson(Map<String, dynamic> json) =>
|
||||||
|
@ -16,7 +16,7 @@ T _$identity<T>(T value) => value;
|
|||||||
/// @nodoc
|
/// @nodoc
|
||||||
mixin _$SnActivity {
|
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
|
/// Create a copy of SnActivity
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@ -29,16 +29,16 @@ $SnActivityCopyWith<SnActivity> get copyWith => _$SnActivityCopyWithImpl<SnActiv
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
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)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@override
|
@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
|
@override
|
||||||
String toString() {
|
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;
|
factory $SnActivityCopyWith(SnActivity value, $Res Function(SnActivity) _then) = _$SnActivityCopyWithImpl;
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({
|
$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
|
/// @nodoc
|
||||||
@ -66,31 +66,19 @@ class _$SnActivityCopyWithImpl<$Res>
|
|||||||
|
|
||||||
/// Create a copy of SnActivity
|
/// Create a copy of SnActivity
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? type = null,Object? 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(
|
return _then(_self.copyWith(
|
||||||
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||||
as String,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
|
as String,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
|
||||||
as String,resourceIdentifier: null == resourceIdentifier ? _self.resourceIdentifier : resourceIdentifier // 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 String,data: freezed == data ? _self.data : data // 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 dynamic,createdAt: null == createdAt ? _self.createdAt : createdAt // 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,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
|
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
|
||||||
as 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()
|
@JsonSerializable()
|
||||||
|
|
||||||
class _SnActivity implements SnActivity {
|
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);
|
factory _SnActivity.fromJson(Map<String, dynamic> json) => _$SnActivityFromJson(json);
|
||||||
|
|
||||||
@override final String id;
|
@override final String id;
|
||||||
@override final String type;
|
@override final String type;
|
||||||
@override final String resourceIdentifier;
|
@override final String resourceIdentifier;
|
||||||
@override final int visibility;
|
|
||||||
@override final String accountId;
|
|
||||||
@override final SnAccount account;
|
|
||||||
@override final dynamic data;
|
@override final dynamic data;
|
||||||
@override final DateTime createdAt;
|
@override final DateTime createdAt;
|
||||||
@override final DateTime updatedAt;
|
@override final DateTime updatedAt;
|
||||||
@override final dynamic deletedAt;
|
@override final DateTime? deletedAt;
|
||||||
|
|
||||||
/// Create a copy of SnActivity
|
/// Create a copy of SnActivity
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@ -125,16 +110,16 @@ Map<String, dynamic> toJson() {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
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)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@override
|
@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
|
@override
|
||||||
String toString() {
|
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;
|
factory _$SnActivityCopyWith(_SnActivity value, $Res Function(_SnActivity) _then) = __$SnActivityCopyWithImpl;
|
||||||
@override @useResult
|
@override @useResult
|
||||||
$Res call({
|
$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
|
/// @nodoc
|
||||||
@ -162,32 +147,20 @@ class __$SnActivityCopyWithImpl<$Res>
|
|||||||
|
|
||||||
/// Create a copy of SnActivity
|
/// Create a copy of SnActivity
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? type = null,Object? 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(
|
return _then(_SnActivity(
|
||||||
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||||
as String,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
|
as String,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
|
||||||
as String,resourceIdentifier: null == resourceIdentifier ? _self.resourceIdentifier : resourceIdentifier // 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 String,data: freezed == data ? _self.data : data // 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 dynamic,createdAt: null == createdAt ? _self.createdAt : createdAt // 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,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
|
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
|
||||||
as 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,
|
id: json['id'] as String,
|
||||||
type: json['type'] as String,
|
type: json['type'] as String,
|
||||||
resourceIdentifier: json['resource_identifier'] 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'],
|
data: json['data'],
|
||||||
createdAt: DateTime.parse(json['created_at'] as String),
|
createdAt: DateTime.parse(json['created_at'] as String),
|
||||||
updatedAt: DateTime.parse(json['updated_at'] as String),
|
updatedAt: DateTime.parse(json['updated_at'] as String),
|
||||||
deletedAt: json['deleted_at'],
|
deletedAt:
|
||||||
|
json['deleted_at'] == null
|
||||||
|
? null
|
||||||
|
: DateTime.parse(json['deleted_at'] as String),
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$SnActivityToJson(_SnActivity instance) =>
|
Map<String, dynamic> _$SnActivityToJson(_SnActivity instance) =>
|
||||||
@ -24,13 +24,10 @@ Map<String, dynamic> _$SnActivityToJson(_SnActivity instance) =>
|
|||||||
'id': instance.id,
|
'id': instance.id,
|
||||||
'type': instance.type,
|
'type': instance.type,
|
||||||
'resource_identifier': instance.resourceIdentifier,
|
'resource_identifier': instance.resourceIdentifier,
|
||||||
'visibility': instance.visibility,
|
|
||||||
'account_id': instance.accountId,
|
|
||||||
'account': instance.account.toJson(),
|
|
||||||
'data': instance.data,
|
'data': instance.data,
|
||||||
'created_at': instance.createdAt.toIso8601String(),
|
'created_at': instance.createdAt.toIso8601String(),
|
||||||
'updated_at': instance.updatedAt.toIso8601String(),
|
'updated_at': instance.updatedAt.toIso8601String(),
|
||||||
'deleted_at': instance.deletedAt,
|
'deleted_at': instance.deletedAt?.toIso8601String(),
|
||||||
};
|
};
|
||||||
|
|
||||||
_SnCheckInResult _$SnCheckInResultFromJson(Map<String, dynamic> json) =>
|
_SnCheckInResult _$SnCheckInResultFromJson(Map<String, dynamic> json) =>
|
||||||
|
@ -18,13 +18,18 @@ sealed class SnAuthChallenge with _$SnAuthChallenge {
|
|||||||
required DateTime expiredAt,
|
required DateTime expiredAt,
|
||||||
required int stepRemain,
|
required int stepRemain,
|
||||||
required int stepTotal,
|
required int stepTotal,
|
||||||
|
required int failedAttempts,
|
||||||
|
required int platform,
|
||||||
|
required int type,
|
||||||
required List<String> blacklistFactors,
|
required List<String> blacklistFactors,
|
||||||
required List<String> audiences,
|
required List<dynamic> audiences,
|
||||||
required List<String> scopes,
|
required List<dynamic> scopes,
|
||||||
required String ipAddress,
|
required String ipAddress,
|
||||||
required String userAgent,
|
required String userAgent,
|
||||||
required String? deviceId,
|
required String deviceId,
|
||||||
required String? nonce,
|
required String? nonce,
|
||||||
|
required String? location,
|
||||||
|
required String accountId,
|
||||||
required DateTime createdAt,
|
required DateTime createdAt,
|
||||||
required DateTime updatedAt,
|
required DateTime updatedAt,
|
||||||
required DateTime? deletedAt,
|
required DateTime? deletedAt,
|
||||||
@ -34,6 +39,25 @@ sealed class SnAuthChallenge with _$SnAuthChallenge {
|
|||||||
_$SnAuthChallengeFromJson(json);
|
_$SnAuthChallengeFromJson(json);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
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
|
@freezed
|
||||||
sealed class SnAuthFactor with _$SnAuthFactor {
|
sealed class SnAuthFactor with _$SnAuthFactor {
|
||||||
const factory SnAuthFactor({
|
const factory SnAuthFactor({
|
||||||
@ -42,8 +66,46 @@ sealed class SnAuthFactor with _$SnAuthFactor {
|
|||||||
required DateTime createdAt,
|
required DateTime createdAt,
|
||||||
required DateTime updatedAt,
|
required DateTime updatedAt,
|
||||||
required DateTime? deletedAt,
|
required DateTime? deletedAt,
|
||||||
|
required DateTime? expiredAt,
|
||||||
|
required DateTime? enabledAt,
|
||||||
|
required int trustworthy,
|
||||||
|
required Map<String, dynamic>? createdResponse,
|
||||||
}) = _SnAuthFactor;
|
}) = _SnAuthFactor;
|
||||||
|
|
||||||
factory SnAuthFactor.fromJson(Map<String, dynamic> json) =>
|
factory SnAuthFactor.fromJson(Map<String, dynamic> json) =>
|
||||||
_$SnAuthFactorFromJson(json);
|
_$SnAuthFactorFromJson(json);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
sealed class SnAuthDevice with _$SnAuthDevice {
|
||||||
|
const factory SnAuthDevice({
|
||||||
|
required dynamic label,
|
||||||
|
required String userAgent,
|
||||||
|
required String deviceId,
|
||||||
|
required int platform,
|
||||||
|
required List<SnAuthSession> sessions,
|
||||||
|
// Not from backend, used for UI
|
||||||
|
@Default(false) bool isCurrent,
|
||||||
|
}) = _SnAuthDevice;
|
||||||
|
|
||||||
|
factory SnAuthDevice.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$SnAuthDeviceFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
@ -149,7 +149,7 @@ as String,
|
|||||||
/// @nodoc
|
/// @nodoc
|
||||||
mixin _$SnAuthChallenge {
|
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
|
/// Create a copy of SnAuthChallenge
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@ -162,16 +162,16 @@ $SnAuthChallengeCopyWith<SnAuthChallenge> get copyWith => _$SnAuthChallengeCopyW
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnAuthChallenge&&(identical(other.id, id) || other.id == id)&&(identical(other.expiredAt, expiredAt) || other.expiredAt == expiredAt)&&(identical(other.stepRemain, stepRemain) || other.stepRemain == stepRemain)&&(identical(other.stepTotal, stepTotal) || other.stepTotal == stepTotal)&&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)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@override
|
@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
|
@override
|
||||||
String toString() {
|
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)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -182,7 +182,7 @@ abstract mixin class $SnAuthChallengeCopyWith<$Res> {
|
|||||||
factory $SnAuthChallengeCopyWith(SnAuthChallenge value, $Res Function(SnAuthChallenge) _then) = _$SnAuthChallengeCopyWithImpl;
|
factory $SnAuthChallengeCopyWith(SnAuthChallenge value, $Res Function(SnAuthChallenge) _then) = _$SnAuthChallengeCopyWithImpl;
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
String id, DateTime expiredAt, int stepRemain, int stepTotal, 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
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@ -199,20 +199,25 @@ class _$SnAuthChallengeCopyWithImpl<$Res>
|
|||||||
|
|
||||||
/// Create a copy of SnAuthChallenge
|
/// Create a copy of SnAuthChallenge
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? expiredAt = null,Object? stepRemain = null,Object? stepTotal = null,Object? 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(
|
return _then(_self.copyWith(
|
||||||
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||||
as String,expiredAt: null == expiredAt ? _self.expiredAt : expiredAt // ignore: cast_nullable_to_non_nullable
|
as String,expiredAt: null == expiredAt ? _self.expiredAt : expiredAt // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime,stepRemain: null == stepRemain ? _self.stepRemain : stepRemain // ignore: cast_nullable_to_non_nullable
|
as DateTime,stepRemain: null == stepRemain ? _self.stepRemain : stepRemain // ignore: cast_nullable_to_non_nullable
|
||||||
as int,stepTotal: null == stepTotal ? _self.stepTotal : stepTotal // ignore: cast_nullable_to_non_nullable
|
as int,stepTotal: null == stepTotal ? _self.stepTotal : stepTotal // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,failedAttempts: null == failedAttempts ? _self.failedAttempts : failedAttempts // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,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 int,blacklistFactors: null == blacklistFactors ? _self.blacklistFactors : blacklistFactors // ignore: cast_nullable_to_non_nullable
|
||||||
as List<String>,audiences: null == audiences ? _self.audiences : audiences // ignore: cast_nullable_to_non_nullable
|
as List<String>,audiences: null == audiences ? _self.audiences : audiences // ignore: cast_nullable_to_non_nullable
|
||||||
as List<String>,scopes: null == scopes ? _self.scopes : scopes // ignore: cast_nullable_to_non_nullable
|
as List<dynamic>,scopes: null == scopes ? _self.scopes : scopes // ignore: cast_nullable_to_non_nullable
|
||||||
as List<String>,ipAddress: null == ipAddress ? _self.ipAddress : ipAddress // ignore: cast_nullable_to_non_nullable
|
as List<dynamic>,ipAddress: null == ipAddress ? _self.ipAddress : ipAddress // ignore: cast_nullable_to_non_nullable
|
||||||
as String,userAgent: null == userAgent ? _self.userAgent : userAgent // ignore: cast_nullable_to_non_nullable
|
as String,userAgent: null == userAgent ? _self.userAgent : userAgent // ignore: cast_nullable_to_non_nullable
|
||||||
as String,deviceId: freezed == deviceId ? _self.deviceId : deviceId // ignore: cast_nullable_to_non_nullable
|
as String,deviceId: null == deviceId ? _self.deviceId : deviceId // ignore: cast_nullable_to_non_nullable
|
||||||
as String?,nonce: freezed == nonce ? _self.nonce : nonce // ignore: cast_nullable_to_non_nullable
|
as String,nonce: freezed == nonce ? _self.nonce : nonce // ignore: cast_nullable_to_non_nullable
|
||||||
as String?,createdAt: null == createdAt ? _self.createdAt : createdAt // 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,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
|
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime?,
|
as DateTime?,
|
||||||
@ -226,13 +231,16 @@ as DateTime?,
|
|||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
|
|
||||||
class _SnAuthChallenge implements SnAuthChallenge {
|
class _SnAuthChallenge implements SnAuthChallenge {
|
||||||
const _SnAuthChallenge({required this.id, required this.expiredAt, required this.stepRemain, required this.stepTotal, required 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);
|
factory _SnAuthChallenge.fromJson(Map<String, dynamic> json) => _$SnAuthChallengeFromJson(json);
|
||||||
|
|
||||||
@override final String id;
|
@override final String id;
|
||||||
@override final DateTime expiredAt;
|
@override final DateTime expiredAt;
|
||||||
@override final int stepRemain;
|
@override final int stepRemain;
|
||||||
@override final int stepTotal;
|
@override final int stepTotal;
|
||||||
|
@override final int failedAttempts;
|
||||||
|
@override final int platform;
|
||||||
|
@override final int type;
|
||||||
final List<String> _blacklistFactors;
|
final List<String> _blacklistFactors;
|
||||||
@override List<String> get blacklistFactors {
|
@override List<String> get blacklistFactors {
|
||||||
if (_blacklistFactors is EqualUnmodifiableListView) return _blacklistFactors;
|
if (_blacklistFactors is EqualUnmodifiableListView) return _blacklistFactors;
|
||||||
@ -240,15 +248,15 @@ class _SnAuthChallenge implements SnAuthChallenge {
|
|||||||
return EqualUnmodifiableListView(_blacklistFactors);
|
return EqualUnmodifiableListView(_blacklistFactors);
|
||||||
}
|
}
|
||||||
|
|
||||||
final List<String> _audiences;
|
final List<dynamic> _audiences;
|
||||||
@override List<String> get audiences {
|
@override List<dynamic> get audiences {
|
||||||
if (_audiences is EqualUnmodifiableListView) return _audiences;
|
if (_audiences is EqualUnmodifiableListView) return _audiences;
|
||||||
// ignore: implicit_dynamic_type
|
// ignore: implicit_dynamic_type
|
||||||
return EqualUnmodifiableListView(_audiences);
|
return EqualUnmodifiableListView(_audiences);
|
||||||
}
|
}
|
||||||
|
|
||||||
final List<String> _scopes;
|
final List<dynamic> _scopes;
|
||||||
@override List<String> get scopes {
|
@override List<dynamic> get scopes {
|
||||||
if (_scopes is EqualUnmodifiableListView) return _scopes;
|
if (_scopes is EqualUnmodifiableListView) return _scopes;
|
||||||
// ignore: implicit_dynamic_type
|
// ignore: implicit_dynamic_type
|
||||||
return EqualUnmodifiableListView(_scopes);
|
return EqualUnmodifiableListView(_scopes);
|
||||||
@ -256,8 +264,10 @@ class _SnAuthChallenge implements SnAuthChallenge {
|
|||||||
|
|
||||||
@override final String ipAddress;
|
@override final String ipAddress;
|
||||||
@override final String userAgent;
|
@override final String userAgent;
|
||||||
@override final String? deviceId;
|
@override final String deviceId;
|
||||||
@override final String? nonce;
|
@override final String? nonce;
|
||||||
|
@override final String? location;
|
||||||
|
@override final String accountId;
|
||||||
@override final DateTime createdAt;
|
@override final DateTime createdAt;
|
||||||
@override final DateTime updatedAt;
|
@override final DateTime updatedAt;
|
||||||
@override final DateTime? deletedAt;
|
@override final DateTime? deletedAt;
|
||||||
@ -275,16 +285,16 @@ Map<String, dynamic> toJson() {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnAuthChallenge&&(identical(other.id, id) || other.id == id)&&(identical(other.expiredAt, expiredAt) || other.expiredAt == expiredAt)&&(identical(other.stepRemain, stepRemain) || other.stepRemain == stepRemain)&&(identical(other.stepTotal, stepTotal) || other.stepTotal == stepTotal)&&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)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@override
|
@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
|
@override
|
||||||
String toString() {
|
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)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -295,7 +305,7 @@ abstract mixin class _$SnAuthChallengeCopyWith<$Res> implements $SnAuthChallenge
|
|||||||
factory _$SnAuthChallengeCopyWith(_SnAuthChallenge value, $Res Function(_SnAuthChallenge) _then) = __$SnAuthChallengeCopyWithImpl;
|
factory _$SnAuthChallengeCopyWith(_SnAuthChallenge value, $Res Function(_SnAuthChallenge) _then) = __$SnAuthChallengeCopyWithImpl;
|
||||||
@override @useResult
|
@override @useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
String id, DateTime expiredAt, int stepRemain, int stepTotal, 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
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@ -312,20 +322,25 @@ class __$SnAuthChallengeCopyWithImpl<$Res>
|
|||||||
|
|
||||||
/// Create a copy of SnAuthChallenge
|
/// Create a copy of SnAuthChallenge
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? expiredAt = null,Object? stepRemain = null,Object? stepTotal = null,Object? 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(
|
return _then(_SnAuthChallenge(
|
||||||
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||||
as String,expiredAt: null == expiredAt ? _self.expiredAt : expiredAt // ignore: cast_nullable_to_non_nullable
|
as String,expiredAt: null == expiredAt ? _self.expiredAt : expiredAt // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime,stepRemain: null == stepRemain ? _self.stepRemain : stepRemain // ignore: cast_nullable_to_non_nullable
|
as DateTime,stepRemain: null == stepRemain ? _self.stepRemain : stepRemain // ignore: cast_nullable_to_non_nullable
|
||||||
as int,stepTotal: null == stepTotal ? _self.stepTotal : stepTotal // ignore: cast_nullable_to_non_nullable
|
as int,stepTotal: null == stepTotal ? _self.stepTotal : stepTotal // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,failedAttempts: null == failedAttempts ? _self.failedAttempts : failedAttempts // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,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 int,blacklistFactors: null == blacklistFactors ? _self._blacklistFactors : blacklistFactors // ignore: cast_nullable_to_non_nullable
|
||||||
as List<String>,audiences: null == audiences ? _self._audiences : audiences // ignore: cast_nullable_to_non_nullable
|
as List<String>,audiences: null == audiences ? _self._audiences : audiences // ignore: cast_nullable_to_non_nullable
|
||||||
as List<String>,scopes: null == scopes ? _self._scopes : scopes // ignore: cast_nullable_to_non_nullable
|
as List<dynamic>,scopes: null == scopes ? _self._scopes : scopes // ignore: cast_nullable_to_non_nullable
|
||||||
as List<String>,ipAddress: null == ipAddress ? _self.ipAddress : ipAddress // ignore: cast_nullable_to_non_nullable
|
as List<dynamic>,ipAddress: null == ipAddress ? _self.ipAddress : ipAddress // ignore: cast_nullable_to_non_nullable
|
||||||
as String,userAgent: null == userAgent ? _self.userAgent : userAgent // ignore: cast_nullable_to_non_nullable
|
as String,userAgent: null == userAgent ? _self.userAgent : userAgent // ignore: cast_nullable_to_non_nullable
|
||||||
as String,deviceId: freezed == deviceId ? _self.deviceId : deviceId // ignore: cast_nullable_to_non_nullable
|
as String,deviceId: null == deviceId ? _self.deviceId : deviceId // ignore: cast_nullable_to_non_nullable
|
||||||
as String?,nonce: freezed == nonce ? _self.nonce : nonce // ignore: cast_nullable_to_non_nullable
|
as String,nonce: freezed == nonce ? _self.nonce : nonce // ignore: cast_nullable_to_non_nullable
|
||||||
as String?,createdAt: null == createdAt ? _self.createdAt : createdAt // 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,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
|
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime?,
|
as DateTime?,
|
||||||
@ -336,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
|
/// @nodoc
|
||||||
mixin _$SnAuthFactor {
|
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
|
/// Create a copy of SnAuthFactor
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@ -352,16 +545,16 @@ $SnAuthFactorCopyWith<SnAuthFactor> get copyWith => _$SnAuthFactorCopyWithImpl<S
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
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)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@override
|
@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
|
@override
|
||||||
String toString() {
|
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)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -372,7 +565,7 @@ abstract mixin class $SnAuthFactorCopyWith<$Res> {
|
|||||||
factory $SnAuthFactorCopyWith(SnAuthFactor value, $Res Function(SnAuthFactor) _then) = _$SnAuthFactorCopyWithImpl;
|
factory $SnAuthFactorCopyWith(SnAuthFactor value, $Res Function(SnAuthFactor) _then) = _$SnAuthFactorCopyWithImpl;
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({
|
$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
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@ -389,14 +582,18 @@ class _$SnAuthFactorCopyWithImpl<$Res>
|
|||||||
|
|
||||||
/// Create a copy of SnAuthFactor
|
/// Create a copy of SnAuthFactor
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? type = null,Object? 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(
|
return _then(_self.copyWith(
|
||||||
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||||
as String,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
|
as String,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
|
||||||
as int,createdAt: null == createdAt ? _self.createdAt : createdAt // 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,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
|
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime?,
|
as DateTime?,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>?,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -407,7 +604,7 @@ as DateTime?,
|
|||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
|
|
||||||
class _SnAuthFactor implements SnAuthFactor {
|
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);
|
factory _SnAuthFactor.fromJson(Map<String, dynamic> json) => _$SnAuthFactorFromJson(json);
|
||||||
|
|
||||||
@override final String id;
|
@override final String id;
|
||||||
@ -415,6 +612,18 @@ class _SnAuthFactor implements SnAuthFactor {
|
|||||||
@override final DateTime createdAt;
|
@override final DateTime createdAt;
|
||||||
@override final DateTime updatedAt;
|
@override final DateTime updatedAt;
|
||||||
@override final DateTime? deletedAt;
|
@override final DateTime? deletedAt;
|
||||||
|
@override final 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
|
/// Create a copy of SnAuthFactor
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@ -429,16 +638,16 @@ Map<String, dynamic> toJson() {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
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)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@override
|
@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
|
@override
|
||||||
String toString() {
|
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)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -449,7 +658,7 @@ abstract mixin class _$SnAuthFactorCopyWith<$Res> implements $SnAuthFactorCopyWi
|
|||||||
factory _$SnAuthFactorCopyWith(_SnAuthFactor value, $Res Function(_SnAuthFactor) _then) = __$SnAuthFactorCopyWithImpl;
|
factory _$SnAuthFactorCopyWith(_SnAuthFactor value, $Res Function(_SnAuthFactor) _then) = __$SnAuthFactorCopyWithImpl;
|
||||||
@override @useResult
|
@override @useResult
|
||||||
$Res call({
|
$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
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@ -466,13 +675,336 @@ class __$SnAuthFactorCopyWithImpl<$Res>
|
|||||||
|
|
||||||
/// Create a copy of SnAuthFactor
|
/// Create a copy of SnAuthFactor
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? type = null,Object? 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(
|
return _then(_SnAuthFactor(
|
||||||
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||||
as String,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
|
as String,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
|
||||||
as int,createdAt: null == createdAt ? _self.createdAt : createdAt // 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,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
|
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime?,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?,
|
as DateTime?,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
@ -19,18 +19,21 @@ _SnAuthChallenge _$SnAuthChallengeFromJson(Map<String, dynamic> json) =>
|
|||||||
expiredAt: DateTime.parse(json['expired_at'] as String),
|
expiredAt: DateTime.parse(json['expired_at'] as String),
|
||||||
stepRemain: (json['step_remain'] as num).toInt(),
|
stepRemain: (json['step_remain'] as num).toInt(),
|
||||||
stepTotal: (json['step_total'] as num).toInt(),
|
stepTotal: (json['step_total'] as num).toInt(),
|
||||||
|
failedAttempts: (json['failed_attempts'] as num).toInt(),
|
||||||
|
platform: (json['platform'] as num).toInt(),
|
||||||
|
type: (json['type'] as num).toInt(),
|
||||||
blacklistFactors:
|
blacklistFactors:
|
||||||
(json['blacklist_factors'] as List<dynamic>)
|
(json['blacklist_factors'] as List<dynamic>)
|
||||||
.map((e) => e as String)
|
.map((e) => e as String)
|
||||||
.toList(),
|
.toList(),
|
||||||
audiences:
|
audiences: json['audiences'] as List<dynamic>,
|
||||||
(json['audiences'] as List<dynamic>).map((e) => e as String).toList(),
|
scopes: json['scopes'] as List<dynamic>,
|
||||||
scopes:
|
|
||||||
(json['scopes'] as List<dynamic>).map((e) => e as String).toList(),
|
|
||||||
ipAddress: json['ip_address'] as String,
|
ipAddress: json['ip_address'] as String,
|
||||||
userAgent: json['user_agent'] as String,
|
userAgent: json['user_agent'] as String,
|
||||||
deviceId: json['device_id'] as String?,
|
deviceId: json['device_id'] as String,
|
||||||
nonce: json['nonce'] 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),
|
createdAt: DateTime.parse(json['created_at'] as String),
|
||||||
updatedAt: DateTime.parse(json['updated_at'] as String),
|
updatedAt: DateTime.parse(json['updated_at'] as String),
|
||||||
deletedAt:
|
deletedAt:
|
||||||
@ -45,6 +48,9 @@ Map<String, dynamic> _$SnAuthChallengeToJson(_SnAuthChallenge instance) =>
|
|||||||
'expired_at': instance.expiredAt.toIso8601String(),
|
'expired_at': instance.expiredAt.toIso8601String(),
|
||||||
'step_remain': instance.stepRemain,
|
'step_remain': instance.stepRemain,
|
||||||
'step_total': instance.stepTotal,
|
'step_total': instance.stepTotal,
|
||||||
|
'failed_attempts': instance.failedAttempts,
|
||||||
|
'platform': instance.platform,
|
||||||
|
'type': instance.type,
|
||||||
'blacklist_factors': instance.blacklistFactors,
|
'blacklist_factors': instance.blacklistFactors,
|
||||||
'audiences': instance.audiences,
|
'audiences': instance.audiences,
|
||||||
'scopes': instance.scopes,
|
'scopes': instance.scopes,
|
||||||
@ -52,6 +58,41 @@ Map<String, dynamic> _$SnAuthChallengeToJson(_SnAuthChallenge instance) =>
|
|||||||
'user_agent': instance.userAgent,
|
'user_agent': instance.userAgent,
|
||||||
'device_id': instance.deviceId,
|
'device_id': instance.deviceId,
|
||||||
'nonce': instance.nonce,
|
'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(),
|
'created_at': instance.createdAt.toIso8601String(),
|
||||||
'updated_at': instance.updatedAt.toIso8601String(),
|
'updated_at': instance.updatedAt.toIso8601String(),
|
||||||
'deleted_at': instance.deletedAt?.toIso8601String(),
|
'deleted_at': instance.deletedAt?.toIso8601String(),
|
||||||
@ -67,6 +108,16 @@ _SnAuthFactor _$SnAuthFactorFromJson(Map<String, dynamic> json) =>
|
|||||||
json['deleted_at'] == null
|
json['deleted_at'] == null
|
||||||
? null
|
? null
|
||||||
: DateTime.parse(json['deleted_at'] as String),
|
: DateTime.parse(json['deleted_at'] as String),
|
||||||
|
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) =>
|
Map<String, dynamic> _$SnAuthFactorToJson(_SnAuthFactor instance) =>
|
||||||
@ -76,4 +127,61 @@ Map<String, dynamic> _$SnAuthFactorToJson(_SnAuthFactor instance) =>
|
|||||||
'created_at': instance.createdAt.toIso8601String(),
|
'created_at': instance.createdAt.toIso8601String(),
|
||||||
'updated_at': instance.updatedAt.toIso8601String(),
|
'updated_at': instance.updatedAt.toIso8601String(),
|
||||||
'deleted_at': instance.deletedAt?.toIso8601String(),
|
'deleted_at': instance.deletedAt?.toIso8601String(),
|
||||||
|
'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(),
|
||||||
|
};
|
||||||
|
@ -87,7 +87,11 @@ sealed class SnChatMember with _$SnChatMember {
|
|||||||
required int role,
|
required int role,
|
||||||
required int notify,
|
required int notify,
|
||||||
required DateTime? joinedAt,
|
required DateTime? joinedAt,
|
||||||
|
required DateTime? breakUntil,
|
||||||
|
required DateTime? timeoutUntil,
|
||||||
required bool isBot,
|
required bool isBot,
|
||||||
|
// Frontend data
|
||||||
|
DateTime? lastTyped,
|
||||||
}) = _SnChatMember;
|
}) = _SnChatMember;
|
||||||
|
|
||||||
factory SnChatMember.fromJson(Map<String, dynamic> json) =>
|
factory SnChatMember.fromJson(Map<String, dynamic> json) =>
|
||||||
|
@ -663,7 +663,8 @@ $SnChatMemberCopyWith<$Res> get sender {
|
|||||||
/// @nodoc
|
/// @nodoc
|
||||||
mixin _$SnChatMember {
|
mixin _$SnChatMember {
|
||||||
|
|
||||||
DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; String get id; String get chatRoomId; SnChatRoom? get chatRoom; String get accountId; SnAccount get account; String? get nick; int get role; int get notify; DateTime? get joinedAt; 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
|
/// Create a copy of SnChatMember
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@ -676,16 +677,16 @@ $SnChatMemberCopyWith<SnChatMember> get copyWith => _$SnChatMemberCopyWithImpl<S
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnChatMember&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.id, id) || other.id == id)&&(identical(other.chatRoomId, chatRoomId) || other.chatRoomId == chatRoomId)&&(identical(other.chatRoom, chatRoom) || other.chatRoom == chatRoom)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.account, account) || other.account == account)&&(identical(other.nick, nick) || other.nick == nick)&&(identical(other.role, role) || other.role == role)&&(identical(other.notify, notify) || other.notify == notify)&&(identical(other.joinedAt, joinedAt) || other.joinedAt == joinedAt)&&(identical(other.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)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(runtimeType,createdAt,updatedAt,deletedAt,id,chatRoomId,chatRoom,accountId,account,nick,role,notify,joinedAt,isBot);
|
int get hashCode => Object.hash(runtimeType,createdAt,updatedAt,deletedAt,id,chatRoomId,chatRoom,accountId,account,nick,role,notify,joinedAt,breakUntil,timeoutUntil,isBot,lastTyped);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'SnChatMember(createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, id: $id, chatRoomId: $chatRoomId, chatRoom: $chatRoom, accountId: $accountId, account: $account, nick: $nick, role: $role, notify: $notify, joinedAt: $joinedAt, 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)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -696,7 +697,7 @@ abstract mixin class $SnChatMemberCopyWith<$Res> {
|
|||||||
factory $SnChatMemberCopyWith(SnChatMember value, $Res Function(SnChatMember) _then) = _$SnChatMemberCopyWithImpl;
|
factory $SnChatMemberCopyWith(SnChatMember value, $Res Function(SnChatMember) _then) = _$SnChatMemberCopyWithImpl;
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, String id, String chatRoomId, SnChatRoom? chatRoom, String accountId, SnAccount account, String? nick, int role, int notify, DateTime? joinedAt, 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
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@ -713,7 +714,7 @@ class _$SnChatMemberCopyWithImpl<$Res>
|
|||||||
|
|
||||||
/// Create a copy of SnChatMember
|
/// Create a copy of SnChatMember
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@pragma('vm:prefer-inline') @override $Res call({Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? id = null,Object? chatRoomId = null,Object? chatRoom = freezed,Object? accountId = null,Object? account = null,Object? nick = freezed,Object? role = null,Object? notify = null,Object? joinedAt = freezed,Object? 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(
|
return _then(_self.copyWith(
|
||||||
createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
||||||
@ -727,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 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,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 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 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
|
/// Create a copy of SnChatMember
|
||||||
@ -760,7 +764,7 @@ $SnAccountCopyWith<$Res> get account {
|
|||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
|
|
||||||
class _SnChatMember implements SnChatMember {
|
class _SnChatMember implements SnChatMember {
|
||||||
const _SnChatMember({required this.createdAt, required this.updatedAt, required this.deletedAt, required this.id, required this.chatRoomId, required this.chatRoom, required this.accountId, required this.account, required this.nick, required this.role, required this.notify, required this.joinedAt, required this.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);
|
factory _SnChatMember.fromJson(Map<String, dynamic> json) => _$SnChatMemberFromJson(json);
|
||||||
|
|
||||||
@override final DateTime createdAt;
|
@override final DateTime createdAt;
|
||||||
@ -775,7 +779,11 @@ class _SnChatMember implements SnChatMember {
|
|||||||
@override final int role;
|
@override final int role;
|
||||||
@override final int notify;
|
@override final int notify;
|
||||||
@override final DateTime? joinedAt;
|
@override final DateTime? joinedAt;
|
||||||
|
@override final DateTime? breakUntil;
|
||||||
|
@override final DateTime? timeoutUntil;
|
||||||
@override final bool isBot;
|
@override final bool isBot;
|
||||||
|
// Frontend data
|
||||||
|
@override final DateTime? lastTyped;
|
||||||
|
|
||||||
/// Create a copy of SnChatMember
|
/// Create a copy of SnChatMember
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@ -790,16 +798,16 @@ Map<String, dynamic> toJson() {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnChatMember&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.id, id) || other.id == id)&&(identical(other.chatRoomId, chatRoomId) || other.chatRoomId == chatRoomId)&&(identical(other.chatRoom, chatRoom) || other.chatRoom == chatRoom)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.account, account) || other.account == account)&&(identical(other.nick, nick) || other.nick == nick)&&(identical(other.role, role) || other.role == role)&&(identical(other.notify, notify) || other.notify == notify)&&(identical(other.joinedAt, joinedAt) || other.joinedAt == joinedAt)&&(identical(other.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)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(runtimeType,createdAt,updatedAt,deletedAt,id,chatRoomId,chatRoom,accountId,account,nick,role,notify,joinedAt,isBot);
|
int get hashCode => Object.hash(runtimeType,createdAt,updatedAt,deletedAt,id,chatRoomId,chatRoom,accountId,account,nick,role,notify,joinedAt,breakUntil,timeoutUntil,isBot,lastTyped);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'SnChatMember(createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, id: $id, chatRoomId: $chatRoomId, chatRoom: $chatRoom, accountId: $accountId, account: $account, nick: $nick, role: $role, notify: $notify, joinedAt: $joinedAt, 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)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -810,7 +818,7 @@ abstract mixin class _$SnChatMemberCopyWith<$Res> implements $SnChatMemberCopyWi
|
|||||||
factory _$SnChatMemberCopyWith(_SnChatMember value, $Res Function(_SnChatMember) _then) = __$SnChatMemberCopyWithImpl;
|
factory _$SnChatMemberCopyWith(_SnChatMember value, $Res Function(_SnChatMember) _then) = __$SnChatMemberCopyWithImpl;
|
||||||
@override @useResult
|
@override @useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, String id, String chatRoomId, SnChatRoom? chatRoom, String accountId, SnAccount account, String? nick, int role, int notify, DateTime? joinedAt, 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
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@ -827,7 +835,7 @@ class __$SnChatMemberCopyWithImpl<$Res>
|
|||||||
|
|
||||||
/// Create a copy of SnChatMember
|
/// Create a copy of SnChatMember
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@override @pragma('vm:prefer-inline') $Res call({Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? id = null,Object? chatRoomId = null,Object? chatRoom = freezed,Object? accountId = null,Object? account = null,Object? nick = freezed,Object? role = null,Object? notify = null,Object? joinedAt = freezed,Object? 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(
|
return _then(_SnChatMember(
|
||||||
createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
||||||
@ -841,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 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,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 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 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?,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -166,7 +166,19 @@ _SnChatMember _$SnChatMemberFromJson(Map<String, dynamic> json) =>
|
|||||||
json['joined_at'] == null
|
json['joined_at'] == null
|
||||||
? null
|
? null
|
||||||
: DateTime.parse(json['joined_at'] as String),
|
: 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,
|
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) =>
|
Map<String, dynamic> _$SnChatMemberToJson(_SnChatMember instance) =>
|
||||||
@ -183,7 +195,10 @@ Map<String, dynamic> _$SnChatMemberToJson(_SnChatMember instance) =>
|
|||||||
'role': instance.role,
|
'role': instance.role,
|
||||||
'notify': instance.notify,
|
'notify': instance.notify,
|
||||||
'joined_at': instance.joinedAt?.toIso8601String(),
|
'joined_at': instance.joinedAt?.toIso8601String(),
|
||||||
|
'break_until': instance.breakUntil?.toIso8601String(),
|
||||||
|
'timeout_until': instance.timeoutUntil?.toIso8601String(),
|
||||||
'is_bot': instance.isBot,
|
'is_bot': instance.isBot,
|
||||||
|
'last_typed': instance.lastTyped?.toIso8601String(),
|
||||||
};
|
};
|
||||||
|
|
||||||
_SnChatSummary _$SnChatSummaryFromJson(Map<String, dynamic> json) =>
|
_SnChatSummary _$SnChatSummaryFromJson(Map<String, dynamic> json) =>
|
||||||
|
23
lib/models/embed.dart
Normal file
23
lib/models/embed.dart
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
|
part 'embed.freezed.dart';
|
||||||
|
part 'embed.g.dart';
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
sealed class SnEmbedLink with _$SnEmbedLink {
|
||||||
|
const factory SnEmbedLink({
|
||||||
|
@JsonKey(name: 'Type') required String type,
|
||||||
|
@JsonKey(name: 'Url') required String url,
|
||||||
|
@JsonKey(name: 'Title') required String title,
|
||||||
|
@JsonKey(name: 'Description') required String? description,
|
||||||
|
@JsonKey(name: 'ImageUrl') required String? imageUrl,
|
||||||
|
@JsonKey(name: 'FaviconUrl') required String faviconUrl,
|
||||||
|
@JsonKey(name: 'SiteName') required String siteName,
|
||||||
|
@JsonKey(name: 'ContentType') required String? contentType,
|
||||||
|
@JsonKey(name: 'Author') required String? author,
|
||||||
|
@JsonKey(name: 'PublishedDate') required DateTime? publishedDate,
|
||||||
|
}) = _SnEmbedLink;
|
||||||
|
|
||||||
|
factory SnEmbedLink.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$SnEmbedLinkFromJson(json);
|
||||||
|
}
|
175
lib/models/embed.freezed.dart
Normal file
175
lib/models/embed.freezed.dart
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
// 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 'embed.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// FreezedGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
// dart format off
|
||||||
|
T _$identity<T>(T value) => value;
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$SnEmbedLink {
|
||||||
|
|
||||||
|
@JsonKey(name: 'Type') String get type;@JsonKey(name: 'Url') String get url;@JsonKey(name: 'Title') String get title;@JsonKey(name: 'Description') String? get description;@JsonKey(name: 'ImageUrl') String? get imageUrl;@JsonKey(name: 'FaviconUrl') String get faviconUrl;@JsonKey(name: 'SiteName') String get siteName;@JsonKey(name: 'ContentType') String? get contentType;@JsonKey(name: 'Author') String? get author;@JsonKey(name: 'PublishedDate') DateTime? get publishedDate;
|
||||||
|
/// Create a copy of SnEmbedLink
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SnEmbedLinkCopyWith<SnEmbedLink> get copyWith => _$SnEmbedLinkCopyWithImpl<SnEmbedLink>(this as SnEmbedLink, _$identity);
|
||||||
|
|
||||||
|
/// Serializes this SnEmbedLink to a JSON map.
|
||||||
|
Map<String, dynamic> toJson();
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnEmbedLink&&(identical(other.type, type) || other.type == type)&&(identical(other.url, url) || other.url == url)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)&&(identical(other.imageUrl, imageUrl) || other.imageUrl == imageUrl)&&(identical(other.faviconUrl, faviconUrl) || other.faviconUrl == faviconUrl)&&(identical(other.siteName, siteName) || other.siteName == siteName)&&(identical(other.contentType, contentType) || other.contentType == contentType)&&(identical(other.author, author) || other.author == author)&&(identical(other.publishedDate, publishedDate) || other.publishedDate == publishedDate));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,type,url,title,description,imageUrl,faviconUrl,siteName,contentType,author,publishedDate);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'SnEmbedLink(type: $type, url: $url, title: $title, description: $description, imageUrl: $imageUrl, faviconUrl: $faviconUrl, siteName: $siteName, contentType: $contentType, author: $author, publishedDate: $publishedDate)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class $SnEmbedLinkCopyWith<$Res> {
|
||||||
|
factory $SnEmbedLinkCopyWith(SnEmbedLink value, $Res Function(SnEmbedLink) _then) = _$SnEmbedLinkCopyWithImpl;
|
||||||
|
@useResult
|
||||||
|
$Res call({
|
||||||
|
@JsonKey(name: 'Type') String type,@JsonKey(name: 'Url') String url,@JsonKey(name: 'Title') String title,@JsonKey(name: 'Description') String? description,@JsonKey(name: 'ImageUrl') String? imageUrl,@JsonKey(name: 'FaviconUrl') String faviconUrl,@JsonKey(name: 'SiteName') String siteName,@JsonKey(name: 'ContentType') String? contentType,@JsonKey(name: 'Author') String? author,@JsonKey(name: 'PublishedDate') DateTime? publishedDate
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class _$SnEmbedLinkCopyWithImpl<$Res>
|
||||||
|
implements $SnEmbedLinkCopyWith<$Res> {
|
||||||
|
_$SnEmbedLinkCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final SnEmbedLink _self;
|
||||||
|
final $Res Function(SnEmbedLink) _then;
|
||||||
|
|
||||||
|
/// Create a copy of SnEmbedLink
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline') @override $Res call({Object? type = null,Object? url = null,Object? title = null,Object? description = freezed,Object? imageUrl = freezed,Object? faviconUrl = null,Object? siteName = null,Object? contentType = freezed,Object? author = freezed,Object? publishedDate = freezed,}) {
|
||||||
|
return _then(_self.copyWith(
|
||||||
|
type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,url: null == url ? _self.url : url // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,imageUrl: freezed == imageUrl ? _self.imageUrl : imageUrl // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,faviconUrl: null == faviconUrl ? _self.faviconUrl : faviconUrl // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,siteName: null == siteName ? _self.siteName : siteName // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,contentType: freezed == contentType ? _self.contentType : contentType // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,author: freezed == author ? _self.author : author // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,publishedDate: freezed == publishedDate ? _self.publishedDate : publishedDate // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime?,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
@JsonSerializable()
|
||||||
|
|
||||||
|
class _SnEmbedLink implements SnEmbedLink {
|
||||||
|
const _SnEmbedLink({@JsonKey(name: 'Type') required this.type, @JsonKey(name: 'Url') required this.url, @JsonKey(name: 'Title') required this.title, @JsonKey(name: 'Description') required this.description, @JsonKey(name: 'ImageUrl') required this.imageUrl, @JsonKey(name: 'FaviconUrl') required this.faviconUrl, @JsonKey(name: 'SiteName') required this.siteName, @JsonKey(name: 'ContentType') required this.contentType, @JsonKey(name: 'Author') required this.author, @JsonKey(name: 'PublishedDate') required this.publishedDate});
|
||||||
|
factory _SnEmbedLink.fromJson(Map<String, dynamic> json) => _$SnEmbedLinkFromJson(json);
|
||||||
|
|
||||||
|
@override@JsonKey(name: 'Type') final String type;
|
||||||
|
@override@JsonKey(name: 'Url') final String url;
|
||||||
|
@override@JsonKey(name: 'Title') final String title;
|
||||||
|
@override@JsonKey(name: 'Description') final String? description;
|
||||||
|
@override@JsonKey(name: 'ImageUrl') final String? imageUrl;
|
||||||
|
@override@JsonKey(name: 'FaviconUrl') final String faviconUrl;
|
||||||
|
@override@JsonKey(name: 'SiteName') final String siteName;
|
||||||
|
@override@JsonKey(name: 'ContentType') final String? contentType;
|
||||||
|
@override@JsonKey(name: 'Author') final String? author;
|
||||||
|
@override@JsonKey(name: 'PublishedDate') final DateTime? publishedDate;
|
||||||
|
|
||||||
|
/// Create a copy of SnEmbedLink
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$SnEmbedLinkCopyWith<_SnEmbedLink> get copyWith => __$SnEmbedLinkCopyWithImpl<_SnEmbedLink>(this, _$identity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return _$SnEmbedLinkToJson(this, );
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnEmbedLink&&(identical(other.type, type) || other.type == type)&&(identical(other.url, url) || other.url == url)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)&&(identical(other.imageUrl, imageUrl) || other.imageUrl == imageUrl)&&(identical(other.faviconUrl, faviconUrl) || other.faviconUrl == faviconUrl)&&(identical(other.siteName, siteName) || other.siteName == siteName)&&(identical(other.contentType, contentType) || other.contentType == contentType)&&(identical(other.author, author) || other.author == author)&&(identical(other.publishedDate, publishedDate) || other.publishedDate == publishedDate));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,type,url,title,description,imageUrl,faviconUrl,siteName,contentType,author,publishedDate);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'SnEmbedLink(type: $type, url: $url, title: $title, description: $description, imageUrl: $imageUrl, faviconUrl: $faviconUrl, siteName: $siteName, contentType: $contentType, author: $author, publishedDate: $publishedDate)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class _$SnEmbedLinkCopyWith<$Res> implements $SnEmbedLinkCopyWith<$Res> {
|
||||||
|
factory _$SnEmbedLinkCopyWith(_SnEmbedLink value, $Res Function(_SnEmbedLink) _then) = __$SnEmbedLinkCopyWithImpl;
|
||||||
|
@override @useResult
|
||||||
|
$Res call({
|
||||||
|
@JsonKey(name: 'Type') String type,@JsonKey(name: 'Url') String url,@JsonKey(name: 'Title') String title,@JsonKey(name: 'Description') String? description,@JsonKey(name: 'ImageUrl') String? imageUrl,@JsonKey(name: 'FaviconUrl') String faviconUrl,@JsonKey(name: 'SiteName') String siteName,@JsonKey(name: 'ContentType') String? contentType,@JsonKey(name: 'Author') String? author,@JsonKey(name: 'PublishedDate') DateTime? publishedDate
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class __$SnEmbedLinkCopyWithImpl<$Res>
|
||||||
|
implements _$SnEmbedLinkCopyWith<$Res> {
|
||||||
|
__$SnEmbedLinkCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final _SnEmbedLink _self;
|
||||||
|
final $Res Function(_SnEmbedLink) _then;
|
||||||
|
|
||||||
|
/// Create a copy of SnEmbedLink
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @pragma('vm:prefer-inline') $Res call({Object? type = null,Object? url = null,Object? title = null,Object? description = freezed,Object? imageUrl = freezed,Object? faviconUrl = null,Object? siteName = null,Object? contentType = freezed,Object? author = freezed,Object? publishedDate = freezed,}) {
|
||||||
|
return _then(_SnEmbedLink(
|
||||||
|
type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,url: null == url ? _self.url : url // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,imageUrl: freezed == imageUrl ? _self.imageUrl : imageUrl // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,faviconUrl: null == faviconUrl ? _self.faviconUrl : faviconUrl // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,siteName: null == siteName ? _self.siteName : siteName // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,contentType: freezed == contentType ? _self.contentType : contentType // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,author: freezed == author ? _self.author : author // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,publishedDate: freezed == publishedDate ? _self.publishedDate : publishedDate // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime?,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// dart format on
|
37
lib/models/embed.g.dart
Normal file
37
lib/models/embed.g.dart
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'embed.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// JsonSerializableGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
_SnEmbedLink _$SnEmbedLinkFromJson(Map<String, dynamic> json) => _SnEmbedLink(
|
||||||
|
type: json['Type'] as String,
|
||||||
|
url: json['Url'] as String,
|
||||||
|
title: json['Title'] as String,
|
||||||
|
description: json['Description'] as String?,
|
||||||
|
imageUrl: json['ImageUrl'] as String?,
|
||||||
|
faviconUrl: json['FaviconUrl'] as String,
|
||||||
|
siteName: json['SiteName'] as String,
|
||||||
|
contentType: json['ContentType'] as String?,
|
||||||
|
author: json['Author'] as String?,
|
||||||
|
publishedDate:
|
||||||
|
json['PublishedDate'] == null
|
||||||
|
? null
|
||||||
|
: DateTime.parse(json['PublishedDate'] as String),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$SnEmbedLinkToJson(_SnEmbedLink instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'Type': instance.type,
|
||||||
|
'Url': instance.url,
|
||||||
|
'Title': instance.title,
|
||||||
|
'Description': instance.description,
|
||||||
|
'ImageUrl': instance.imageUrl,
|
||||||
|
'FaviconUrl': instance.faviconUrl,
|
||||||
|
'SiteName': instance.siteName,
|
||||||
|
'ContentType': instance.contentType,
|
||||||
|
'Author': instance.author,
|
||||||
|
'PublishedDate': instance.publishedDate?.toIso8601String(),
|
||||||
|
};
|
@ -22,6 +22,7 @@ sealed class SnPost with _$SnPost {
|
|||||||
required int viewsTotal,
|
required int viewsTotal,
|
||||||
required int upvotes,
|
required int upvotes,
|
||||||
required int downvotes,
|
required int downvotes,
|
||||||
|
required int repliesCount,
|
||||||
required String? threadedPostId,
|
required String? threadedPostId,
|
||||||
required SnPost? threadedPost,
|
required SnPost? threadedPost,
|
||||||
required String? repliedPostId,
|
required String? repliedPostId,
|
||||||
@ -38,6 +39,7 @@ sealed class SnPost with _$SnPost {
|
|||||||
required DateTime createdAt,
|
required DateTime createdAt,
|
||||||
required DateTime updatedAt,
|
required DateTime updatedAt,
|
||||||
required DateTime? deletedAt,
|
required DateTime? deletedAt,
|
||||||
|
@Default(false) bool isTruncated,
|
||||||
}) = _SnPost;
|
}) = _SnPost;
|
||||||
|
|
||||||
factory SnPost.fromJson(Map<String, dynamic> json) => _$SnPostFromJson(json);
|
factory SnPost.fromJson(Map<String, dynamic> json) => _$SnPostFromJson(json);
|
||||||
@ -59,6 +61,7 @@ sealed class SnPublisher with _$SnPublisher {
|
|||||||
required DateTime updatedAt,
|
required DateTime updatedAt,
|
||||||
required DateTime? deletedAt,
|
required DateTime? deletedAt,
|
||||||
required String? realmId,
|
required String? realmId,
|
||||||
|
required SnVerificationMark? verification,
|
||||||
}) = _SnPublisher;
|
}) = _SnPublisher;
|
||||||
|
|
||||||
factory SnPublisher.fromJson(Map<String, dynamic> json) =>
|
factory SnPublisher.fromJson(Map<String, dynamic> json) =>
|
||||||
@ -83,7 +86,7 @@ sealed class SnPublisherStats with _$SnPublisherStats {
|
|||||||
sealed class SnSubscriptionStatus with _$SnSubscriptionStatus {
|
sealed class SnSubscriptionStatus with _$SnSubscriptionStatus {
|
||||||
const factory SnSubscriptionStatus({
|
const factory SnSubscriptionStatus({
|
||||||
required bool isSubscribed,
|
required bool isSubscribed,
|
||||||
required int publisherId,
|
required String publisherId,
|
||||||
required String publisherName,
|
required String publisherName,
|
||||||
}) = _SnSubscriptionStatus;
|
}) = _SnSubscriptionStatus;
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ T _$identity<T>(T value) => value;
|
|||||||
/// @nodoc
|
/// @nodoc
|
||||||
mixin _$SnPost {
|
mixin _$SnPost {
|
||||||
|
|
||||||
String get id; String? get title; String? get description; String? get language; DateTime? get editedAt; DateTime get publishedAt; int get visibility; String? get content; int get type; Map<String, dynamic>? get meta; int get viewsUnique; int get viewsTotal; int get upvotes; int get downvotes; 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; bool get isTruncated;
|
||||||
/// Create a copy of SnPost
|
/// Create a copy of SnPost
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@ -29,16 +29,16 @@ $SnPostCopyWith<SnPost> get copyWith => _$SnPostCopyWithImpl<SnPost>(this as SnP
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnPost&&(identical(other.id, id) || other.id == id)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)&&(identical(other.language, language) || other.language == language)&&(identical(other.editedAt, editedAt) || other.editedAt == editedAt)&&(identical(other.publishedAt, publishedAt) || other.publishedAt == publishedAt)&&(identical(other.visibility, visibility) || other.visibility == visibility)&&(identical(other.content, content) || other.content == content)&&(identical(other.type, type) || other.type == type)&&const DeepCollectionEquality().equals(other.meta, meta)&&(identical(other.viewsUnique, viewsUnique) || other.viewsUnique == viewsUnique)&&(identical(other.viewsTotal, viewsTotal) || other.viewsTotal == viewsTotal)&&(identical(other.upvotes, upvotes) || other.upvotes == upvotes)&&(identical(other.downvotes, downvotes) || other.downvotes == downvotes)&&(identical(other.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)&&(identical(other.isTruncated, isTruncated) || other.isTruncated == isTruncated));
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hashAll([runtimeType,id,title,description,language,editedAt,publishedAt,visibility,content,type,const DeepCollectionEquality().hash(meta),viewsUnique,viewsTotal,upvotes,downvotes,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,isTruncated]);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'SnPost(id: $id, title: $title, description: $description, language: $language, editedAt: $editedAt, publishedAt: $publishedAt, visibility: $visibility, content: $content, type: $type, meta: $meta, viewsUnique: $viewsUnique, viewsTotal: $viewsTotal, upvotes: $upvotes, downvotes: $downvotes, 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, isTruncated: $isTruncated)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -49,7 +49,7 @@ abstract mixin class $SnPostCopyWith<$Res> {
|
|||||||
factory $SnPostCopyWith(SnPost value, $Res Function(SnPost) _then) = _$SnPostCopyWithImpl;
|
factory $SnPostCopyWith(SnPost value, $Res Function(SnPost) _then) = _$SnPostCopyWithImpl;
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
String id, String? title, String? description, String? language, DateTime? editedAt, DateTime publishedAt, int visibility, String? content, int type, Map<String, dynamic>? meta, int viewsUnique, int viewsTotal, int upvotes, int downvotes, 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, bool isTruncated
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@ -66,7 +66,7 @@ class _$SnPostCopyWithImpl<$Res>
|
|||||||
|
|
||||||
/// Create a copy of SnPost
|
/// Create a copy of SnPost
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? title = freezed,Object? description = freezed,Object? language = freezed,Object? editedAt = freezed,Object? publishedAt = 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,Object? isTruncated = null,}) {
|
||||||
return _then(_self.copyWith(
|
return _then(_self.copyWith(
|
||||||
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||||
as String,title: freezed == title ? _self.title : title // ignore: cast_nullable_to_non_nullable
|
as String,title: freezed == title ? _self.title : title // ignore: cast_nullable_to_non_nullable
|
||||||
@ -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,viewsTotal: null == viewsTotal ? _self.viewsTotal : viewsTotal // ignore: cast_nullable_to_non_nullable
|
||||||
as int,upvotes: null == upvotes ? _self.upvotes : upvotes // ignore: cast_nullable_to_non_nullable
|
as int,upvotes: null == upvotes ? _self.upvotes : upvotes // ignore: cast_nullable_to_non_nullable
|
||||||
as int,downvotes: null == downvotes ? _self.downvotes : downvotes // ignore: cast_nullable_to_non_nullable
|
as int,downvotes: null == downvotes ? _self.downvotes : downvotes // ignore: cast_nullable_to_non_nullable
|
||||||
|
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 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 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
|
as SnPost?,repliedPostId: freezed == repliedPostId ? _self.repliedPostId : repliedPostId // ignore: cast_nullable_to_non_nullable
|
||||||
@ -98,7 +99,8 @@ as List<dynamic>,collections: null == collections ? _self.collections : collecti
|
|||||||
as List<dynamic>,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
as List<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,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
|
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime?,
|
as DateTime?,isTruncated: null == isTruncated ? _self.isTruncated : isTruncated // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
/// Create a copy of SnPost
|
/// Create a copy of SnPost
|
||||||
@ -154,7 +156,7 @@ $SnPublisherCopyWith<$Res> get publisher {
|
|||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
|
|
||||||
class _SnPost implements SnPost {
|
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, this.isTruncated = false}): _meta = meta,_attachments = attachments,_reactionsCount = reactionsCount,_reactions = reactions,_tags = tags,_categories = categories,_collections = collections;
|
||||||
factory _SnPost.fromJson(Map<String, dynamic> json) => _$SnPostFromJson(json);
|
factory _SnPost.fromJson(Map<String, dynamic> json) => _$SnPostFromJson(json);
|
||||||
|
|
||||||
@override final String id;
|
@override final String id;
|
||||||
@ -179,6 +181,7 @@ class _SnPost implements SnPost {
|
|||||||
@override final int viewsTotal;
|
@override final int viewsTotal;
|
||||||
@override final int upvotes;
|
@override final int upvotes;
|
||||||
@override final int downvotes;
|
@override final int downvotes;
|
||||||
|
@override final int repliesCount;
|
||||||
@override final String? threadedPostId;
|
@override final String? threadedPostId;
|
||||||
@override final SnPost? threadedPost;
|
@override final SnPost? threadedPost;
|
||||||
@override final String? repliedPostId;
|
@override final String? repliedPostId;
|
||||||
@ -231,6 +234,7 @@ class _SnPost implements SnPost {
|
|||||||
@override final DateTime createdAt;
|
@override final DateTime createdAt;
|
||||||
@override final DateTime updatedAt;
|
@override final DateTime updatedAt;
|
||||||
@override final DateTime? deletedAt;
|
@override final DateTime? deletedAt;
|
||||||
|
@override@JsonKey() final bool isTruncated;
|
||||||
|
|
||||||
/// Create a copy of SnPost
|
/// Create a copy of SnPost
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@ -245,16 +249,16 @@ Map<String, dynamic> toJson() {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnPost&&(identical(other.id, id) || other.id == id)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)&&(identical(other.language, language) || other.language == language)&&(identical(other.editedAt, editedAt) || other.editedAt == editedAt)&&(identical(other.publishedAt, publishedAt) || other.publishedAt == publishedAt)&&(identical(other.visibility, visibility) || other.visibility == visibility)&&(identical(other.content, content) || other.content == content)&&(identical(other.type, type) || other.type == type)&&const DeepCollectionEquality().equals(other._meta, _meta)&&(identical(other.viewsUnique, viewsUnique) || other.viewsUnique == viewsUnique)&&(identical(other.viewsTotal, viewsTotal) || other.viewsTotal == viewsTotal)&&(identical(other.upvotes, upvotes) || other.upvotes == upvotes)&&(identical(other.downvotes, downvotes) || other.downvotes == downvotes)&&(identical(other.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)&&(identical(other.isTruncated, isTruncated) || other.isTruncated == isTruncated));
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hashAll([runtimeType,id,title,description,language,editedAt,publishedAt,visibility,content,type,const DeepCollectionEquality().hash(_meta),viewsUnique,viewsTotal,upvotes,downvotes,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,isTruncated]);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'SnPost(id: $id, title: $title, description: $description, language: $language, editedAt: $editedAt, publishedAt: $publishedAt, visibility: $visibility, content: $content, type: $type, meta: $meta, viewsUnique: $viewsUnique, viewsTotal: $viewsTotal, upvotes: $upvotes, downvotes: $downvotes, 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, isTruncated: $isTruncated)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -265,7 +269,7 @@ abstract mixin class _$SnPostCopyWith<$Res> implements $SnPostCopyWith<$Res> {
|
|||||||
factory _$SnPostCopyWith(_SnPost value, $Res Function(_SnPost) _then) = __$SnPostCopyWithImpl;
|
factory _$SnPostCopyWith(_SnPost value, $Res Function(_SnPost) _then) = __$SnPostCopyWithImpl;
|
||||||
@override @useResult
|
@override @useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
String id, String? title, String? description, String? language, DateTime? editedAt, DateTime publishedAt, int visibility, String? content, int type, Map<String, dynamic>? meta, int viewsUnique, int viewsTotal, int upvotes, int downvotes, 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, bool isTruncated
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@ -282,7 +286,7 @@ class __$SnPostCopyWithImpl<$Res>
|
|||||||
|
|
||||||
/// Create a copy of SnPost
|
/// Create a copy of SnPost
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? title = freezed,Object? description = freezed,Object? language = freezed,Object? editedAt = freezed,Object? publishedAt = 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,Object? isTruncated = null,}) {
|
||||||
return _then(_SnPost(
|
return _then(_SnPost(
|
||||||
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||||
as String,title: freezed == title ? _self.title : title // ignore: cast_nullable_to_non_nullable
|
as String,title: freezed == title ? _self.title : title // ignore: cast_nullable_to_non_nullable
|
||||||
@ -298,6 +302,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,viewsTotal: null == viewsTotal ? _self.viewsTotal : viewsTotal // ignore: cast_nullable_to_non_nullable
|
||||||
as int,upvotes: null == upvotes ? _self.upvotes : upvotes // ignore: cast_nullable_to_non_nullable
|
as int,upvotes: null == upvotes ? _self.upvotes : upvotes // ignore: cast_nullable_to_non_nullable
|
||||||
as int,downvotes: null == downvotes ? _self.downvotes : downvotes // ignore: cast_nullable_to_non_nullable
|
as int,downvotes: null == downvotes ? _self.downvotes : downvotes // ignore: cast_nullable_to_non_nullable
|
||||||
|
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 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 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
|
as SnPost?,repliedPostId: freezed == repliedPostId ? _self.repliedPostId : repliedPostId // ignore: cast_nullable_to_non_nullable
|
||||||
@ -314,7 +319,8 @@ as List<dynamic>,collections: null == collections ? _self._collections : collect
|
|||||||
as List<dynamic>,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
as List<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,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
|
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime?,
|
as DateTime?,isTruncated: null == isTruncated ? _self.isTruncated : isTruncated // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -370,7 +376,7 @@ $SnPublisherCopyWith<$Res> get publisher {
|
|||||||
/// @nodoc
|
/// @nodoc
|
||||||
mixin _$SnPublisher {
|
mixin _$SnPublisher {
|
||||||
|
|
||||||
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;
|
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
|
/// Create a copy of SnPublisher
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@ -383,16 +389,16 @@ $SnPublisherCopyWith<SnPublisher> get copyWith => _$SnPublisherCopyWithImpl<SnPu
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
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.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));
|
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)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(runtimeType,id,type,name,nick,bio,picture,background,account,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
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
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)';
|
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 +409,11 @@ abstract mixin class $SnPublisherCopyWith<$Res> {
|
|||||||
factory $SnPublisherCopyWith(SnPublisher value, $Res Function(SnPublisher) _then) = _$SnPublisherCopyWithImpl;
|
factory $SnPublisherCopyWith(SnPublisher value, $Res Function(SnPublisher) _then) = _$SnPublisherCopyWithImpl;
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
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
|
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;$SnAccountCopyWith<$Res>? get account;
|
$SnCloudFileCopyWith<$Res>? get picture;$SnCloudFileCopyWith<$Res>? get background;$SnAccountCopyWith<$Res>? get account;$SnVerificationMarkCopyWith<$Res>? get verification;
|
||||||
|
|
||||||
}
|
}
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
@ -420,7 +426,7 @@ class _$SnPublisherCopyWithImpl<$Res>
|
|||||||
|
|
||||||
/// Create a copy of SnPublisher
|
/// Create a copy of SnPublisher
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? type = null,Object? 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,}) {
|
@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(
|
return _then(_self.copyWith(
|
||||||
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||||
as String,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
|
as String,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
|
||||||
@ -435,7 +441,8 @@ as String?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore:
|
|||||||
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
|
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime?,realmId: freezed == realmId ? _self.realmId : realmId // 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
|
/// Create a copy of SnPublisher
|
||||||
@ -474,6 +481,18 @@ $SnAccountCopyWith<$Res>? get account {
|
|||||||
return $SnAccountCopyWith<$Res>(_self.account!, (value) {
|
return $SnAccountCopyWith<$Res>(_self.account!, (value) {
|
||||||
return _then(_self.copyWith(account: value));
|
return _then(_self.copyWith(account: value));
|
||||||
});
|
});
|
||||||
|
}/// Create a copy of 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));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -482,7 +501,7 @@ $SnAccountCopyWith<$Res>? get account {
|
|||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
|
|
||||||
class _SnPublisher implements SnPublisher {
|
class _SnPublisher implements SnPublisher {
|
||||||
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});
|
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);
|
factory _SnPublisher.fromJson(Map<String, dynamic> json) => _$SnPublisherFromJson(json);
|
||||||
|
|
||||||
@override final String id;
|
@override final String id;
|
||||||
@ -498,6 +517,7 @@ class _SnPublisher implements SnPublisher {
|
|||||||
@override final DateTime updatedAt;
|
@override final DateTime updatedAt;
|
||||||
@override final DateTime? deletedAt;
|
@override final DateTime? deletedAt;
|
||||||
@override final String? realmId;
|
@override final String? realmId;
|
||||||
|
@override final SnVerificationMark? verification;
|
||||||
|
|
||||||
/// Create a copy of SnPublisher
|
/// Create a copy of SnPublisher
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@ -512,16 +532,16 @@ Map<String, dynamic> toJson() {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
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.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));
|
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)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(runtimeType,id,type,name,nick,bio,picture,background,account,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
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
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)';
|
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)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -532,11 +552,11 @@ abstract mixin class _$SnPublisherCopyWith<$Res> implements $SnPublisherCopyWith
|
|||||||
factory _$SnPublisherCopyWith(_SnPublisher value, $Res Function(_SnPublisher) _then) = __$SnPublisherCopyWithImpl;
|
factory _$SnPublisherCopyWith(_SnPublisher value, $Res Function(_SnPublisher) _then) = __$SnPublisherCopyWithImpl;
|
||||||
@override @useResult
|
@override @useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
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
|
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 $SnAccountCopyWith<$Res>? get account;
|
@override $SnCloudFileCopyWith<$Res>? get picture;@override $SnCloudFileCopyWith<$Res>? get background;@override $SnAccountCopyWith<$Res>? get account;@override $SnVerificationMarkCopyWith<$Res>? get verification;
|
||||||
|
|
||||||
}
|
}
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
@ -549,7 +569,7 @@ class __$SnPublisherCopyWithImpl<$Res>
|
|||||||
|
|
||||||
/// Create a copy of SnPublisher
|
/// Create a copy of SnPublisher
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? type = null,Object? 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,}) {
|
@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(
|
return _then(_SnPublisher(
|
||||||
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||||
as String,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
|
as String,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
|
||||||
@ -564,7 +584,8 @@ as String?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore:
|
|||||||
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
|
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime?,realmId: freezed == realmId ? _self.realmId : realmId // 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?,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -604,6 +625,18 @@ $SnAccountCopyWith<$Res>? get account {
|
|||||||
return $SnAccountCopyWith<$Res>(_self.account!, (value) {
|
return $SnAccountCopyWith<$Res>(_self.account!, (value) {
|
||||||
return _then(_self.copyWith(account: value));
|
return _then(_self.copyWith(account: value));
|
||||||
});
|
});
|
||||||
|
}/// Create a copy of 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));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -756,7 +789,7 @@ as int,
|
|||||||
/// @nodoc
|
/// @nodoc
|
||||||
mixin _$SnSubscriptionStatus {
|
mixin _$SnSubscriptionStatus {
|
||||||
|
|
||||||
bool get isSubscribed; int get publisherId; String get publisherName;
|
bool get isSubscribed; String get publisherId; String get publisherName;
|
||||||
/// Create a copy of SnSubscriptionStatus
|
/// Create a copy of SnSubscriptionStatus
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@ -789,7 +822,7 @@ abstract mixin class $SnSubscriptionStatusCopyWith<$Res> {
|
|||||||
factory $SnSubscriptionStatusCopyWith(SnSubscriptionStatus value, $Res Function(SnSubscriptionStatus) _then) = _$SnSubscriptionStatusCopyWithImpl;
|
factory $SnSubscriptionStatusCopyWith(SnSubscriptionStatus value, $Res Function(SnSubscriptionStatus) _then) = _$SnSubscriptionStatusCopyWithImpl;
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
bool isSubscribed, int publisherId, String publisherName
|
bool isSubscribed, String publisherId, String publisherName
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@ -810,7 +843,7 @@ class _$SnSubscriptionStatusCopyWithImpl<$Res>
|
|||||||
return _then(_self.copyWith(
|
return _then(_self.copyWith(
|
||||||
isSubscribed: null == isSubscribed ? _self.isSubscribed : isSubscribed // ignore: cast_nullable_to_non_nullable
|
isSubscribed: null == isSubscribed ? _self.isSubscribed : isSubscribed // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,publisherId: null == publisherId ? _self.publisherId : publisherId // ignore: cast_nullable_to_non_nullable
|
as bool,publisherId: null == publisherId ? _self.publisherId : publisherId // ignore: cast_nullable_to_non_nullable
|
||||||
as int,publisherName: null == publisherName ? _self.publisherName : publisherName // ignore: cast_nullable_to_non_nullable
|
as String,publisherName: null == publisherName ? _self.publisherName : publisherName // ignore: cast_nullable_to_non_nullable
|
||||||
as String,
|
as String,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@ -826,7 +859,7 @@ class _SnSubscriptionStatus implements SnSubscriptionStatus {
|
|||||||
factory _SnSubscriptionStatus.fromJson(Map<String, dynamic> json) => _$SnSubscriptionStatusFromJson(json);
|
factory _SnSubscriptionStatus.fromJson(Map<String, dynamic> json) => _$SnSubscriptionStatusFromJson(json);
|
||||||
|
|
||||||
@override final bool isSubscribed;
|
@override final bool isSubscribed;
|
||||||
@override final int publisherId;
|
@override final String publisherId;
|
||||||
@override final String publisherName;
|
@override final String publisherName;
|
||||||
|
|
||||||
/// Create a copy of SnSubscriptionStatus
|
/// Create a copy of SnSubscriptionStatus
|
||||||
@ -862,7 +895,7 @@ abstract mixin class _$SnSubscriptionStatusCopyWith<$Res> implements $SnSubscrip
|
|||||||
factory _$SnSubscriptionStatusCopyWith(_SnSubscriptionStatus value, $Res Function(_SnSubscriptionStatus) _then) = __$SnSubscriptionStatusCopyWithImpl;
|
factory _$SnSubscriptionStatusCopyWith(_SnSubscriptionStatus value, $Res Function(_SnSubscriptionStatus) _then) = __$SnSubscriptionStatusCopyWithImpl;
|
||||||
@override @useResult
|
@override @useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
bool isSubscribed, int publisherId, String publisherName
|
bool isSubscribed, String publisherId, String publisherName
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@ -883,7 +916,7 @@ class __$SnSubscriptionStatusCopyWithImpl<$Res>
|
|||||||
return _then(_SnSubscriptionStatus(
|
return _then(_SnSubscriptionStatus(
|
||||||
isSubscribed: null == isSubscribed ? _self.isSubscribed : isSubscribed // ignore: cast_nullable_to_non_nullable
|
isSubscribed: null == isSubscribed ? _self.isSubscribed : isSubscribed // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,publisherId: null == publisherId ? _self.publisherId : publisherId // ignore: cast_nullable_to_non_nullable
|
as bool,publisherId: null == publisherId ? _self.publisherId : publisherId // ignore: cast_nullable_to_non_nullable
|
||||||
as int,publisherName: null == publisherName ? _self.publisherName : publisherName // ignore: cast_nullable_to_non_nullable
|
as String,publisherName: null == publisherName ? _self.publisherName : publisherName // ignore: cast_nullable_to_non_nullable
|
||||||
as String,
|
as String,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,7 @@ _SnPost _$SnPostFromJson(Map<String, dynamic> json) => _SnPost(
|
|||||||
viewsTotal: (json['views_total'] as num).toInt(),
|
viewsTotal: (json['views_total'] as num).toInt(),
|
||||||
upvotes: (json['upvotes'] as num).toInt(),
|
upvotes: (json['upvotes'] as num).toInt(),
|
||||||
downvotes: (json['downvotes'] as num).toInt(),
|
downvotes: (json['downvotes'] as num).toInt(),
|
||||||
|
repliesCount: (json['replies_count'] as num).toInt(),
|
||||||
threadedPostId: json['threaded_post_id'] as String?,
|
threadedPostId: json['threaded_post_id'] as String?,
|
||||||
threadedPost:
|
threadedPost:
|
||||||
json['threaded_post'] == null
|
json['threaded_post'] == null
|
||||||
@ -59,6 +60,7 @@ _SnPost _$SnPostFromJson(Map<String, dynamic> json) => _SnPost(
|
|||||||
json['deleted_at'] == null
|
json['deleted_at'] == null
|
||||||
? null
|
? null
|
||||||
: DateTime.parse(json['deleted_at'] as String),
|
: DateTime.parse(json['deleted_at'] as String),
|
||||||
|
isTruncated: json['is_truncated'] as bool? ?? false,
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$SnPostToJson(_SnPost instance) => <String, dynamic>{
|
Map<String, dynamic> _$SnPostToJson(_SnPost instance) => <String, dynamic>{
|
||||||
@ -76,6 +78,7 @@ Map<String, dynamic> _$SnPostToJson(_SnPost instance) => <String, dynamic>{
|
|||||||
'views_total': instance.viewsTotal,
|
'views_total': instance.viewsTotal,
|
||||||
'upvotes': instance.upvotes,
|
'upvotes': instance.upvotes,
|
||||||
'downvotes': instance.downvotes,
|
'downvotes': instance.downvotes,
|
||||||
|
'replies_count': instance.repliesCount,
|
||||||
'threaded_post_id': instance.threadedPostId,
|
'threaded_post_id': instance.threadedPostId,
|
||||||
'threaded_post': instance.threadedPost?.toJson(),
|
'threaded_post': instance.threadedPost?.toJson(),
|
||||||
'replied_post_id': instance.repliedPostId,
|
'replied_post_id': instance.repliedPostId,
|
||||||
@ -92,6 +95,7 @@ Map<String, dynamic> _$SnPostToJson(_SnPost instance) => <String, dynamic>{
|
|||||||
'created_at': instance.createdAt.toIso8601String(),
|
'created_at': instance.createdAt.toIso8601String(),
|
||||||
'updated_at': instance.updatedAt.toIso8601String(),
|
'updated_at': instance.updatedAt.toIso8601String(),
|
||||||
'deleted_at': instance.deletedAt?.toIso8601String(),
|
'deleted_at': instance.deletedAt?.toIso8601String(),
|
||||||
|
'is_truncated': instance.isTruncated,
|
||||||
};
|
};
|
||||||
|
|
||||||
_SnPublisher _$SnPublisherFromJson(Map<String, dynamic> json) => _SnPublisher(
|
_SnPublisher _$SnPublisherFromJson(Map<String, dynamic> json) => _SnPublisher(
|
||||||
@ -120,6 +124,12 @@ _SnPublisher _$SnPublisherFromJson(Map<String, dynamic> json) => _SnPublisher(
|
|||||||
? null
|
? null
|
||||||
: DateTime.parse(json['deleted_at'] as String),
|
: DateTime.parse(json['deleted_at'] as String),
|
||||||
realmId: json['realm_id'] 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) =>
|
Map<String, dynamic> _$SnPublisherToJson(_SnPublisher instance) =>
|
||||||
@ -137,6 +147,7 @@ Map<String, dynamic> _$SnPublisherToJson(_SnPublisher instance) =>
|
|||||||
'updated_at': instance.updatedAt.toIso8601String(),
|
'updated_at': instance.updatedAt.toIso8601String(),
|
||||||
'deleted_at': instance.deletedAt?.toIso8601String(),
|
'deleted_at': instance.deletedAt?.toIso8601String(),
|
||||||
'realm_id': instance.realmId,
|
'realm_id': instance.realmId,
|
||||||
|
'verification': instance.verification?.toJson(),
|
||||||
};
|
};
|
||||||
|
|
||||||
_SnPublisherStats _$SnPublisherStatsFromJson(Map<String, dynamic> json) =>
|
_SnPublisherStats _$SnPublisherStatsFromJson(Map<String, dynamic> json) =>
|
||||||
@ -161,7 +172,7 @@ _SnSubscriptionStatus _$SnSubscriptionStatusFromJson(
|
|||||||
Map<String, dynamic> json,
|
Map<String, dynamic> json,
|
||||||
) => _SnSubscriptionStatus(
|
) => _SnSubscriptionStatus(
|
||||||
isSubscribed: json['is_subscribed'] as bool,
|
isSubscribed: json['is_subscribed'] as bool,
|
||||||
publisherId: (json['publisher_id'] as num).toInt(),
|
publisherId: json['publisher_id'] as String,
|
||||||
publisherName: json['publisher_name'] as String,
|
publisherName: json['publisher_name'] as String,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
import 'package:island/models/file.dart';
|
import 'package:island/models/file.dart';
|
||||||
|
import 'package:island/models/wallet.dart';
|
||||||
|
|
||||||
part 'user.freezed.dart';
|
part 'user.freezed.dart';
|
||||||
part 'user.g.dart';
|
part 'user.g.dart';
|
||||||
@ -27,15 +28,24 @@ sealed class SnAccount with _$SnAccount {
|
|||||||
sealed class SnAccountProfile with _$SnAccountProfile {
|
sealed class SnAccountProfile with _$SnAccountProfile {
|
||||||
const factory SnAccountProfile({
|
const factory SnAccountProfile({
|
||||||
required String id,
|
required String id,
|
||||||
required String? firstName,
|
@Default('') String firstName,
|
||||||
required String? middleName,
|
@Default('') String middleName,
|
||||||
required String? lastName,
|
@Default('') String lastName,
|
||||||
@Default('') String bio,
|
@Default('') String bio,
|
||||||
|
@Default('') String gender,
|
||||||
|
@Default('') String pronouns,
|
||||||
|
@Default('') String location,
|
||||||
|
@Default('') String timeZone,
|
||||||
|
DateTime? birthday,
|
||||||
|
DateTime? lastSeenAt,
|
||||||
|
SnAccountBadge? activeBadge,
|
||||||
required int experience,
|
required int experience,
|
||||||
required int level,
|
required int level,
|
||||||
required double levelingProgress,
|
required double levelingProgress,
|
||||||
required SnCloudFile? picture,
|
required SnCloudFile? picture,
|
||||||
required SnCloudFile? background,
|
required SnCloudFile? background,
|
||||||
|
required SnVerificationMark? verification,
|
||||||
|
required SnWalletSubscription? stellarMembership,
|
||||||
required DateTime createdAt,
|
required DateTime createdAt,
|
||||||
required DateTime updatedAt,
|
required DateTime updatedAt,
|
||||||
required DateTime? deletedAt,
|
required DateTime? deletedAt,
|
||||||
@ -78,6 +88,7 @@ sealed class SnAccountBadge with _$SnAccountBadge {
|
|||||||
required String accountId,
|
required String accountId,
|
||||||
required DateTime createdAt,
|
required DateTime createdAt,
|
||||||
required DateTime updatedAt,
|
required DateTime updatedAt,
|
||||||
|
required DateTime? activatedAt,
|
||||||
required DateTime? deletedAt,
|
required DateTime? deletedAt,
|
||||||
}) = _SnAccountBadge;
|
}) = _SnAccountBadge;
|
||||||
|
|
||||||
@ -85,6 +96,24 @@ sealed class SnAccountBadge with _$SnAccountBadge {
|
|||||||
_$SnAccountBadgeFromJson(json);
|
_$SnAccountBadgeFromJson(json);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
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
|
@freezed
|
||||||
sealed class SnNotification with _$SnNotification {
|
sealed class SnNotification with _$SnNotification {
|
||||||
const factory SnNotification({
|
const factory SnNotification({
|
||||||
@ -105,3 +134,16 @@ sealed class SnNotification with _$SnNotification {
|
|||||||
factory SnNotification.fromJson(Map<String, dynamic> json) =>
|
factory SnNotification.fromJson(Map<String, dynamic> json) =>
|
||||||
_$SnNotificationFromJson(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
|
/// @nodoc
|
||||||
mixin _$SnAccountProfile {
|
mixin _$SnAccountProfile {
|
||||||
|
|
||||||
String get id; String? get firstName; String? get middleName; String? get lastName; String get bio; int get experience; int get level; double get levelingProgress; SnCloudFile? get picture; 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; SnWalletSubscription? get stellarMembership; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt;
|
||||||
/// Create a copy of SnAccountProfile
|
/// Create a copy of SnAccountProfile
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@ -213,16 +213,16 @@ $SnAccountProfileCopyWith<SnAccountProfile> get copyWith => _$SnAccountProfileCo
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnAccountProfile&&(identical(other.id, id) || other.id == id)&&(identical(other.firstName, firstName) || other.firstName == firstName)&&(identical(other.middleName, middleName) || other.middleName == middleName)&&(identical(other.lastName, lastName) || other.lastName == lastName)&&(identical(other.bio, bio) || other.bio == bio)&&(identical(other.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.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.stellarMembership, stellarMembership) || other.stellarMembership == stellarMembership)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(runtimeType,id,firstName,middleName,lastName,bio,experience,level,levelingProgress,picture,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,stellarMembership,createdAt,updatedAt,deletedAt]);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'SnAccountProfile(id: $id, firstName: $firstName, middleName: $middleName, lastName: $lastName, bio: $bio, experience: $experience, level: $level, levelingProgress: $levelingProgress, picture: $picture, 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, stellarMembership: $stellarMembership, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -233,11 +233,11 @@ abstract mixin class $SnAccountProfileCopyWith<$Res> {
|
|||||||
factory $SnAccountProfileCopyWith(SnAccountProfile value, $Res Function(SnAccountProfile) _then) = _$SnAccountProfileCopyWithImpl;
|
factory $SnAccountProfileCopyWith(SnAccountProfile value, $Res Function(SnAccountProfile) _then) = _$SnAccountProfileCopyWithImpl;
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
String id, String? firstName, String? middleName, String? lastName, String bio, int experience, int level, double levelingProgress, SnCloudFile? picture, 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, SnWalletSubscription? stellarMembership, 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;$SnWalletSubscriptionCopyWith<$Res>? get stellarMembership;
|
||||||
|
|
||||||
}
|
}
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
@ -250,19 +250,28 @@ class _$SnAccountProfileCopyWithImpl<$Res>
|
|||||||
|
|
||||||
/// Create a copy of SnAccountProfile
|
/// Create a copy of SnAccountProfile
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? firstName = freezed,Object? middleName = freezed,Object? lastName = freezed,Object? bio = null,Object? experience = null,Object? level = null,Object? levelingProgress = null,Object? picture = 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? stellarMembership = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
|
||||||
return _then(_self.copyWith(
|
return _then(_self.copyWith(
|
||||||
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||||
as String,firstName: freezed == firstName ? _self.firstName : firstName // ignore: cast_nullable_to_non_nullable
|
as String,firstName: null == 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,middleName: null == 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,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,bio: null == bio ? _self.bio : bio // ignore: cast_nullable_to_non_nullable
|
||||||
as String,experience: null == experience ? _self.experience : experience // 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,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 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 double,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?,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?,verification: freezed == verification ? _self.verification : verification // ignore: cast_nullable_to_non_nullable
|
||||||
|
as SnVerificationMark?,stellarMembership: freezed == stellarMembership ? _self.stellarMembership : stellarMembership // ignore: cast_nullable_to_non_nullable
|
||||||
|
as SnWalletSubscription?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
|
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime?,
|
as DateTime?,
|
||||||
@ -272,6 +281,18 @@ as DateTime?,
|
|||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@override
|
@override
|
||||||
@pragma('vm:prefer-inline')
|
@pragma('vm:prefer-inline')
|
||||||
|
$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 {
|
$SnCloudFileCopyWith<$Res>? get picture {
|
||||||
if (_self.picture == null) {
|
if (_self.picture == null) {
|
||||||
return null;
|
return null;
|
||||||
@ -292,6 +313,30 @@ $SnCloudFileCopyWith<$Res>? get background {
|
|||||||
return $SnCloudFileCopyWith<$Res>(_self.background!, (value) {
|
return $SnCloudFileCopyWith<$Res>(_self.background!, (value) {
|
||||||
return _then(_self.copyWith(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));
|
||||||
|
});
|
||||||
|
}/// Create a copy of SnAccountProfile
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SnWalletSubscriptionCopyWith<$Res>? get stellarMembership {
|
||||||
|
if (_self.stellarMembership == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $SnWalletSubscriptionCopyWith<$Res>(_self.stellarMembership!, (value) {
|
||||||
|
return _then(_self.copyWith(stellarMembership: value));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -300,19 +345,28 @@ $SnCloudFileCopyWith<$Res>? get background {
|
|||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
|
|
||||||
class _SnAccountProfile implements SnAccountProfile {
|
class _SnAccountProfile implements SnAccountProfile {
|
||||||
const _SnAccountProfile({required this.id, required this.firstName, required this.middleName, required this.lastName, this.bio = '', required this.experience, required this.level, required this.levelingProgress, required this.picture, 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.stellarMembership, required this.createdAt, required this.updatedAt, required this.deletedAt});
|
||||||
factory _SnAccountProfile.fromJson(Map<String, dynamic> json) => _$SnAccountProfileFromJson(json);
|
factory _SnAccountProfile.fromJson(Map<String, dynamic> json) => _$SnAccountProfileFromJson(json);
|
||||||
|
|
||||||
@override final String id;
|
@override final String id;
|
||||||
@override final String? firstName;
|
@override@JsonKey() final String firstName;
|
||||||
@override final String? middleName;
|
@override@JsonKey() final String middleName;
|
||||||
@override final String? lastName;
|
@override@JsonKey() final String lastName;
|
||||||
@override@JsonKey() final String bio;
|
@override@JsonKey() final String bio;
|
||||||
|
@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 experience;
|
||||||
@override final int level;
|
@override final int level;
|
||||||
@override final double levelingProgress;
|
@override final double levelingProgress;
|
||||||
@override final SnCloudFile? picture;
|
@override final SnCloudFile? picture;
|
||||||
@override final SnCloudFile? background;
|
@override final SnCloudFile? background;
|
||||||
|
@override final SnVerificationMark? verification;
|
||||||
|
@override final SnWalletSubscription? stellarMembership;
|
||||||
@override final DateTime createdAt;
|
@override final DateTime createdAt;
|
||||||
@override final DateTime updatedAt;
|
@override final DateTime updatedAt;
|
||||||
@override final DateTime? deletedAt;
|
@override final DateTime? deletedAt;
|
||||||
@ -330,16 +384,16 @@ Map<String, dynamic> toJson() {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnAccountProfile&&(identical(other.id, id) || other.id == id)&&(identical(other.firstName, firstName) || other.firstName == firstName)&&(identical(other.middleName, middleName) || other.middleName == middleName)&&(identical(other.lastName, lastName) || other.lastName == lastName)&&(identical(other.bio, bio) || other.bio == bio)&&(identical(other.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.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.stellarMembership, stellarMembership) || other.stellarMembership == stellarMembership)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(runtimeType,id,firstName,middleName,lastName,bio,experience,level,levelingProgress,picture,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,stellarMembership,createdAt,updatedAt,deletedAt]);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'SnAccountProfile(id: $id, firstName: $firstName, middleName: $middleName, lastName: $lastName, bio: $bio, experience: $experience, level: $level, levelingProgress: $levelingProgress, picture: $picture, 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, stellarMembership: $stellarMembership, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -350,11 +404,11 @@ abstract mixin class _$SnAccountProfileCopyWith<$Res> implements $SnAccountProfi
|
|||||||
factory _$SnAccountProfileCopyWith(_SnAccountProfile value, $Res Function(_SnAccountProfile) _then) = __$SnAccountProfileCopyWithImpl;
|
factory _$SnAccountProfileCopyWith(_SnAccountProfile value, $Res Function(_SnAccountProfile) _then) = __$SnAccountProfileCopyWithImpl;
|
||||||
@override @useResult
|
@override @useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
String id, String? firstName, String? middleName, String? lastName, String bio, int experience, int level, double levelingProgress, SnCloudFile? picture, 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, SnWalletSubscription? stellarMembership, 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;@override $SnWalletSubscriptionCopyWith<$Res>? get stellarMembership;
|
||||||
|
|
||||||
}
|
}
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
@ -367,19 +421,28 @@ class __$SnAccountProfileCopyWithImpl<$Res>
|
|||||||
|
|
||||||
/// Create a copy of SnAccountProfile
|
/// Create a copy of SnAccountProfile
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? firstName = freezed,Object? middleName = freezed,Object? lastName = freezed,Object? bio = null,Object? experience = null,Object? level = null,Object? levelingProgress = null,Object? picture = 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? stellarMembership = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
|
||||||
return _then(_SnAccountProfile(
|
return _then(_SnAccountProfile(
|
||||||
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||||
as String,firstName: freezed == firstName ? _self.firstName : firstName // ignore: cast_nullable_to_non_nullable
|
as String,firstName: null == 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,middleName: null == 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,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,bio: null == bio ? _self.bio : bio // ignore: cast_nullable_to_non_nullable
|
||||||
as String,experience: null == experience ? _self.experience : experience // 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,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 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 double,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?,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?,verification: freezed == verification ? _self.verification : verification // ignore: cast_nullable_to_non_nullable
|
||||||
|
as SnVerificationMark?,stellarMembership: freezed == stellarMembership ? _self.stellarMembership : stellarMembership // ignore: cast_nullable_to_non_nullable
|
||||||
|
as SnWalletSubscription?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
|
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime?,
|
as DateTime?,
|
||||||
@ -390,6 +453,18 @@ as DateTime?,
|
|||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@override
|
@override
|
||||||
@pragma('vm:prefer-inline')
|
@pragma('vm:prefer-inline')
|
||||||
|
$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 {
|
$SnCloudFileCopyWith<$Res>? get picture {
|
||||||
if (_self.picture == null) {
|
if (_self.picture == null) {
|
||||||
return null;
|
return null;
|
||||||
@ -410,6 +485,30 @@ $SnCloudFileCopyWith<$Res>? get background {
|
|||||||
return $SnCloudFileCopyWith<$Res>(_self.background!, (value) {
|
return $SnCloudFileCopyWith<$Res>(_self.background!, (value) {
|
||||||
return _then(_self.copyWith(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));
|
||||||
|
});
|
||||||
|
}/// Create a copy of SnAccountProfile
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SnWalletSubscriptionCopyWith<$Res>? get stellarMembership {
|
||||||
|
if (_self.stellarMembership == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $SnWalletSubscriptionCopyWith<$Res>(_self.stellarMembership!, (value) {
|
||||||
|
return _then(_self.copyWith(stellarMembership: value));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -583,7 +682,7 @@ as DateTime?,
|
|||||||
/// @nodoc
|
/// @nodoc
|
||||||
mixin _$SnAccountBadge {
|
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
|
/// Create a copy of SnAccountBadge
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@ -596,16 +695,16 @@ $SnAccountBadgeCopyWith<SnAccountBadge> get copyWith => _$SnAccountBadgeCopyWith
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
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)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@override
|
@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
|
@override
|
||||||
String toString() {
|
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)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -616,7 +715,7 @@ abstract mixin class $SnAccountBadgeCopyWith<$Res> {
|
|||||||
factory $SnAccountBadgeCopyWith(SnAccountBadge value, $Res Function(SnAccountBadge) _then) = _$SnAccountBadgeCopyWithImpl;
|
factory $SnAccountBadgeCopyWith(SnAccountBadge value, $Res Function(SnAccountBadge) _then) = _$SnAccountBadgeCopyWithImpl;
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({
|
$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
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@ -633,7 +732,7 @@ class _$SnAccountBadgeCopyWithImpl<$Res>
|
|||||||
|
|
||||||
/// Create a copy of SnAccountBadge
|
/// Create a copy of SnAccountBadge
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? type = null,Object? 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(
|
return _then(_self.copyWith(
|
||||||
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||||
as String,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
|
as String,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
|
||||||
@ -644,7 +743,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 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 String,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
|
as DateTime,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?,
|
as DateTime?,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@ -656,7 +756,7 @@ as DateTime?,
|
|||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
|
|
||||||
class _SnAccountBadge implements SnAccountBadge {
|
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);
|
factory _SnAccountBadge.fromJson(Map<String, dynamic> json) => _$SnAccountBadgeFromJson(json);
|
||||||
|
|
||||||
@override final String id;
|
@override final String id;
|
||||||
@ -674,6 +774,7 @@ class _SnAccountBadge implements SnAccountBadge {
|
|||||||
@override final String accountId;
|
@override final String accountId;
|
||||||
@override final DateTime createdAt;
|
@override final DateTime createdAt;
|
||||||
@override final DateTime updatedAt;
|
@override final DateTime updatedAt;
|
||||||
|
@override final DateTime? activatedAt;
|
||||||
@override final DateTime? deletedAt;
|
@override final DateTime? deletedAt;
|
||||||
|
|
||||||
/// Create a copy of SnAccountBadge
|
/// Create a copy of SnAccountBadge
|
||||||
@ -689,16 +790,16 @@ Map<String, dynamic> toJson() {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
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)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@override
|
@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
|
@override
|
||||||
String toString() {
|
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)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -709,7 +810,7 @@ abstract mixin class _$SnAccountBadgeCopyWith<$Res> implements $SnAccountBadgeCo
|
|||||||
factory _$SnAccountBadgeCopyWith(_SnAccountBadge value, $Res Function(_SnAccountBadge) _then) = __$SnAccountBadgeCopyWithImpl;
|
factory _$SnAccountBadgeCopyWith(_SnAccountBadge value, $Res Function(_SnAccountBadge) _then) = __$SnAccountBadgeCopyWithImpl;
|
||||||
@override @useResult
|
@override @useResult
|
||||||
$Res call({
|
$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
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@ -726,7 +827,7 @@ class __$SnAccountBadgeCopyWithImpl<$Res>
|
|||||||
|
|
||||||
/// Create a copy of SnAccountBadge
|
/// Create a copy of SnAccountBadge
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? type = null,Object? 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(
|
return _then(_SnAccountBadge(
|
||||||
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||||
as String,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
|
as String,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
|
||||||
@ -737,6 +838,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 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 String,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,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,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime?,
|
as DateTime?,
|
||||||
));
|
));
|
||||||
@ -915,6 +1174,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
|
// dart format on
|
||||||
|
@ -43,10 +43,28 @@ Map<String, dynamic> _$SnAccountToJson(_SnAccount instance) =>
|
|||||||
_SnAccountProfile _$SnAccountProfileFromJson(Map<String, dynamic> json) =>
|
_SnAccountProfile _$SnAccountProfileFromJson(Map<String, dynamic> json) =>
|
||||||
_SnAccountProfile(
|
_SnAccountProfile(
|
||||||
id: json['id'] as String,
|
id: json['id'] as String,
|
||||||
firstName: json['first_name'] as String?,
|
firstName: json['first_name'] as String? ?? '',
|
||||||
middleName: json['middle_name'] as String?,
|
middleName: json['middle_name'] as String? ?? '',
|
||||||
lastName: json['last_name'] as String?,
|
lastName: json['last_name'] as String? ?? '',
|
||||||
bio: json['bio'] as String? ?? '',
|
bio: json['bio'] 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(),
|
experience: (json['experience'] as num).toInt(),
|
||||||
level: (json['level'] as num).toInt(),
|
level: (json['level'] as num).toInt(),
|
||||||
levelingProgress: (json['leveling_progress'] as num).toDouble(),
|
levelingProgress: (json['leveling_progress'] as num).toDouble(),
|
||||||
@ -60,6 +78,18 @@ _SnAccountProfile _$SnAccountProfileFromJson(Map<String, dynamic> json) =>
|
|||||||
: SnCloudFile.fromJson(
|
: SnCloudFile.fromJson(
|
||||||
json['background'] as Map<String, dynamic>,
|
json['background'] as Map<String, dynamic>,
|
||||||
),
|
),
|
||||||
|
verification:
|
||||||
|
json['verification'] == null
|
||||||
|
? null
|
||||||
|
: SnVerificationMark.fromJson(
|
||||||
|
json['verification'] as Map<String, dynamic>,
|
||||||
|
),
|
||||||
|
stellarMembership:
|
||||||
|
json['stellar_membership'] == null
|
||||||
|
? null
|
||||||
|
: SnWalletSubscription.fromJson(
|
||||||
|
json['stellar_membership'] as Map<String, dynamic>,
|
||||||
|
),
|
||||||
createdAt: DateTime.parse(json['created_at'] as String),
|
createdAt: DateTime.parse(json['created_at'] as String),
|
||||||
updatedAt: DateTime.parse(json['updated_at'] as String),
|
updatedAt: DateTime.parse(json['updated_at'] as String),
|
||||||
deletedAt:
|
deletedAt:
|
||||||
@ -75,11 +105,20 @@ Map<String, dynamic> _$SnAccountProfileToJson(_SnAccountProfile instance) =>
|
|||||||
'middle_name': instance.middleName,
|
'middle_name': instance.middleName,
|
||||||
'last_name': instance.lastName,
|
'last_name': instance.lastName,
|
||||||
'bio': instance.bio,
|
'bio': instance.bio,
|
||||||
|
'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,
|
'experience': instance.experience,
|
||||||
'level': instance.level,
|
'level': instance.level,
|
||||||
'leveling_progress': instance.levelingProgress,
|
'leveling_progress': instance.levelingProgress,
|
||||||
'picture': instance.picture?.toJson(),
|
'picture': instance.picture?.toJson(),
|
||||||
'background': instance.background?.toJson(),
|
'background': instance.background?.toJson(),
|
||||||
|
'verification': instance.verification?.toJson(),
|
||||||
|
'stellar_membership': instance.stellarMembership?.toJson(),
|
||||||
'created_at': instance.createdAt.toIso8601String(),
|
'created_at': instance.createdAt.toIso8601String(),
|
||||||
'updated_at': instance.updatedAt.toIso8601String(),
|
'updated_at': instance.updatedAt.toIso8601String(),
|
||||||
'deleted_at': instance.deletedAt?.toIso8601String(),
|
'deleted_at': instance.deletedAt?.toIso8601String(),
|
||||||
@ -137,6 +176,10 @@ _SnAccountBadge _$SnAccountBadgeFromJson(Map<String, dynamic> json) =>
|
|||||||
accountId: json['account_id'] as String,
|
accountId: json['account_id'] as String,
|
||||||
createdAt: DateTime.parse(json['created_at'] as String),
|
createdAt: DateTime.parse(json['created_at'] as String),
|
||||||
updatedAt: DateTime.parse(json['updated_at'] as String),
|
updatedAt: DateTime.parse(json['updated_at'] as String),
|
||||||
|
activatedAt:
|
||||||
|
json['activated_at'] == null
|
||||||
|
? null
|
||||||
|
: DateTime.parse(json['activated_at'] as String),
|
||||||
deletedAt:
|
deletedAt:
|
||||||
json['deleted_at'] == null
|
json['deleted_at'] == null
|
||||||
? null
|
? null
|
||||||
@ -154,6 +197,39 @@ Map<String, dynamic> _$SnAccountBadgeToJson(_SnAccountBadge instance) =>
|
|||||||
'account_id': instance.accountId,
|
'account_id': instance.accountId,
|
||||||
'created_at': instance.createdAt.toIso8601String(),
|
'created_at': instance.createdAt.toIso8601String(),
|
||||||
'updated_at': instance.updatedAt.toIso8601String(),
|
'updated_at': instance.updatedAt.toIso8601String(),
|
||||||
|
'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(),
|
'deleted_at': instance.deletedAt?.toIso8601String(),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -194,3 +270,19 @@ Map<String, dynamic> _$SnNotificationToJson(_SnNotification instance) =>
|
|||||||
'viewed_at': instance.viewedAt?.toIso8601String(),
|
'viewed_at': instance.viewedAt?.toIso8601String(),
|
||||||
'account_id': instance.accountId,
|
'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,
|
||||||
|
};
|
||||||
|
@ -56,3 +56,58 @@ sealed class SnTransaction with _$SnTransaction {
|
|||||||
factory SnTransaction.fromJson(Map<String, dynamic> json) =>
|
factory SnTransaction.fromJson(Map<String, dynamic> json) =>
|
||||||
_$SnTransactionFromJson(json);
|
_$SnTransactionFromJson(json);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
sealed class SnWalletSubscription with _$SnWalletSubscription {
|
||||||
|
const factory SnWalletSubscription({
|
||||||
|
required String id,
|
||||||
|
required DateTime begunAt,
|
||||||
|
required DateTime endedAt,
|
||||||
|
required String identifier,
|
||||||
|
required bool isActive,
|
||||||
|
required bool isFreeTrial,
|
||||||
|
required int status,
|
||||||
|
required String paymentMethod,
|
||||||
|
required Map<String, dynamic> paymentDetails,
|
||||||
|
required double basePrice,
|
||||||
|
required String? couponId,
|
||||||
|
required dynamic coupon,
|
||||||
|
required DateTime renewalAt,
|
||||||
|
required String accountId,
|
||||||
|
required SnAccount? account,
|
||||||
|
required bool isAvailable,
|
||||||
|
required double finalPrice,
|
||||||
|
required DateTime createdAt,
|
||||||
|
required DateTime updatedAt,
|
||||||
|
required DateTime? deletedAt,
|
||||||
|
}) = _SnWalletSubscription;
|
||||||
|
|
||||||
|
factory SnWalletSubscription.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$SnWalletSubscriptionFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
sealed class SnWalletOrder with _$SnWalletOrder {
|
||||||
|
const factory SnWalletOrder({
|
||||||
|
required String id,
|
||||||
|
required int status,
|
||||||
|
required String currency,
|
||||||
|
required dynamic remarks,
|
||||||
|
required String appIdentifier,
|
||||||
|
@Default({}) Map<String, dynamic> meta,
|
||||||
|
required int amount,
|
||||||
|
required DateTime expiredAt,
|
||||||
|
required String? payeeWalletId,
|
||||||
|
required SnWallet? payeeWallet,
|
||||||
|
required String? transactionId,
|
||||||
|
required SnTransaction? transaction,
|
||||||
|
required String? issuerAppId,
|
||||||
|
required dynamic issuerApp,
|
||||||
|
required DateTime createdAt,
|
||||||
|
required DateTime updatedAt,
|
||||||
|
required DateTime? deletedAt,
|
||||||
|
}) = _SnWalletOrder;
|
||||||
|
|
||||||
|
factory SnWalletOrder.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$SnWalletOrderFromJson(json);
|
||||||
|
}
|
||||||
|
@ -558,4 +558,459 @@ $SnWalletCopyWith<$Res>? get payeeWallet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$SnWalletSubscription {
|
||||||
|
|
||||||
|
String get id; DateTime get begunAt; DateTime get endedAt; String get identifier; bool get isActive; bool get isFreeTrial; int get status; String get paymentMethod; Map<String, dynamic> get paymentDetails; double get basePrice; String? get couponId; dynamic get coupon; DateTime get renewalAt; String get accountId; SnAccount? get account; bool get isAvailable; double get finalPrice; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt;
|
||||||
|
/// Create a copy of SnWalletSubscription
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SnWalletSubscriptionCopyWith<SnWalletSubscription> get copyWith => _$SnWalletSubscriptionCopyWithImpl<SnWalletSubscription>(this as SnWalletSubscription, _$identity);
|
||||||
|
|
||||||
|
/// Serializes this SnWalletSubscription to a JSON map.
|
||||||
|
Map<String, dynamic> toJson();
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnWalletSubscription&&(identical(other.id, id) || other.id == id)&&(identical(other.begunAt, begunAt) || other.begunAt == begunAt)&&(identical(other.endedAt, endedAt) || other.endedAt == endedAt)&&(identical(other.identifier, identifier) || other.identifier == identifier)&&(identical(other.isActive, isActive) || other.isActive == isActive)&&(identical(other.isFreeTrial, isFreeTrial) || other.isFreeTrial == isFreeTrial)&&(identical(other.status, status) || other.status == status)&&(identical(other.paymentMethod, paymentMethod) || other.paymentMethod == paymentMethod)&&const DeepCollectionEquality().equals(other.paymentDetails, paymentDetails)&&(identical(other.basePrice, basePrice) || other.basePrice == basePrice)&&(identical(other.couponId, couponId) || other.couponId == couponId)&&const DeepCollectionEquality().equals(other.coupon, coupon)&&(identical(other.renewalAt, renewalAt) || other.renewalAt == renewalAt)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.account, account) || other.account == account)&&(identical(other.isAvailable, isAvailable) || other.isAvailable == isAvailable)&&(identical(other.finalPrice, finalPrice) || other.finalPrice == finalPrice)&&(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,begunAt,endedAt,identifier,isActive,isFreeTrial,status,paymentMethod,const DeepCollectionEquality().hash(paymentDetails),basePrice,couponId,const DeepCollectionEquality().hash(coupon),renewalAt,accountId,account,isAvailable,finalPrice,createdAt,updatedAt,deletedAt]);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'SnWalletSubscription(id: $id, begunAt: $begunAt, endedAt: $endedAt, identifier: $identifier, isActive: $isActive, isFreeTrial: $isFreeTrial, status: $status, paymentMethod: $paymentMethod, paymentDetails: $paymentDetails, basePrice: $basePrice, couponId: $couponId, coupon: $coupon, renewalAt: $renewalAt, accountId: $accountId, account: $account, isAvailable: $isAvailable, finalPrice: $finalPrice, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class $SnWalletSubscriptionCopyWith<$Res> {
|
||||||
|
factory $SnWalletSubscriptionCopyWith(SnWalletSubscription value, $Res Function(SnWalletSubscription) _then) = _$SnWalletSubscriptionCopyWithImpl;
|
||||||
|
@useResult
|
||||||
|
$Res call({
|
||||||
|
String id, DateTime begunAt, DateTime endedAt, String identifier, bool isActive, bool isFreeTrial, int status, String paymentMethod, Map<String, dynamic> paymentDetails, double basePrice, String? couponId, dynamic coupon, DateTime renewalAt, String accountId, SnAccount? account, bool isAvailable, double finalPrice, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
$SnAccountCopyWith<$Res>? get account;
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class _$SnWalletSubscriptionCopyWithImpl<$Res>
|
||||||
|
implements $SnWalletSubscriptionCopyWith<$Res> {
|
||||||
|
_$SnWalletSubscriptionCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final SnWalletSubscription _self;
|
||||||
|
final $Res Function(SnWalletSubscription) _then;
|
||||||
|
|
||||||
|
/// Create a copy of SnWalletSubscription
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? begunAt = null,Object? endedAt = null,Object? identifier = null,Object? isActive = null,Object? isFreeTrial = null,Object? status = null,Object? paymentMethod = null,Object? paymentDetails = null,Object? basePrice = null,Object? couponId = freezed,Object? coupon = freezed,Object? renewalAt = null,Object? accountId = null,Object? account = freezed,Object? isAvailable = null,Object? finalPrice = 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,begunAt: null == begunAt ? _self.begunAt : begunAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,endedAt: null == endedAt ? _self.endedAt : endedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,identifier: null == identifier ? _self.identifier : identifier // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,isActive: null == isActive ? _self.isActive : isActive // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,isFreeTrial: null == isFreeTrial ? _self.isFreeTrial : isFreeTrial // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,status: null == status ? _self.status : status // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,paymentMethod: null == paymentMethod ? _self.paymentMethod : paymentMethod // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,paymentDetails: null == paymentDetails ? _self.paymentDetails : paymentDetails // ignore: cast_nullable_to_non_nullable
|
||||||
|
as Map<String, dynamic>,basePrice: null == basePrice ? _self.basePrice : basePrice // ignore: cast_nullable_to_non_nullable
|
||||||
|
as double,couponId: freezed == couponId ? _self.couponId : couponId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,coupon: freezed == coupon ? _self.coupon : coupon // ignore: cast_nullable_to_non_nullable
|
||||||
|
as dynamic,renewalAt: null == renewalAt ? _self.renewalAt : renewalAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,account: freezed == account ? _self.account : account // ignore: cast_nullable_to_non_nullable
|
||||||
|
as SnAccount?,isAvailable: null == isAvailable ? _self.isAvailable : isAvailable // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,finalPrice: null == finalPrice ? _self.finalPrice : finalPrice // ignore: cast_nullable_to_non_nullable
|
||||||
|
as double,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 SnWalletSubscription
|
||||||
|
/// 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));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
@JsonSerializable()
|
||||||
|
|
||||||
|
class _SnWalletSubscription implements SnWalletSubscription {
|
||||||
|
const _SnWalletSubscription({required this.id, required this.begunAt, required this.endedAt, required this.identifier, required this.isActive, required this.isFreeTrial, required this.status, required this.paymentMethod, required final Map<String, dynamic> paymentDetails, required this.basePrice, required this.couponId, required this.coupon, required this.renewalAt, required this.accountId, required this.account, required this.isAvailable, required this.finalPrice, required this.createdAt, required this.updatedAt, required this.deletedAt}): _paymentDetails = paymentDetails;
|
||||||
|
factory _SnWalletSubscription.fromJson(Map<String, dynamic> json) => _$SnWalletSubscriptionFromJson(json);
|
||||||
|
|
||||||
|
@override final String id;
|
||||||
|
@override final DateTime begunAt;
|
||||||
|
@override final DateTime endedAt;
|
||||||
|
@override final String identifier;
|
||||||
|
@override final bool isActive;
|
||||||
|
@override final bool isFreeTrial;
|
||||||
|
@override final int status;
|
||||||
|
@override final String paymentMethod;
|
||||||
|
final Map<String, dynamic> _paymentDetails;
|
||||||
|
@override Map<String, dynamic> get paymentDetails {
|
||||||
|
if (_paymentDetails is EqualUnmodifiableMapView) return _paymentDetails;
|
||||||
|
// ignore: implicit_dynamic_type
|
||||||
|
return EqualUnmodifiableMapView(_paymentDetails);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override final double basePrice;
|
||||||
|
@override final String? couponId;
|
||||||
|
@override final dynamic coupon;
|
||||||
|
@override final DateTime renewalAt;
|
||||||
|
@override final String accountId;
|
||||||
|
@override final SnAccount? account;
|
||||||
|
@override final bool isAvailable;
|
||||||
|
@override final double finalPrice;
|
||||||
|
@override final DateTime createdAt;
|
||||||
|
@override final DateTime updatedAt;
|
||||||
|
@override final DateTime? deletedAt;
|
||||||
|
|
||||||
|
/// Create a copy of SnWalletSubscription
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$SnWalletSubscriptionCopyWith<_SnWalletSubscription> get copyWith => __$SnWalletSubscriptionCopyWithImpl<_SnWalletSubscription>(this, _$identity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return _$SnWalletSubscriptionToJson(this, );
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnWalletSubscription&&(identical(other.id, id) || other.id == id)&&(identical(other.begunAt, begunAt) || other.begunAt == begunAt)&&(identical(other.endedAt, endedAt) || other.endedAt == endedAt)&&(identical(other.identifier, identifier) || other.identifier == identifier)&&(identical(other.isActive, isActive) || other.isActive == isActive)&&(identical(other.isFreeTrial, isFreeTrial) || other.isFreeTrial == isFreeTrial)&&(identical(other.status, status) || other.status == status)&&(identical(other.paymentMethod, paymentMethod) || other.paymentMethod == paymentMethod)&&const DeepCollectionEquality().equals(other._paymentDetails, _paymentDetails)&&(identical(other.basePrice, basePrice) || other.basePrice == basePrice)&&(identical(other.couponId, couponId) || other.couponId == couponId)&&const DeepCollectionEquality().equals(other.coupon, coupon)&&(identical(other.renewalAt, renewalAt) || other.renewalAt == renewalAt)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.account, account) || other.account == account)&&(identical(other.isAvailable, isAvailable) || other.isAvailable == isAvailable)&&(identical(other.finalPrice, finalPrice) || other.finalPrice == finalPrice)&&(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,begunAt,endedAt,identifier,isActive,isFreeTrial,status,paymentMethod,const DeepCollectionEquality().hash(_paymentDetails),basePrice,couponId,const DeepCollectionEquality().hash(coupon),renewalAt,accountId,account,isAvailable,finalPrice,createdAt,updatedAt,deletedAt]);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'SnWalletSubscription(id: $id, begunAt: $begunAt, endedAt: $endedAt, identifier: $identifier, isActive: $isActive, isFreeTrial: $isFreeTrial, status: $status, paymentMethod: $paymentMethod, paymentDetails: $paymentDetails, basePrice: $basePrice, couponId: $couponId, coupon: $coupon, renewalAt: $renewalAt, accountId: $accountId, account: $account, isAvailable: $isAvailable, finalPrice: $finalPrice, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class _$SnWalletSubscriptionCopyWith<$Res> implements $SnWalletSubscriptionCopyWith<$Res> {
|
||||||
|
factory _$SnWalletSubscriptionCopyWith(_SnWalletSubscription value, $Res Function(_SnWalletSubscription) _then) = __$SnWalletSubscriptionCopyWithImpl;
|
||||||
|
@override @useResult
|
||||||
|
$Res call({
|
||||||
|
String id, DateTime begunAt, DateTime endedAt, String identifier, bool isActive, bool isFreeTrial, int status, String paymentMethod, Map<String, dynamic> paymentDetails, double basePrice, String? couponId, dynamic coupon, DateTime renewalAt, String accountId, SnAccount? account, bool isAvailable, double finalPrice, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
@override $SnAccountCopyWith<$Res>? get account;
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class __$SnWalletSubscriptionCopyWithImpl<$Res>
|
||||||
|
implements _$SnWalletSubscriptionCopyWith<$Res> {
|
||||||
|
__$SnWalletSubscriptionCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final _SnWalletSubscription _self;
|
||||||
|
final $Res Function(_SnWalletSubscription) _then;
|
||||||
|
|
||||||
|
/// Create a copy of SnWalletSubscription
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? begunAt = null,Object? endedAt = null,Object? identifier = null,Object? isActive = null,Object? isFreeTrial = null,Object? status = null,Object? paymentMethod = null,Object? paymentDetails = null,Object? basePrice = null,Object? couponId = freezed,Object? coupon = freezed,Object? renewalAt = null,Object? accountId = null,Object? account = freezed,Object? isAvailable = null,Object? finalPrice = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
|
||||||
|
return _then(_SnWalletSubscription(
|
||||||
|
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,begunAt: null == begunAt ? _self.begunAt : begunAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,endedAt: null == endedAt ? _self.endedAt : endedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,identifier: null == identifier ? _self.identifier : identifier // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,isActive: null == isActive ? _self.isActive : isActive // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,isFreeTrial: null == isFreeTrial ? _self.isFreeTrial : isFreeTrial // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,status: null == status ? _self.status : status // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,paymentMethod: null == paymentMethod ? _self.paymentMethod : paymentMethod // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,paymentDetails: null == paymentDetails ? _self._paymentDetails : paymentDetails // ignore: cast_nullable_to_non_nullable
|
||||||
|
as Map<String, dynamic>,basePrice: null == basePrice ? _self.basePrice : basePrice // ignore: cast_nullable_to_non_nullable
|
||||||
|
as double,couponId: freezed == couponId ? _self.couponId : couponId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,coupon: freezed == coupon ? _self.coupon : coupon // ignore: cast_nullable_to_non_nullable
|
||||||
|
as dynamic,renewalAt: null == renewalAt ? _self.renewalAt : renewalAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,account: freezed == account ? _self.account : account // ignore: cast_nullable_to_non_nullable
|
||||||
|
as SnAccount?,isAvailable: null == isAvailable ? _self.isAvailable : isAvailable // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,finalPrice: null == finalPrice ? _self.finalPrice : finalPrice // ignore: cast_nullable_to_non_nullable
|
||||||
|
as double,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 SnWalletSubscription
|
||||||
|
/// 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));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$SnWalletOrder {
|
||||||
|
|
||||||
|
String get id; int get status; String get currency; dynamic get remarks; String get appIdentifier; Map<String, dynamic> get meta; int get amount; DateTime get expiredAt; String? get payeeWalletId; SnWallet? get payeeWallet; String? get transactionId; SnTransaction? get transaction; String? get issuerAppId; dynamic get issuerApp; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt;
|
||||||
|
/// Create a copy of SnWalletOrder
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SnWalletOrderCopyWith<SnWalletOrder> get copyWith => _$SnWalletOrderCopyWithImpl<SnWalletOrder>(this as SnWalletOrder, _$identity);
|
||||||
|
|
||||||
|
/// Serializes this SnWalletOrder to a JSON map.
|
||||||
|
Map<String, dynamic> toJson();
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnWalletOrder&&(identical(other.id, id) || other.id == id)&&(identical(other.status, status) || other.status == status)&&(identical(other.currency, currency) || other.currency == currency)&&const DeepCollectionEquality().equals(other.remarks, remarks)&&(identical(other.appIdentifier, appIdentifier) || other.appIdentifier == appIdentifier)&&const DeepCollectionEquality().equals(other.meta, meta)&&(identical(other.amount, amount) || other.amount == amount)&&(identical(other.expiredAt, expiredAt) || other.expiredAt == expiredAt)&&(identical(other.payeeWalletId, payeeWalletId) || other.payeeWalletId == payeeWalletId)&&(identical(other.payeeWallet, payeeWallet) || other.payeeWallet == payeeWallet)&&(identical(other.transactionId, transactionId) || other.transactionId == transactionId)&&(identical(other.transaction, transaction) || other.transaction == transaction)&&(identical(other.issuerAppId, issuerAppId) || other.issuerAppId == issuerAppId)&&const DeepCollectionEquality().equals(other.issuerApp, issuerApp)&&(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,status,currency,const DeepCollectionEquality().hash(remarks),appIdentifier,const DeepCollectionEquality().hash(meta),amount,expiredAt,payeeWalletId,payeeWallet,transactionId,transaction,issuerAppId,const DeepCollectionEquality().hash(issuerApp),createdAt,updatedAt,deletedAt);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'SnWalletOrder(id: $id, status: $status, currency: $currency, remarks: $remarks, appIdentifier: $appIdentifier, meta: $meta, amount: $amount, expiredAt: $expiredAt, payeeWalletId: $payeeWalletId, payeeWallet: $payeeWallet, transactionId: $transactionId, transaction: $transaction, issuerAppId: $issuerAppId, issuerApp: $issuerApp, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class $SnWalletOrderCopyWith<$Res> {
|
||||||
|
factory $SnWalletOrderCopyWith(SnWalletOrder value, $Res Function(SnWalletOrder) _then) = _$SnWalletOrderCopyWithImpl;
|
||||||
|
@useResult
|
||||||
|
$Res call({
|
||||||
|
String id, int status, String currency, dynamic remarks, String appIdentifier, Map<String, dynamic> meta, int amount, DateTime expiredAt, String? payeeWalletId, SnWallet? payeeWallet, String? transactionId, SnTransaction? transaction, String? issuerAppId, dynamic issuerApp, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
$SnWalletCopyWith<$Res>? get payeeWallet;$SnTransactionCopyWith<$Res>? get transaction;
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class _$SnWalletOrderCopyWithImpl<$Res>
|
||||||
|
implements $SnWalletOrderCopyWith<$Res> {
|
||||||
|
_$SnWalletOrderCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final SnWalletOrder _self;
|
||||||
|
final $Res Function(SnWalletOrder) _then;
|
||||||
|
|
||||||
|
/// Create a copy of SnWalletOrder
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? status = null,Object? currency = null,Object? remarks = freezed,Object? appIdentifier = null,Object? meta = null,Object? amount = null,Object? expiredAt = null,Object? payeeWalletId = freezed,Object? payeeWallet = freezed,Object? transactionId = freezed,Object? transaction = freezed,Object? issuerAppId = freezed,Object? issuerApp = 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,status: null == status ? _self.status : status // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,currency: null == currency ? _self.currency : currency // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,remarks: freezed == remarks ? _self.remarks : remarks // ignore: cast_nullable_to_non_nullable
|
||||||
|
as dynamic,appIdentifier: null == appIdentifier ? _self.appIdentifier : appIdentifier // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,meta: null == meta ? _self.meta : meta // ignore: cast_nullable_to_non_nullable
|
||||||
|
as Map<String, dynamic>,amount: null == amount ? _self.amount : amount // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,expiredAt: null == expiredAt ? _self.expiredAt : expiredAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,payeeWalletId: freezed == payeeWalletId ? _self.payeeWalletId : payeeWalletId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,payeeWallet: freezed == payeeWallet ? _self.payeeWallet : payeeWallet // ignore: cast_nullable_to_non_nullable
|
||||||
|
as SnWallet?,transactionId: freezed == transactionId ? _self.transactionId : transactionId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,transaction: freezed == transaction ? _self.transaction : transaction // ignore: cast_nullable_to_non_nullable
|
||||||
|
as SnTransaction?,issuerAppId: freezed == issuerAppId ? _self.issuerAppId : issuerAppId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,issuerApp: freezed == issuerApp ? _self.issuerApp : issuerApp // 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 DateTime?,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
/// Create a copy of SnWalletOrder
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SnWalletCopyWith<$Res>? get payeeWallet {
|
||||||
|
if (_self.payeeWallet == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $SnWalletCopyWith<$Res>(_self.payeeWallet!, (value) {
|
||||||
|
return _then(_self.copyWith(payeeWallet: value));
|
||||||
|
});
|
||||||
|
}/// Create a copy of SnWalletOrder
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SnTransactionCopyWith<$Res>? get transaction {
|
||||||
|
if (_self.transaction == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $SnTransactionCopyWith<$Res>(_self.transaction!, (value) {
|
||||||
|
return _then(_self.copyWith(transaction: value));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
@JsonSerializable()
|
||||||
|
|
||||||
|
class _SnWalletOrder implements SnWalletOrder {
|
||||||
|
const _SnWalletOrder({required this.id, required this.status, required this.currency, required this.remarks, required this.appIdentifier, final Map<String, dynamic> meta = const {}, required this.amount, required this.expiredAt, required this.payeeWalletId, required this.payeeWallet, required this.transactionId, required this.transaction, required this.issuerAppId, required this.issuerApp, required this.createdAt, required this.updatedAt, required this.deletedAt}): _meta = meta;
|
||||||
|
factory _SnWalletOrder.fromJson(Map<String, dynamic> json) => _$SnWalletOrderFromJson(json);
|
||||||
|
|
||||||
|
@override final String id;
|
||||||
|
@override final int status;
|
||||||
|
@override final String currency;
|
||||||
|
@override final dynamic remarks;
|
||||||
|
@override final String appIdentifier;
|
||||||
|
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 int amount;
|
||||||
|
@override final DateTime expiredAt;
|
||||||
|
@override final String? payeeWalletId;
|
||||||
|
@override final SnWallet? payeeWallet;
|
||||||
|
@override final String? transactionId;
|
||||||
|
@override final SnTransaction? transaction;
|
||||||
|
@override final String? issuerAppId;
|
||||||
|
@override final dynamic issuerApp;
|
||||||
|
@override final DateTime createdAt;
|
||||||
|
@override final DateTime updatedAt;
|
||||||
|
@override final DateTime? deletedAt;
|
||||||
|
|
||||||
|
/// Create a copy of SnWalletOrder
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$SnWalletOrderCopyWith<_SnWalletOrder> get copyWith => __$SnWalletOrderCopyWithImpl<_SnWalletOrder>(this, _$identity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return _$SnWalletOrderToJson(this, );
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnWalletOrder&&(identical(other.id, id) || other.id == id)&&(identical(other.status, status) || other.status == status)&&(identical(other.currency, currency) || other.currency == currency)&&const DeepCollectionEquality().equals(other.remarks, remarks)&&(identical(other.appIdentifier, appIdentifier) || other.appIdentifier == appIdentifier)&&const DeepCollectionEquality().equals(other._meta, _meta)&&(identical(other.amount, amount) || other.amount == amount)&&(identical(other.expiredAt, expiredAt) || other.expiredAt == expiredAt)&&(identical(other.payeeWalletId, payeeWalletId) || other.payeeWalletId == payeeWalletId)&&(identical(other.payeeWallet, payeeWallet) || other.payeeWallet == payeeWallet)&&(identical(other.transactionId, transactionId) || other.transactionId == transactionId)&&(identical(other.transaction, transaction) || other.transaction == transaction)&&(identical(other.issuerAppId, issuerAppId) || other.issuerAppId == issuerAppId)&&const DeepCollectionEquality().equals(other.issuerApp, issuerApp)&&(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,status,currency,const DeepCollectionEquality().hash(remarks),appIdentifier,const DeepCollectionEquality().hash(_meta),amount,expiredAt,payeeWalletId,payeeWallet,transactionId,transaction,issuerAppId,const DeepCollectionEquality().hash(issuerApp),createdAt,updatedAt,deletedAt);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'SnWalletOrder(id: $id, status: $status, currency: $currency, remarks: $remarks, appIdentifier: $appIdentifier, meta: $meta, amount: $amount, expiredAt: $expiredAt, payeeWalletId: $payeeWalletId, payeeWallet: $payeeWallet, transactionId: $transactionId, transaction: $transaction, issuerAppId: $issuerAppId, issuerApp: $issuerApp, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class _$SnWalletOrderCopyWith<$Res> implements $SnWalletOrderCopyWith<$Res> {
|
||||||
|
factory _$SnWalletOrderCopyWith(_SnWalletOrder value, $Res Function(_SnWalletOrder) _then) = __$SnWalletOrderCopyWithImpl;
|
||||||
|
@override @useResult
|
||||||
|
$Res call({
|
||||||
|
String id, int status, String currency, dynamic remarks, String appIdentifier, Map<String, dynamic> meta, int amount, DateTime expiredAt, String? payeeWalletId, SnWallet? payeeWallet, String? transactionId, SnTransaction? transaction, String? issuerAppId, dynamic issuerApp, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
@override $SnWalletCopyWith<$Res>? get payeeWallet;@override $SnTransactionCopyWith<$Res>? get transaction;
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class __$SnWalletOrderCopyWithImpl<$Res>
|
||||||
|
implements _$SnWalletOrderCopyWith<$Res> {
|
||||||
|
__$SnWalletOrderCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final _SnWalletOrder _self;
|
||||||
|
final $Res Function(_SnWalletOrder) _then;
|
||||||
|
|
||||||
|
/// Create a copy of SnWalletOrder
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? status = null,Object? currency = null,Object? remarks = freezed,Object? appIdentifier = null,Object? meta = null,Object? amount = null,Object? expiredAt = null,Object? payeeWalletId = freezed,Object? payeeWallet = freezed,Object? transactionId = freezed,Object? transaction = freezed,Object? issuerAppId = freezed,Object? issuerApp = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
|
||||||
|
return _then(_SnWalletOrder(
|
||||||
|
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,status: null == status ? _self.status : status // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,currency: null == currency ? _self.currency : currency // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,remarks: freezed == remarks ? _self.remarks : remarks // ignore: cast_nullable_to_non_nullable
|
||||||
|
as dynamic,appIdentifier: null == appIdentifier ? _self.appIdentifier : appIdentifier // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,meta: null == meta ? _self._meta : meta // ignore: cast_nullable_to_non_nullable
|
||||||
|
as Map<String, dynamic>,amount: null == amount ? _self.amount : amount // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,expiredAt: null == expiredAt ? _self.expiredAt : expiredAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,payeeWalletId: freezed == payeeWalletId ? _self.payeeWalletId : payeeWalletId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,payeeWallet: freezed == payeeWallet ? _self.payeeWallet : payeeWallet // ignore: cast_nullable_to_non_nullable
|
||||||
|
as SnWallet?,transactionId: freezed == transactionId ? _self.transactionId : transactionId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,transaction: freezed == transaction ? _self.transaction : transaction // ignore: cast_nullable_to_non_nullable
|
||||||
|
as SnTransaction?,issuerAppId: freezed == issuerAppId ? _self.issuerAppId : issuerAppId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,issuerApp: freezed == issuerApp ? _self.issuerApp : issuerApp // 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 DateTime?,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a copy of SnWalletOrder
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SnWalletCopyWith<$Res>? get payeeWallet {
|
||||||
|
if (_self.payeeWallet == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $SnWalletCopyWith<$Res>(_self.payeeWallet!, (value) {
|
||||||
|
return _then(_self.copyWith(payeeWallet: value));
|
||||||
|
});
|
||||||
|
}/// Create a copy of SnWalletOrder
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SnTransactionCopyWith<$Res>? get transaction {
|
||||||
|
if (_self.transaction == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $SnTransactionCopyWith<$Res>(_self.transaction!, (value) {
|
||||||
|
return _then(_self.copyWith(transaction: value));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// dart format on
|
// dart format on
|
||||||
|
@ -100,3 +100,112 @@ Map<String, dynamic> _$SnTransactionToJson(_SnTransaction instance) =>
|
|||||||
'updated_at': instance.updatedAt.toIso8601String(),
|
'updated_at': instance.updatedAt.toIso8601String(),
|
||||||
'deleted_at': instance.deletedAt?.toIso8601String(),
|
'deleted_at': instance.deletedAt?.toIso8601String(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_SnWalletSubscription _$SnWalletSubscriptionFromJson(
|
||||||
|
Map<String, dynamic> json,
|
||||||
|
) => _SnWalletSubscription(
|
||||||
|
id: json['id'] as String,
|
||||||
|
begunAt: DateTime.parse(json['begun_at'] as String),
|
||||||
|
endedAt: DateTime.parse(json['ended_at'] as String),
|
||||||
|
identifier: json['identifier'] as String,
|
||||||
|
isActive: json['is_active'] as bool,
|
||||||
|
isFreeTrial: json['is_free_trial'] as bool,
|
||||||
|
status: (json['status'] as num).toInt(),
|
||||||
|
paymentMethod: json['payment_method'] as String,
|
||||||
|
paymentDetails: json['payment_details'] as Map<String, dynamic>,
|
||||||
|
basePrice: (json['base_price'] as num).toDouble(),
|
||||||
|
couponId: json['coupon_id'] as String?,
|
||||||
|
coupon: json['coupon'],
|
||||||
|
renewalAt: DateTime.parse(json['renewal_at'] as String),
|
||||||
|
accountId: json['account_id'] as String,
|
||||||
|
account:
|
||||||
|
json['account'] == null
|
||||||
|
? null
|
||||||
|
: SnAccount.fromJson(json['account'] as Map<String, dynamic>),
|
||||||
|
isAvailable: json['is_available'] as bool,
|
||||||
|
finalPrice: (json['final_price'] as num).toDouble(),
|
||||||
|
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> _$SnWalletSubscriptionToJson(
|
||||||
|
_SnWalletSubscription instance,
|
||||||
|
) => <String, dynamic>{
|
||||||
|
'id': instance.id,
|
||||||
|
'begun_at': instance.begunAt.toIso8601String(),
|
||||||
|
'ended_at': instance.endedAt.toIso8601String(),
|
||||||
|
'identifier': instance.identifier,
|
||||||
|
'is_active': instance.isActive,
|
||||||
|
'is_free_trial': instance.isFreeTrial,
|
||||||
|
'status': instance.status,
|
||||||
|
'payment_method': instance.paymentMethod,
|
||||||
|
'payment_details': instance.paymentDetails,
|
||||||
|
'base_price': instance.basePrice,
|
||||||
|
'coupon_id': instance.couponId,
|
||||||
|
'coupon': instance.coupon,
|
||||||
|
'renewal_at': instance.renewalAt.toIso8601String(),
|
||||||
|
'account_id': instance.accountId,
|
||||||
|
'account': instance.account?.toJson(),
|
||||||
|
'is_available': instance.isAvailable,
|
||||||
|
'final_price': instance.finalPrice,
|
||||||
|
'created_at': instance.createdAt.toIso8601String(),
|
||||||
|
'updated_at': instance.updatedAt.toIso8601String(),
|
||||||
|
'deleted_at': instance.deletedAt?.toIso8601String(),
|
||||||
|
};
|
||||||
|
|
||||||
|
_SnWalletOrder _$SnWalletOrderFromJson(Map<String, dynamic> json) =>
|
||||||
|
_SnWalletOrder(
|
||||||
|
id: json['id'] as String,
|
||||||
|
status: (json['status'] as num).toInt(),
|
||||||
|
currency: json['currency'] as String,
|
||||||
|
remarks: json['remarks'],
|
||||||
|
appIdentifier: json['app_identifier'] as String,
|
||||||
|
meta: json['meta'] as Map<String, dynamic>? ?? const {},
|
||||||
|
amount: (json['amount'] as num).toInt(),
|
||||||
|
expiredAt: DateTime.parse(json['expired_at'] as String),
|
||||||
|
payeeWalletId: json['payee_wallet_id'] as String?,
|
||||||
|
payeeWallet:
|
||||||
|
json['payee_wallet'] == null
|
||||||
|
? null
|
||||||
|
: SnWallet.fromJson(json['payee_wallet'] as Map<String, dynamic>),
|
||||||
|
transactionId: json['transaction_id'] as String?,
|
||||||
|
transaction:
|
||||||
|
json['transaction'] == null
|
||||||
|
? null
|
||||||
|
: SnTransaction.fromJson(
|
||||||
|
json['transaction'] as Map<String, dynamic>,
|
||||||
|
),
|
||||||
|
issuerAppId: json['issuer_app_id'] as String?,
|
||||||
|
issuerApp: json['issuer_app'],
|
||||||
|
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> _$SnWalletOrderToJson(_SnWalletOrder instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'id': instance.id,
|
||||||
|
'status': instance.status,
|
||||||
|
'currency': instance.currency,
|
||||||
|
'remarks': instance.remarks,
|
||||||
|
'app_identifier': instance.appIdentifier,
|
||||||
|
'meta': instance.meta,
|
||||||
|
'amount': instance.amount,
|
||||||
|
'expired_at': instance.expiredAt.toIso8601String(),
|
||||||
|
'payee_wallet_id': instance.payeeWalletId,
|
||||||
|
'payee_wallet': instance.payeeWallet?.toJson(),
|
||||||
|
'transaction_id': instance.transactionId,
|
||||||
|
'transaction': instance.transaction?.toJson(),
|
||||||
|
'issuer_app_id': instance.issuerAppId,
|
||||||
|
'issuer_app': instance.issuerApp,
|
||||||
|
'created_at': instance.createdAt.toIso8601String(),
|
||||||
|
'updated_at': instance.updatedAt.toIso8601String(),
|
||||||
|
'deleted_at': instance.deletedAt?.toIso8601String(),
|
||||||
|
};
|
||||||
|
@ -66,6 +66,8 @@ class CallNotifier extends _$CallNotifier {
|
|||||||
|
|
||||||
Timer? _durationTimer;
|
Timer? _durationTimer;
|
||||||
|
|
||||||
|
Room? get room => _room;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
CallState build() {
|
CallState build() {
|
||||||
// Subscribe to websocket updates
|
// Subscribe to websocket updates
|
||||||
@ -230,6 +232,9 @@ class CallNotifier extends _$CallNotifier {
|
|||||||
String? get roomId => _roomId;
|
String? get roomId => _roomId;
|
||||||
|
|
||||||
Future<void> joinRoom(String roomId) async {
|
Future<void> joinRoom(String roomId) async {
|
||||||
|
if (_roomId == roomId && _room != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
_roomId = roomId;
|
_roomId = roomId;
|
||||||
if (_room != null) {
|
if (_room != null) {
|
||||||
await _room!.disconnect();
|
await _room!.disconnect();
|
||||||
|
@ -6,7 +6,7 @@ part of 'call.dart';
|
|||||||
// RiverpodGenerator
|
// RiverpodGenerator
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
String _$callNotifierHash() => r'2082a572b5cfb4bf929dc1ed492c52cd2735452e';
|
String _$callNotifierHash() => r'47eaba43aa2af1a107725998f4a34af2c94fbc55';
|
||||||
|
|
||||||
/// See also [CallNotifier].
|
/// See also [CallNotifier].
|
||||||
@ProviderFor(CallNotifier)
|
@ProviderFor(CallNotifier)
|
||||||
|
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
|
// RiverpodGenerator
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
String _$accountEventCalendarHash() =>
|
String _$eventCalendarHash() => r'6f2454404fa8660b96334d654490e1a40ee53e10';
|
||||||
r'57405caaf53a83d121b6bb4b70540134fb581525';
|
|
||||||
|
|
||||||
/// Copied from Dart SDK
|
/// Copied from Dart SDK
|
||||||
class _SystemHash {
|
class _SystemHash {
|
||||||
@ -30,24 +29,36 @@ class _SystemHash {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// See also [accountEventCalendar].
|
/// Provider for fetching event calendar data
|
||||||
@ProviderFor(accountEventCalendar)
|
/// This can be used anywhere in the app where calendar data is needed
|
||||||
const accountEventCalendarProvider = AccountEventCalendarFamily();
|
///
|
||||||
|
/// Copied from [eventCalendar].
|
||||||
|
@ProviderFor(eventCalendar)
|
||||||
|
const eventCalendarProvider = EventCalendarFamily();
|
||||||
|
|
||||||
/// See also [accountEventCalendar].
|
/// Provider for fetching event calendar data
|
||||||
class AccountEventCalendarFamily
|
/// This can be used anywhere in the app where calendar data is needed
|
||||||
|
///
|
||||||
|
/// Copied from [eventCalendar].
|
||||||
|
class EventCalendarFamily
|
||||||
extends Family<AsyncValue<List<SnEventCalendarEntry>>> {
|
extends Family<AsyncValue<List<SnEventCalendarEntry>>> {
|
||||||
/// See also [accountEventCalendar].
|
/// Provider for fetching event calendar data
|
||||||
const AccountEventCalendarFamily();
|
/// This can be used anywhere in the app where calendar data is needed
|
||||||
|
///
|
||||||
|
/// Copied from [eventCalendar].
|
||||||
|
const EventCalendarFamily();
|
||||||
|
|
||||||
/// See also [accountEventCalendar].
|
/// Provider for fetching event calendar data
|
||||||
AccountEventCalendarProvider call(EventCalendarQuery query) {
|
/// This can be used anywhere in the app where calendar data is needed
|
||||||
return AccountEventCalendarProvider(query);
|
///
|
||||||
|
/// Copied from [eventCalendar].
|
||||||
|
EventCalendarProvider call(EventCalendarQuery query) {
|
||||||
|
return EventCalendarProvider(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
AccountEventCalendarProvider getProviderOverride(
|
EventCalendarProvider getProviderOverride(
|
||||||
covariant AccountEventCalendarProvider provider,
|
covariant EventCalendarProvider provider,
|
||||||
) {
|
) {
|
||||||
return call(provider.query);
|
return call(provider.query);
|
||||||
}
|
}
|
||||||
@ -64,29 +75,35 @@ class AccountEventCalendarFamily
|
|||||||
_allTransitiveDependencies;
|
_allTransitiveDependencies;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String? get name => r'accountEventCalendarProvider';
|
String? get name => r'eventCalendarProvider';
|
||||||
}
|
}
|
||||||
|
|
||||||
/// See also [accountEventCalendar].
|
/// Provider for fetching event calendar data
|
||||||
class AccountEventCalendarProvider
|
/// This can be used anywhere in the app where calendar data is needed
|
||||||
|
///
|
||||||
|
/// Copied from [eventCalendar].
|
||||||
|
class EventCalendarProvider
|
||||||
extends AutoDisposeFutureProvider<List<SnEventCalendarEntry>> {
|
extends AutoDisposeFutureProvider<List<SnEventCalendarEntry>> {
|
||||||
/// See also [accountEventCalendar].
|
/// Provider for fetching event calendar data
|
||||||
AccountEventCalendarProvider(EventCalendarQuery query)
|
/// This can be used anywhere in the app where calendar data is needed
|
||||||
|
///
|
||||||
|
/// Copied from [eventCalendar].
|
||||||
|
EventCalendarProvider(EventCalendarQuery query)
|
||||||
: this._internal(
|
: this._internal(
|
||||||
(ref) => accountEventCalendar(ref as AccountEventCalendarRef, query),
|
(ref) => eventCalendar(ref as EventCalendarRef, query),
|
||||||
from: accountEventCalendarProvider,
|
from: eventCalendarProvider,
|
||||||
name: r'accountEventCalendarProvider',
|
name: r'eventCalendarProvider',
|
||||||
debugGetCreateSourceHash:
|
debugGetCreateSourceHash:
|
||||||
const bool.fromEnvironment('dart.vm.product')
|
const bool.fromEnvironment('dart.vm.product')
|
||||||
? null
|
? null
|
||||||
: _$accountEventCalendarHash,
|
: _$eventCalendarHash,
|
||||||
dependencies: AccountEventCalendarFamily._dependencies,
|
dependencies: EventCalendarFamily._dependencies,
|
||||||
allTransitiveDependencies:
|
allTransitiveDependencies:
|
||||||
AccountEventCalendarFamily._allTransitiveDependencies,
|
EventCalendarFamily._allTransitiveDependencies,
|
||||||
query: query,
|
query: query,
|
||||||
);
|
);
|
||||||
|
|
||||||
AccountEventCalendarProvider._internal(
|
EventCalendarProvider._internal(
|
||||||
super._createNotifier, {
|
super._createNotifier, {
|
||||||
required super.name,
|
required super.name,
|
||||||
required super.dependencies,
|
required super.dependencies,
|
||||||
@ -100,15 +117,13 @@ class AccountEventCalendarProvider
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Override overrideWith(
|
Override overrideWith(
|
||||||
FutureOr<List<SnEventCalendarEntry>> Function(
|
FutureOr<List<SnEventCalendarEntry>> Function(EventCalendarRef provider)
|
||||||
AccountEventCalendarRef provider,
|
|
||||||
)
|
|
||||||
create,
|
create,
|
||||||
) {
|
) {
|
||||||
return ProviderOverride(
|
return ProviderOverride(
|
||||||
origin: this,
|
origin: this,
|
||||||
override: AccountEventCalendarProvider._internal(
|
override: EventCalendarProvider._internal(
|
||||||
(ref) => create(ref as AccountEventCalendarRef),
|
(ref) => create(ref as EventCalendarRef),
|
||||||
from: from,
|
from: from,
|
||||||
name: null,
|
name: null,
|
||||||
dependencies: null,
|
dependencies: null,
|
||||||
@ -121,12 +136,12 @@ class AccountEventCalendarProvider
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
AutoDisposeFutureProviderElement<List<SnEventCalendarEntry>> createElement() {
|
AutoDisposeFutureProviderElement<List<SnEventCalendarEntry>> createElement() {
|
||||||
return _AccountEventCalendarProviderElement(this);
|
return _EventCalendarProviderElement(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return other is AccountEventCalendarProvider && other.query == query;
|
return other is EventCalendarProvider && other.query == query;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -140,20 +155,19 @@ class AccountEventCalendarProvider
|
|||||||
|
|
||||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||||
// ignore: unused_element
|
// ignore: unused_element
|
||||||
mixin AccountEventCalendarRef
|
mixin EventCalendarRef
|
||||||
on AutoDisposeFutureProviderRef<List<SnEventCalendarEntry>> {
|
on AutoDisposeFutureProviderRef<List<SnEventCalendarEntry>> {
|
||||||
/// The parameter `query` of this provider.
|
/// The parameter `query` of this provider.
|
||||||
EventCalendarQuery get query;
|
EventCalendarQuery get query;
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AccountEventCalendarProviderElement
|
class _EventCalendarProviderElement
|
||||||
extends AutoDisposeFutureProviderElement<List<SnEventCalendarEntry>>
|
extends AutoDisposeFutureProviderElement<List<SnEventCalendarEntry>>
|
||||||
with AccountEventCalendarRef {
|
with EventCalendarRef {
|
||||||
_AccountEventCalendarProviderElement(super.provider);
|
_EventCalendarProviderElement(super.provider);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
EventCalendarQuery get query =>
|
EventCalendarQuery get query => (origin as EventCalendarProvider).query;
|
||||||
(origin as AccountEventCalendarProvider).query;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ignore_for_file: type=lint
|
// ignore_for_file: type=lint
|
@ -16,27 +16,42 @@ import 'config.dart';
|
|||||||
final imagePickerProvider = Provider((ref) => ImagePicker());
|
final imagePickerProvider = Provider((ref) => ImagePicker());
|
||||||
|
|
||||||
final userAgentProvider = FutureProvider<String>((ref) async {
|
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;
|
final String platformInfo;
|
||||||
if (kIsWeb) {
|
if (kIsWeb) {
|
||||||
final deviceInfo = await DeviceInfoPlugin().webBrowserInfo;
|
final deviceInfo = await DeviceInfoPlugin().webBrowserInfo;
|
||||||
platformInfo = 'Web; ${deviceInfo.vendor}';
|
platformInfo = 'Web; ${sanitizeForHeader(deviceInfo.vendor ?? 'Unknown')}';
|
||||||
} else if (Platform.isAndroid) {
|
} else if (Platform.isAndroid) {
|
||||||
final deviceInfo = await DeviceInfoPlugin().androidInfo;
|
final deviceInfo = await DeviceInfoPlugin().androidInfo;
|
||||||
platformInfo =
|
platformInfo =
|
||||||
'Android; ${deviceInfo.brand} ${deviceInfo.model}; ${deviceInfo.id}';
|
'Android; ${sanitizeForHeader(deviceInfo.brand)} ${sanitizeForHeader(deviceInfo.model)}; ${sanitizeForHeader(deviceInfo.id)}';
|
||||||
} else if (Platform.isIOS) {
|
} else if (Platform.isIOS) {
|
||||||
final deviceInfo = await DeviceInfoPlugin().iosInfo;
|
final deviceInfo = await DeviceInfoPlugin().iosInfo;
|
||||||
platformInfo = 'iOS; ${deviceInfo.model}; ${deviceInfo.name}';
|
platformInfo =
|
||||||
|
'iOS; ${sanitizeForHeader(deviceInfo.model)}; ${sanitizeForHeader(deviceInfo.name)}';
|
||||||
} else if (Platform.isMacOS) {
|
} else if (Platform.isMacOS) {
|
||||||
final deviceInfo = await DeviceInfoPlugin().macOsInfo;
|
final deviceInfo = await DeviceInfoPlugin().macOsInfo;
|
||||||
platformInfo = 'MacOS; ${deviceInfo.model}; ${deviceInfo.hostName}';
|
platformInfo =
|
||||||
|
'MacOS; ${sanitizeForHeader(deviceInfo.model)}; ${sanitizeForHeader(deviceInfo.hostName)}';
|
||||||
} else if (Platform.isWindows) {
|
} else if (Platform.isWindows) {
|
||||||
final deviceInfo = await DeviceInfoPlugin().windowsInfo;
|
final deviceInfo = await DeviceInfoPlugin().windowsInfo;
|
||||||
platformInfo =
|
platformInfo =
|
||||||
'Windows NT; ${deviceInfo.productName}; ${deviceInfo.computerName}';
|
'Windows NT; ${sanitizeForHeader(deviceInfo.productName)}; ${sanitizeForHeader(deviceInfo.computerName)}';
|
||||||
} else if (Platform.isLinux) {
|
} else if (Platform.isLinux) {
|
||||||
final deviceInfo = await DeviceInfoPlugin().linuxInfo;
|
final deviceInfo = await DeviceInfoPlugin().linuxInfo;
|
||||||
platformInfo = 'Linux; ${deviceInfo.prettyName}';
|
platformInfo = 'Linux; ${sanitizeForHeader(deviceInfo.prettyName)}';
|
||||||
} else {
|
} else {
|
||||||
platformInfo = 'Unknown';
|
platformInfo = 'Unknown';
|
||||||
}
|
}
|
||||||
|
@ -33,6 +33,7 @@ class UserInfoNotifier extends StateNotifier<AsyncValue<SnAccount?>> {
|
|||||||
final prefs = _ref.read(sharedPreferencesProvider);
|
final prefs = _ref.read(sharedPreferencesProvider);
|
||||||
await prefs.remove(kTokenPairStoreKey);
|
await prefs.remove(kTokenPairStoreKey);
|
||||||
_ref.invalidate(userInfoProvider);
|
_ref.invalidate(userInfoProvider);
|
||||||
|
_ref.invalidate(tokenProvider);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,48 +9,59 @@ class AppRouter extends RootStackRouter {
|
|||||||
@override
|
@override
|
||||||
List<AutoRoute> get routes => [
|
List<AutoRoute> get routes => [
|
||||||
AutoRoute(
|
AutoRoute(
|
||||||
page: ExploreShellRoute.page,
|
page: TabsRoute.page,
|
||||||
path: '/',
|
path: '/',
|
||||||
children: [
|
children: [
|
||||||
AutoRoute(page: ExploreRoute.page, path: ''),
|
AutoRoute(
|
||||||
AutoRoute(page: PostComposeRoute.page, path: 'posts/compose'),
|
page: ExploreShellRoute.page,
|
||||||
AutoRoute(page: PostDetailRoute.page, path: 'posts/:id'),
|
path: '',
|
||||||
AutoRoute(page: PostEditRoute.page, path: 'posts/:id/edit'),
|
children: [
|
||||||
AutoRoute(page: PublisherProfileRoute.page, path: 'publishers/:name'),
|
AutoRoute(page: ExploreRoute.page, path: ''),
|
||||||
],
|
AutoRoute(page: PostDetailRoute.page, path: 'posts/:id'),
|
||||||
),
|
AutoRoute(
|
||||||
AutoRoute(
|
page: PublisherProfileRoute.page,
|
||||||
page: AccountShellRoute.page,
|
path: 'publishers/:name',
|
||||||
path: '/account',
|
),
|
||||||
children: [
|
],
|
||||||
AutoRoute(page: AccountRoute.page, path: ''),
|
),
|
||||||
AutoRoute(page: NotificationRoute.page, path: 'notifications'),
|
AutoRoute(
|
||||||
AutoRoute(page: WalletRoute.page, path: 'wallet'),
|
page: AccountShellRoute.page,
|
||||||
AutoRoute(page: RelationshipRoute.page, path: 'relationships'),
|
path: 'account',
|
||||||
AutoRoute(page: AccountProfileRoute.page, path: ':name'),
|
children: [
|
||||||
AutoRoute(page: UpdateProfileRoute.page, path: 'me/update'),
|
AutoRoute(page: AccountRoute.page, path: ''),
|
||||||
AutoRoute(page: AccountSettingsRoute.page, path: 'settings'),
|
AutoRoute(page: NotificationRoute.page, path: 'notifications'),
|
||||||
|
AutoRoute(page: WalletRoute.page, path: 'wallet'),
|
||||||
|
AutoRoute(page: RelationshipRoute.page, path: 'relationships'),
|
||||||
|
AutoRoute(page: AccountProfileRoute.page, path: ':name'),
|
||||||
|
AutoRoute(page: UpdateProfileRoute.page, path: 'me/update'),
|
||||||
|
AutoRoute(page: LevelingRoute.page, path: 'me/leveling'),
|
||||||
|
AutoRoute(page: AccountSettingsRoute.page, path: 'settings'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
AutoRoute(page: RealmListRoute.page, path: 'realms'),
|
||||||
|
AutoRoute(
|
||||||
|
page: ChatShellRoute.page,
|
||||||
|
path: 'chat',
|
||||||
|
children: [
|
||||||
|
AutoRoute(page: ChatListRoute.page, path: ''),
|
||||||
|
AutoRoute(page: ChatRoomRoute.page, path: ':id'),
|
||||||
|
AutoRoute(page: NewChatRoute.page, path: 'new'),
|
||||||
|
AutoRoute(page: EditChatRoute.page, path: ':id/edit'),
|
||||||
|
AutoRoute(page: ChatDetailRoute.page, path: ':id/detail'),
|
||||||
|
],
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
AutoRoute(page: PostComposeRoute.page, path: '/posts/compose'),
|
||||||
|
AutoRoute(page: PostEditRoute.page, path: '/posts/:id/edit'),
|
||||||
|
AutoRoute(page: CallRoute.page, path: '/chat/:id/call'),
|
||||||
AutoRoute(page: EventCalanderRoute.page, path: '/account/:name/calendar'),
|
AutoRoute(page: EventCalanderRoute.page, path: '/account/:name/calendar'),
|
||||||
AutoRoute(page: RealmListRoute.page, path: '/realms'),
|
|
||||||
AutoRoute(
|
|
||||||
page: ChatShellRoute.page,
|
|
||||||
path: '/chat',
|
|
||||||
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'),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
AutoRoute(
|
AutoRoute(
|
||||||
page: CreatorHubShellRoute.page,
|
page: CreatorHubShellRoute.page,
|
||||||
path: '/creators',
|
path: '/creators',
|
||||||
children: [
|
children: [
|
||||||
AutoRoute(page: CreatorHubRoute.page, path: ''),
|
AutoRoute(page: CreatorHubRoute.page, path: ''),
|
||||||
|
AutoRoute(page: CreatorPostListRoute.page, path: ':name/posts'),
|
||||||
AutoRoute(page: StickersRoute.page, path: ':name/stickers'),
|
AutoRoute(page: StickersRoute.page, path: ':name/stickers'),
|
||||||
AutoRoute(page: NewStickerPacksRoute.page, path: ':name/stickers/new'),
|
AutoRoute(page: NewStickerPacksRoute.page, path: ':name/stickers/new'),
|
||||||
AutoRoute(
|
AutoRoute(
|
||||||
|
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/route.gr.dart';
|
||||||
import 'package:island/screens/notification.dart';
|
import 'package:island/screens/notification.dart';
|
||||||
import 'package:island/services/responsive.dart';
|
import 'package:island/services/responsive.dart';
|
||||||
|
import 'package:island/widgets/account/account_name.dart';
|
||||||
import 'package:island/widgets/account/status.dart';
|
import 'package:island/widgets/account/status.dart';
|
||||||
import 'package:island/widgets/account/leveling_progress.dart';
|
import 'package:island/widgets/account/leveling_progress.dart';
|
||||||
import 'package:island/widgets/app_scaffold.dart';
|
import 'package:island/widgets/app_scaffold.dart';
|
||||||
@ -66,8 +67,9 @@ class AccountScreen extends HookConsumerWidget {
|
|||||||
|
|
||||||
return AppScaffold(
|
return AppScaffold(
|
||||||
noBackground: isWide,
|
noBackground: isWide,
|
||||||
appBar: AppBar(title: const Text('account').tr()),
|
appBar: AppBar(backgroundColor: Colors.transparent, toolbarHeight: 0),
|
||||||
body: SingleChildScrollView(
|
body: SingleChildScrollView(
|
||||||
|
padding: getTabbedPadding(context),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Card(
|
Card(
|
||||||
@ -83,7 +85,7 @@ class AccountScreen extends HookConsumerWidget {
|
|||||||
child: AspectRatio(
|
child: AspectRatio(
|
||||||
aspectRatio: 16 / 7,
|
aspectRatio: 16 / 7,
|
||||||
child: CloudImageWidget(
|
child: CloudImageWidget(
|
||||||
fileId: user.value!.profile.background!.id,
|
file: user.value?.profile.background,
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -94,7 +96,7 @@ class AccountScreen extends HookConsumerWidget {
|
|||||||
children: [
|
children: [
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
child: ProfilePictureWidget(
|
child: ProfilePictureWidget(
|
||||||
fileId: user.value?.profile.picture?.id,
|
file: user.value?.profile.picture,
|
||||||
radius: 24,
|
radius: 24,
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
@ -112,7 +114,13 @@ class AccountScreen extends HookConsumerWidget {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.baseline,
|
crossAxisAlignment: CrossAxisAlignment.baseline,
|
||||||
textBaseline: TextBaseline.alphabetic,
|
textBaseline: TextBaseline.alphabetic,
|
||||||
children: [
|
children: [
|
||||||
Text(user.value!.nick).bold().fontSize(16),
|
AccountName(
|
||||||
|
account: user.value!,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
Text('@${user.value!.name}'),
|
Text('@${user.value!.name}'),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -132,11 +140,16 @@ class AccountScreen extends HookConsumerWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
).padding(horizontal: 8),
|
).padding(horizontal: 8),
|
||||||
LevelingProgressCard(
|
GestureDetector(
|
||||||
level: user.value!.profile.level,
|
child: LevelingProgressCard(
|
||||||
experience: user.value!.profile.experience,
|
level: user.value!.profile.level,
|
||||||
progress: user.value!.profile.levelingProgress,
|
experience: user.value!.profile.experience,
|
||||||
).padding(horizontal: 8),
|
progress: user.value!.profile.levelingProgress,
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
context.router.push(LevelingRoute());
|
||||||
|
},
|
||||||
|
).padding(horizontal: 12),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
@ -214,16 +227,6 @@ class AccountScreen extends HookConsumerWidget {
|
|||||||
context.router.push(RelationshipRoute());
|
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),
|
const Divider(height: 1).padding(vertical: 8),
|
||||||
ListTile(
|
ListTile(
|
||||||
minTileHeight: 48,
|
minTileHeight: 48,
|
||||||
@ -235,6 +238,16 @@ class AccountScreen extends HookConsumerWidget {
|
|||||||
context.router.push(SettingsRoute());
|
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(
|
ListTile(
|
||||||
minTileHeight: 48,
|
minTileHeight: 48,
|
||||||
leading: const Icon(Symbols.manage_accounts),
|
leading: const Icon(Symbols.manage_accounts),
|
||||||
|
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),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
605
lib/screens/account/leveling.dart
Normal file
605
lib/screens/account/leveling.dart
Normal file
@ -0,0 +1,605 @@
|
|||||||
|
import 'package:auto_route/auto_route.dart';
|
||||||
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:gap/gap.dart';
|
||||||
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:island/models/user.dart';
|
||||||
|
import 'package:island/models/wallet.dart';
|
||||||
|
import 'package:island/pods/network.dart';
|
||||||
|
import 'package:island/pods/userinfo.dart';
|
||||||
|
import 'package:island/services/responsive.dart';
|
||||||
|
import 'package:island/widgets/account/leveling_progress.dart';
|
||||||
|
import 'package:island/widgets/alert.dart';
|
||||||
|
import 'package:island/widgets/app_scaffold.dart';
|
||||||
|
import 'package:island/widgets/payment/payment_overlay.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
|
||||||
|
@RoutePage()
|
||||||
|
class LevelingScreen extends HookConsumerWidget {
|
||||||
|
const LevelingScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final user = ref.watch(userInfoProvider);
|
||||||
|
|
||||||
|
if (user.value == null) {
|
||||||
|
return AppScaffold(
|
||||||
|
appBar: AppBar(title: Text('levelingProgress'.tr())),
|
||||||
|
body: const Center(child: CircularProgressIndicator()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final currentLevel = user.value!.profile.level;
|
||||||
|
final currentExp = user.value!.profile.experience;
|
||||||
|
final progress = user.value!.profile.levelingProgress;
|
||||||
|
|
||||||
|
return AppScaffold(
|
||||||
|
appBar: AppBar(title: Text('levelingProgress'.tr())),
|
||||||
|
body: SingleChildScrollView(
|
||||||
|
padding: getTabbedPadding(context, horizontal: 20, vertical: 20),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
// Current Progress Card
|
||||||
|
LevelingProgressCard(
|
||||||
|
level: currentLevel,
|
||||||
|
experience: currentExp,
|
||||||
|
progress: progress,
|
||||||
|
),
|
||||||
|
const Gap(24),
|
||||||
|
|
||||||
|
// Level Stairs Graph
|
||||||
|
Text(
|
||||||
|
'Level Progress',
|
||||||
|
style: Theme.of(
|
||||||
|
context,
|
||||||
|
).textTheme.headlineSmall?.copyWith(fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
const Gap(16),
|
||||||
|
|
||||||
|
// Stairs visualization with fixed height and horizontal scroll
|
||||||
|
_buildLevelStairs(context, currentLevel),
|
||||||
|
|
||||||
|
const Gap(24),
|
||||||
|
|
||||||
|
// Membership section
|
||||||
|
_buildMembershipSection(context, ref, user.value!),
|
||||||
|
const Gap(16),
|
||||||
|
|
||||||
|
// Unlocked features section
|
||||||
|
Container(
|
||||||
|
width: double.infinity,
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Unlocked Features',
|
||||||
|
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
const Gap(8),
|
||||||
|
Text(
|
||||||
|
'Features unlocked at your current level will be displayed here.',
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||||
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildLevelStairs(BuildContext context, int currentLevel) {
|
||||||
|
const totalLevels = 14;
|
||||||
|
const stairHeight = 20.0;
|
||||||
|
const stairWidth = 50.0;
|
||||||
|
const containerHeight = 280.0;
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
height: containerHeight,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
border: Border.all(
|
||||||
|
color: Theme.of(context).colorScheme.outline.withOpacity(0.2),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
child: SizedBox(
|
||||||
|
width: (totalLevels * (stairWidth + 8)) + 40,
|
||||||
|
height: containerHeight,
|
||||||
|
child: CustomPaint(
|
||||||
|
painter: LevelStairsPainter(
|
||||||
|
currentLevel: currentLevel,
|
||||||
|
totalLevels: totalLevels,
|
||||||
|
primaryColor: Theme.of(context).colorScheme.primary,
|
||||||
|
surfaceColor: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||||
|
onSurfaceColor: Theme.of(context).colorScheme.onSurface,
|
||||||
|
stairHeight: stairHeight,
|
||||||
|
stairWidth: stairWidth,
|
||||||
|
),
|
||||||
|
child: Stack(
|
||||||
|
children: List.generate(totalLevels, (index) {
|
||||||
|
final level = index + 1;
|
||||||
|
final isCompleted = level <= currentLevel;
|
||||||
|
final isCurrent = level == currentLevel;
|
||||||
|
|
||||||
|
// Calculate position from bottom
|
||||||
|
final bottomPosition = 0.0;
|
||||||
|
final leftPosition = 20.0 + (index * (stairWidth + 8));
|
||||||
|
|
||||||
|
// Make higher levels progressively taller
|
||||||
|
final progressiveHeight =
|
||||||
|
40.0 + (index * 15.0); // Base height + progressive increase
|
||||||
|
|
||||||
|
return Positioned(
|
||||||
|
left: leftPosition,
|
||||||
|
bottom: bottomPosition,
|
||||||
|
child: Container(
|
||||||
|
width: stairWidth,
|
||||||
|
height: progressiveHeight,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color:
|
||||||
|
isCompleted
|
||||||
|
? Theme.of(context).colorScheme.primary
|
||||||
|
: Theme.of(
|
||||||
|
context,
|
||||||
|
).colorScheme.surfaceContainerHigh,
|
||||||
|
borderRadius: const BorderRadius.only(
|
||||||
|
topLeft: Radius.circular(6),
|
||||||
|
topRight: Radius.circular(6),
|
||||||
|
),
|
||||||
|
border:
|
||||||
|
isCurrent
|
||||||
|
? Border.all(
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
width: 2,
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
boxShadow:
|
||||||
|
isCurrent
|
||||||
|
? [
|
||||||
|
BoxShadow(
|
||||||
|
color: Theme.of(
|
||||||
|
context,
|
||||||
|
).colorScheme.primary.withOpacity(0.3),
|
||||||
|
blurRadius: 6,
|
||||||
|
spreadRadius: 1,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 8),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
level.toString(),
|
||||||
|
style: GoogleFonts.robotoMono(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color:
|
||||||
|
isCompleted
|
||||||
|
? Theme.of(context).colorScheme.onPrimary
|
||||||
|
: Theme.of(context).colorScheme.onSurface,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (isCurrent) ...[
|
||||||
|
const Gap(4),
|
||||||
|
Container(
|
||||||
|
width: 4,
|
||||||
|
height: 4,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context).colorScheme.onPrimary,
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildMembershipSection(
|
||||||
|
BuildContext context,
|
||||||
|
WidgetRef ref,
|
||||||
|
SnAccount user,
|
||||||
|
) {
|
||||||
|
final membership = user.profile.stellarMembership;
|
||||||
|
final isActive = membership?.isActive ?? false;
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
width: double.infinity,
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
gradient: LinearGradient(
|
||||||
|
colors: [
|
||||||
|
Theme.of(context).colorScheme.primaryContainer,
|
||||||
|
Theme.of(context).colorScheme.secondaryContainer,
|
||||||
|
],
|
||||||
|
begin: Alignment.topLeft,
|
||||||
|
end: Alignment.bottomRight,
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
isActive ? Icons.star : Icons.star_border,
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
size: 24,
|
||||||
|
),
|
||||||
|
const Gap(8),
|
||||||
|
Text(
|
||||||
|
'Stellar Membership',
|
||||||
|
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const Gap(12),
|
||||||
|
|
||||||
|
if (isActive) ...[
|
||||||
|
_buildCurrentMembershipCard(context, membership!),
|
||||||
|
const Gap(16),
|
||||||
|
],
|
||||||
|
|
||||||
|
Text(
|
||||||
|
isActive ? 'Upgrade Your Plan' : 'Choose Your Plan',
|
||||||
|
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
|
||||||
|
),
|
||||||
|
const Gap(12),
|
||||||
|
|
||||||
|
_buildMembershipTiers(context, ref, membership),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildCurrentMembershipCard(
|
||||||
|
BuildContext context,
|
||||||
|
SnWalletSubscription membership,
|
||||||
|
) {
|
||||||
|
final tierName = _getMembershipTierName(membership.identifier);
|
||||||
|
final tierColor = _getMembershipTierColor(context, membership.identifier);
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: tierColor.withOpacity(0.1),
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
border: Border.all(color: tierColor, width: 1),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Icon(Icons.verified, color: tierColor, size: 20),
|
||||||
|
const Gap(8),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Current: $tierName',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: tierColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
'Expires: ${_formatDate(membership.endedAt)}',
|
||||||
|
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||||
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildMembershipTiers(
|
||||||
|
BuildContext context,
|
||||||
|
WidgetRef ref,
|
||||||
|
SnWalletSubscription? currentMembership,
|
||||||
|
) {
|
||||||
|
final tiers = [
|
||||||
|
{
|
||||||
|
'id': 'solian.stellar.primary',
|
||||||
|
'name': 'Stellar',
|
||||||
|
'price': '10 NS\$ per month',
|
||||||
|
'features': [
|
||||||
|
'Basic features',
|
||||||
|
'Priority support',
|
||||||
|
'Ad-free experience',
|
||||||
|
],
|
||||||
|
'color': Colors.blue,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 'solian.stellar.nova',
|
||||||
|
'name': 'Nova',
|
||||||
|
'price': '20 NS\$ per month',
|
||||||
|
'features': [
|
||||||
|
'All Primary features',
|
||||||
|
'Advanced customization',
|
||||||
|
'Early access',
|
||||||
|
],
|
||||||
|
'color': Colors.purple,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 'solian.stellar.supernova',
|
||||||
|
'name': 'Supernova',
|
||||||
|
'price': '30 NS\$ per month',
|
||||||
|
'features': ['All Nova features', 'Exclusive content', 'VIP support'],
|
||||||
|
'color': Colors.orange,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
children:
|
||||||
|
tiers.map((tier) {
|
||||||
|
final isCurrentTier = currentMembership?.identifier == tier['id'];
|
||||||
|
final tierColor = tier['color'] as Color;
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
margin: const EdgeInsets.only(bottom: 8),
|
||||||
|
child: Material(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: InkWell(
|
||||||
|
onTap:
|
||||||
|
isCurrentTier
|
||||||
|
? null
|
||||||
|
: () => _purchaseMembership(
|
||||||
|
context,
|
||||||
|
ref,
|
||||||
|
tier['id'] as String,
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color:
|
||||||
|
isCurrentTier
|
||||||
|
? tierColor.withOpacity(0.1)
|
||||||
|
: Theme.of(context).colorScheme.surface,
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
border: Border.all(
|
||||||
|
color:
|
||||||
|
isCurrentTier
|
||||||
|
? tierColor
|
||||||
|
: Theme.of(
|
||||||
|
context,
|
||||||
|
).colorScheme.outline.withOpacity(0.2),
|
||||||
|
width: isCurrentTier ? 2 : 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
width: 4,
|
||||||
|
height: 40,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: tierColor,
|
||||||
|
borderRadius: BorderRadius.circular(2),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Gap(12),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
tier['name'] as String,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: isCurrentTier ? tierColor : null,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Gap(8),
|
||||||
|
if (isCurrentTier)
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 6,
|
||||||
|
vertical: 2,
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: tierColor,
|
||||||
|
borderRadius: BorderRadius.circular(4),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
'CURRENT',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 10,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
tier['price'] as String,
|
||||||
|
style: Theme.of(
|
||||||
|
context,
|
||||||
|
).textTheme.bodyMedium?.copyWith(
|
||||||
|
color:
|
||||||
|
Theme.of(
|
||||||
|
context,
|
||||||
|
).colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (!isCurrentTier)
|
||||||
|
Icon(
|
||||||
|
Icons.arrow_forward_ios,
|
||||||
|
size: 16,
|
||||||
|
color:
|
||||||
|
Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String _getMembershipTierName(String identifier) {
|
||||||
|
switch (identifier) {
|
||||||
|
case 'solian.stellar.primary':
|
||||||
|
return 'Primary';
|
||||||
|
case 'solian.stellar.nova':
|
||||||
|
return 'Nova';
|
||||||
|
case 'solian.stellar.supernova':
|
||||||
|
return 'Supernova';
|
||||||
|
default:
|
||||||
|
return 'Unknown';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Color _getMembershipTierColor(BuildContext context, String identifier) {
|
||||||
|
switch (identifier) {
|
||||||
|
case 'solian.stellar.primary':
|
||||||
|
return Colors.blue;
|
||||||
|
case 'solian.stellar.nova':
|
||||||
|
return Colors.purple;
|
||||||
|
case 'solian.stellar.supernova':
|
||||||
|
return Colors.orange;
|
||||||
|
default:
|
||||||
|
return Theme.of(context).colorScheme.primary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _formatDate(DateTime date) {
|
||||||
|
return '${date.day}/${date.month}/${date.year}';
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _purchaseMembership(
|
||||||
|
BuildContext context,
|
||||||
|
WidgetRef ref,
|
||||||
|
String tierId,
|
||||||
|
) async {
|
||||||
|
final client = ref.watch(apiClientProvider);
|
||||||
|
try {
|
||||||
|
showLoadingModal(context);
|
||||||
|
final resp = await client.post(
|
||||||
|
'/subscriptions',
|
||||||
|
data: {
|
||||||
|
'identifier': tierId,
|
||||||
|
'payment_method': 'solian.wallet',
|
||||||
|
'payment_details': {'currency': 'golds'},
|
||||||
|
'cycle_duration_days': 30,
|
||||||
|
},
|
||||||
|
options: Options(headers: {'X-Noop': true}),
|
||||||
|
);
|
||||||
|
final subscription = SnWalletSubscription.fromJson(resp.data);
|
||||||
|
if (subscription.status == 1) return;
|
||||||
|
final orderResp = await client.post(
|
||||||
|
'/subscriptions/${subscription.identifier}/order',
|
||||||
|
);
|
||||||
|
final order = SnWalletOrder.fromJson(orderResp.data);
|
||||||
|
|
||||||
|
if (context.mounted) hideLoadingModal(context);
|
||||||
|
|
||||||
|
// Show payment overlay to complete the payment
|
||||||
|
if (!context.mounted) return;
|
||||||
|
final paidOrder = await PaymentOverlay.show(
|
||||||
|
context: context,
|
||||||
|
order: order,
|
||||||
|
enableBiometric: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (paidOrder != null) {
|
||||||
|
// Payment successful, refresh user info or show success message
|
||||||
|
ref.invalidate(userInfoProvider);
|
||||||
|
if (context.mounted) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text('membershipPurchaseSuccess'.tr()),
|
||||||
|
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (context.mounted) hideLoadingModal(context);
|
||||||
|
showErrorAlert(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class LevelStairsPainter extends CustomPainter {
|
||||||
|
final int currentLevel;
|
||||||
|
final int totalLevels;
|
||||||
|
final Color primaryColor;
|
||||||
|
final Color surfaceColor;
|
||||||
|
final Color onSurfaceColor;
|
||||||
|
final double stairHeight;
|
||||||
|
final double stairWidth;
|
||||||
|
|
||||||
|
LevelStairsPainter({
|
||||||
|
required this.currentLevel,
|
||||||
|
required this.totalLevels,
|
||||||
|
required this.primaryColor,
|
||||||
|
required this.surfaceColor,
|
||||||
|
required this.onSurfaceColor,
|
||||||
|
required this.stairHeight,
|
||||||
|
required this.stairWidth,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
void paint(Canvas canvas, Size size) {
|
||||||
|
final paint =
|
||||||
|
Paint()
|
||||||
|
..color = surfaceColor.withOpacity(0.2)
|
||||||
|
..strokeWidth = 1.5
|
||||||
|
..style = PaintingStyle.stroke;
|
||||||
|
|
||||||
|
// Draw connecting lines between stairs
|
||||||
|
for (int i = 0; i < totalLevels - 1; i++) {
|
||||||
|
final startX = 20.0 + (i * (stairWidth + 8)) + stairWidth;
|
||||||
|
final startHeight =
|
||||||
|
40.0 + (i * 15.0); // Progressive height for current stair
|
||||||
|
final startY = size.height - (20.0 + startHeight);
|
||||||
|
|
||||||
|
final endX = 20.0 + ((i + 1) * (stairWidth + 8));
|
||||||
|
final endHeight =
|
||||||
|
40.0 + ((i + 1) * 15.0); // Progressive height for next stair
|
||||||
|
final endY = size.height - (20.0 + endHeight);
|
||||||
|
|
||||||
|
canvas.drawLine(Offset(startX, startY), Offset(endX, endY), paint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
|
||||||
|
}
|
@ -1,232 +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/screens/account/profile.dart';
|
|
||||||
import 'package:island/widgets/app_scaffold.dart';
|
|
||||||
import 'package:island/widgets/content/cloud_files.dart';
|
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
|
||||||
import 'package:table_calendar/table_calendar.dart';
|
|
||||||
|
|
||||||
part 'event_calendar.g.dart';
|
|
||||||
part 'event_calendar.freezed.dart';
|
|
||||||
|
|
||||||
@freezed
|
|
||||||
sealed 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 EventCalanderScreen extends HookConsumerWidget {
|
|
||||||
final String name;
|
|
||||||
const EventCalanderScreen({super.key, @PathParam("name") required this.name});
|
|
||||||
|
|
||||||
@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 user = ref.watch(accountProvider(name));
|
|
||||||
final events = ref.watch(
|
|
||||||
accountEventCalendarProvider(
|
|
||||||
EventCalendarQuery(
|
|
||||||
uname: name,
|
|
||||||
year: selectedYear.value,
|
|
||||||
month: selectedMonth.value,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
final content = 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);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (name != 'me' && user.hasValue)
|
|
||||||
Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
border: Border.all(
|
|
||||||
width: 1 / MediaQuery.of(context).devicePixelRatio,
|
|
||||||
color: Theme.of(context).dividerColor,
|
|
||||||
),
|
|
||||||
borderRadius: BorderRadius.all(Radius.circular(8)),
|
|
||||||
),
|
|
||||||
margin: EdgeInsets.all(16),
|
|
||||||
child: Card(
|
|
||||||
margin: EdgeInsets.zero,
|
|
||||||
elevation: 0,
|
|
||||||
color: Colors.transparent,
|
|
||||||
child: ListTile(
|
|
||||||
leading: ProfilePictureWidget(
|
|
||||||
fileId: user.value!.profile.picture?.id,
|
|
||||||
),
|
|
||||||
title: Text(user.value!.nick).bold(),
|
|
||||||
subtitle: Text('@${user.value!.name}'),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
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: Card(margin: EdgeInsets.all(16), child: content),
|
|
||||||
).center()
|
|
||||||
: content,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
|
@ -6,15 +6,51 @@ import 'package:flutter/foundation.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.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/network.dart';
|
||||||
import 'package:island/pods/userinfo.dart';
|
import 'package:island/pods/userinfo.dart';
|
||||||
|
import 'package:island/screens/account/me/settings_auth_factors.dart';
|
||||||
|
import 'package:island/screens/account/me/settings_connections.dart';
|
||||||
|
import 'package:island/screens/account/me/settings_contacts.dart';
|
||||||
import 'package:island/screens/auth/captcha.dart';
|
import 'package:island/screens/auth/captcha.dart';
|
||||||
|
import 'package:island/screens/auth/login.dart';
|
||||||
import 'package:island/services/responsive.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/alert.dart';
|
||||||
import 'package:island/widgets/app_scaffold.dart';
|
import 'package:island/widgets/app_scaffold.dart';
|
||||||
|
import 'package:island/widgets/response.dart';
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
import 'package:styled_widget/styled_widget.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()
|
@RoutePage()
|
||||||
class AccountSettingsScreen extends HookConsumerWidget {
|
class AccountSettingsScreen extends HookConsumerWidget {
|
||||||
const AccountSettingsScreen({super.key});
|
const AccountSettingsScreen({super.key});
|
||||||
@ -32,6 +68,7 @@ class AccountSettingsScreen extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
if (!confirm || !context.mounted) return;
|
if (!confirm || !context.mounted) return;
|
||||||
try {
|
try {
|
||||||
|
showLoadingModal(context);
|
||||||
final client = ref.read(apiClientProvider);
|
final client = ref.read(apiClientProvider);
|
||||||
await client.delete('/accounts/me');
|
await client.delete('/accounts/me');
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
@ -39,13 +76,15 @@ class AccountSettingsScreen extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
showErrorAlert(err);
|
showErrorAlert(err);
|
||||||
|
} finally {
|
||||||
|
if (context.mounted) hideLoadingModal(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> requestResetPassword() async {
|
Future<void> requestResetPassword() async {
|
||||||
final confirm = await showConfirmAlert(
|
final confirm = await showConfirmAlert(
|
||||||
'accountPasswordChangeDescription'.tr(),
|
'accountPasswordChangeDescription'.tr(),
|
||||||
'accountPassword'.tr(),
|
'accountPasswordChange'.tr(),
|
||||||
);
|
);
|
||||||
if (!confirm || !context.mounted) return;
|
if (!confirm || !context.mounted) return;
|
||||||
final captchaTk = await Navigator.of(
|
final captchaTk = await Navigator.of(
|
||||||
@ -53,6 +92,7 @@ class AccountSettingsScreen extends HookConsumerWidget {
|
|||||||
).push(MaterialPageRoute(builder: (context) => CaptchaScreen()));
|
).push(MaterialPageRoute(builder: (context) => CaptchaScreen()));
|
||||||
if (captchaTk == null) return;
|
if (captchaTk == null) return;
|
||||||
try {
|
try {
|
||||||
|
if (context.mounted) showLoadingModal(context);
|
||||||
final userInfo = ref.read(userInfoProvider);
|
final userInfo = ref.read(userInfoProvider);
|
||||||
final client = ref.read(apiClientProvider);
|
final client = ref.read(apiClientProvider);
|
||||||
await client.post(
|
await client.post(
|
||||||
@ -64,84 +104,327 @@ class AccountSettingsScreen extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
showErrorAlert(err);
|
showErrorAlert(err);
|
||||||
|
} finally {
|
||||||
|
if (context.mounted) hideLoadingModal(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final authFactors = ref.watch(authFactorsProvider);
|
||||||
|
|
||||||
// Group settings into categories for better organization
|
// Group settings into categories for better organization
|
||||||
final securitySettings = [
|
final securitySettings = [
|
||||||
ListTile(
|
ListTile(
|
||||||
minLeadingWidth: 48,
|
minLeadingWidth: 48,
|
||||||
title: Text('accountPassword').tr(),
|
leading: const Icon(Symbols.devices),
|
||||||
subtitle: Text('accountPasswordDescription').tr().fontSize(12),
|
title: Text('authSessions').tr(),
|
||||||
|
subtitle: Text('authSessionsDescription').tr().fontSize(12),
|
||||||
contentPadding: const EdgeInsets.only(left: 24, right: 17),
|
contentPadding: const EdgeInsets.only(left: 24, right: 17),
|
||||||
leading: const Icon(Symbols.password),
|
|
||||||
trailing: const Icon(Symbols.chevron_right),
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
requestResetPassword();
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
isScrollControlled: true,
|
||||||
|
builder: (context) => const AccountSessionSheet(),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
ListTile(
|
ExpansionTile(
|
||||||
minLeadingWidth: 48,
|
leading: const Icon(
|
||||||
title: Text('accountTwoFactor').tr(),
|
Symbols.link,
|
||||||
subtitle: Text('accountTwoFactorDescription').tr().fontSize(12),
|
).alignment(Alignment.centerLeft).width(48),
|
||||||
contentPadding: const EdgeInsets.only(left: 24, right: 17),
|
title: Text('accountConnections').tr(),
|
||||||
leading: const Icon(Symbols.security),
|
subtitle: Text('accountConnectionsDescription').tr().fontSize(12),
|
||||||
trailing: const Icon(Symbols.chevron_right),
|
tilePadding: const EdgeInsets.only(left: 24, right: 17),
|
||||||
onTap: () {
|
children: [
|
||||||
// Navigate to two-factor authentication settings
|
ref
|
||||||
showDialog(
|
.watch(accountConnectionsProvider)
|
||||||
context: context,
|
.when(
|
||||||
builder:
|
data:
|
||||||
(context) => AlertDialog(
|
(connections) => Column(
|
||||||
title: Text('accountTwoFactor').tr(),
|
children: [
|
||||||
content: Text('accountTwoFactorSetupDescription').tr(),
|
for (final connection in connections)
|
||||||
actions: [
|
ListTile(
|
||||||
TextButton(
|
minLeadingWidth: 48,
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
contentPadding: const EdgeInsets.only(
|
||||||
child: Text('Close').tr(),
|
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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
TextButton(
|
error:
|
||||||
onPressed: () {
|
(err, _) => ResponseErrorWidget(
|
||||||
Navigator.of(context).pop();
|
error: err,
|
||||||
// Add navigation to 2FA setup screen
|
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);
|
||||||
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
child: Text('accountTwoFactorSetup').tr(),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
error:
|
||||||
},
|
(err, _) => ResponseErrorWidget(
|
||||||
|
error: err,
|
||||||
|
onRetry: () => ref.invalidate(authFactorsProvider),
|
||||||
|
),
|
||||||
|
loading: () => ResponseLoadingWidget(),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
];
|
ExpansionTile(
|
||||||
|
leading: const Icon(
|
||||||
final privacySettings = [
|
Symbols.contact_mail,
|
||||||
// ListTile(
|
).alignment(Alignment.centerLeft).width(48),
|
||||||
// minLeadingWidth: 48,
|
title: Text('accountContactMethod').tr(),
|
||||||
// title: Text('accountPrivacy').tr(),
|
subtitle: Text('accountContactMethodDescription').tr().fontSize(12),
|
||||||
// subtitle: Text('accountPrivacyDescription').tr().fontSize(12),
|
tilePadding: const EdgeInsets.only(left: 24, right: 17),
|
||||||
// contentPadding: const EdgeInsets.only(left: 24, right: 17),
|
children: [
|
||||||
// leading: const Icon(Symbols.visibility),
|
ref
|
||||||
// trailing: const Icon(Symbols.chevron_right),
|
.watch(contactMethodsProvider)
|
||||||
// onTap: () {
|
.when(
|
||||||
// // Navigate to privacy settings
|
data:
|
||||||
// },
|
(contacts) => Column(
|
||||||
// ),
|
children: [
|
||||||
ListTile(
|
for (final contact in contacts)
|
||||||
minLeadingWidth: 48,
|
ListTile(
|
||||||
title: Text('accountDataExport').tr(),
|
minLeadingWidth: 48,
|
||||||
subtitle: Text('accountDataExportDescription').tr().fontSize(12),
|
contentPadding: const EdgeInsets.only(
|
||||||
contentPadding: const EdgeInsets.only(left: 24, right: 17),
|
left: 16,
|
||||||
leading: const Icon(Symbols.download),
|
right: 17,
|
||||||
trailing: const Icon(Symbols.chevron_right),
|
top: 2,
|
||||||
onTap: () async {
|
bottom: 4,
|
||||||
final confirm = await showConfirmAlert(
|
),
|
||||||
'accountDataExportConfirmation'.tr(),
|
title: Text(
|
||||||
'accountDataExport'.tr(),
|
contact.content,
|
||||||
);
|
style:
|
||||||
if (!confirm || !context.mounted) return;
|
contact.verifiedAt == null
|
||||||
// Add data export logic
|
? TextStyle(
|
||||||
showSnackBar(context, 'accountDataExportRequested'.tr());
|
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(),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -172,10 +455,6 @@ class AccountSettingsScreen extends HookConsumerWidget {
|
|||||||
title: 'accountSecurityTitle',
|
title: 'accountSecurityTitle',
|
||||||
children: securitySettings,
|
children: securitySettings,
|
||||||
),
|
),
|
||||||
_SettingsSection(
|
|
||||||
title: 'accountPrivacyTitle',
|
|
||||||
children: privacySettings,
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -201,10 +480,6 @@ class AccountSettingsScreen extends HookConsumerWidget {
|
|||||||
title: 'accountSecurityTitle',
|
title: 'accountSecurityTitle',
|
||||||
children: securitySettings,
|
children: securitySettings,
|
||||||
),
|
),
|
||||||
_SettingsSection(
|
|
||||||
title: 'accountPrivacyTitle',
|
|
||||||
children: privacySettings,
|
|
||||||
),
|
|
||||||
_SettingsSection(
|
_SettingsSection(
|
||||||
title: 'accountDangerZoneTitle',
|
title: 'accountDangerZoneTitle',
|
||||||
children: dangerZoneSettings,
|
children: dangerZoneSettings,
|
||||||
|
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
|
359
lib/screens/account/me/settings_auth_factors.dart
Normal file
359
lib/screens/account/me/settings_auth_factors.dart
Normal file
@ -0,0 +1,359 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:math' as math;
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final width = math.min(400, MediaQuery.of(context).size.width);
|
||||||
|
|
||||||
|
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 ([0].contains(factorType.value))
|
||||||
|
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(),
|
||||||
|
)
|
||||||
|
else if ([4].contains(factorType.value))
|
||||||
|
OtpTextField(
|
||||||
|
showCursor: false,
|
||||||
|
numberOfFields: 6,
|
||||||
|
obscureText: false,
|
||||||
|
showFieldAsBox: true,
|
||||||
|
focusedBorderColor: Theme.of(context).colorScheme.primary,
|
||||||
|
fieldWidth: (width / 6) - 10,
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
onSubmit: (String verificationCode) {
|
||||||
|
secretController.text = verificationCode;
|
||||||
|
},
|
||||||
|
textStyle: Theme.of(context).textTheme.titleLarge!,
|
||||||
|
),
|
||||||
|
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()),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
392
lib/screens/account/me/settings_connections.dart
Normal file
392
lib/screens/account/me/settings_connections.dart
Normal file
@ -0,0 +1,392 @@
|
|||||||
|
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':
|
||||||
|
case 'afdian':
|
||||||
|
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();
|
||||||
|
case 'afdian':
|
||||||
|
return 'accountConnectionProviderAfdian'.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',
|
||||||
|
'afdian',
|
||||||
|
];
|
||||||
|
|
||||||
|
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':
|
||||||
|
case 'afdian':
|
||||||
|
await Navigator.of(context, rootNavigator: true).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),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -11,6 +11,7 @@ import 'package:island/pods/config.dart';
|
|||||||
import 'package:island/pods/network.dart';
|
import 'package:island/pods/network.dart';
|
||||||
import 'package:island/pods/userinfo.dart';
|
import 'package:island/pods/userinfo.dart';
|
||||||
import 'package:island/services/file.dart';
|
import 'package:island/services/file.dart';
|
||||||
|
import 'package:island/services/timezone.dart';
|
||||||
import 'package:island/widgets/alert.dart';
|
import 'package:island/widgets/alert.dart';
|
||||||
import 'package:island/widgets/app_scaffold.dart';
|
import 'package:island/widgets/app_scaffold.dart';
|
||||||
import 'package:island/widgets/content/cloud_files.dart';
|
import 'package:island/widgets/content/cloud_files.dart';
|
||||||
@ -120,9 +121,33 @@ class UpdateProfileScreen extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final formKeyProfile = useMemoized(GlobalKey<FormState>.new, const []);
|
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(
|
final bioController = useTextEditingController(
|
||||||
text: user.value!.profile.bio,
|
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 {
|
void updateProfile() async {
|
||||||
if (!formKeyProfile.currentState!.validate()) return;
|
if (!formKeyProfile.currentState!.validate()) return;
|
||||||
@ -132,7 +157,17 @@ class UpdateProfileScreen extends HookConsumerWidget {
|
|||||||
final client = ref.watch(apiClientProvider);
|
final client = ref.watch(apiClientProvider);
|
||||||
await client.patch(
|
await client.patch(
|
||||||
'/accounts/me/profile',
|
'/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);
|
final userNotifier = ref.read(userInfoProvider.notifier);
|
||||||
userNotifier.fetchUser();
|
userNotifier.fetchUser();
|
||||||
@ -268,6 +303,45 @@ class UpdateProfileScreen extends HookConsumerWidget {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
spacing: 16,
|
spacing: 16,
|
||||||
children: [
|
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(
|
TextFormField(
|
||||||
decoration: InputDecoration(labelText: 'bio'.tr()),
|
decoration: InputDecoration(labelText: 'bio'.tr()),
|
||||||
maxLines: null,
|
maxLines: null,
|
||||||
@ -276,6 +350,213 @@ class UpdateProfileScreen extends HookConsumerWidget {
|
|||||||
onTapOutside:
|
onTapOutside:
|
||||||
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
(_) => 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(
|
Align(
|
||||||
alignment: Alignment.centerRight,
|
alignment: Alignment.centerRight,
|
||||||
child: TextButton.icon(
|
child: TextButton.icon(
|
||||||
|
@ -1,16 +1,29 @@
|
|||||||
import 'package:auto_route/auto_route.dart';
|
import 'package:auto_route/auto_route.dart';
|
||||||
|
import 'package:dio/dio.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:island/models/chat.dart';
|
||||||
|
import 'package:island/models/relationship.dart';
|
||||||
import 'package:island/models/user.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/network.dart';
|
||||||
import 'package:island/pods/userinfo.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/badge.dart';
|
||||||
|
import 'package:island/widgets/account/fortune_graph.dart';
|
||||||
import 'package:island/widgets/account/leveling_progress.dart';
|
import 'package:island/widgets/account/leveling_progress.dart';
|
||||||
import 'package:island/widgets/account/status.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/app_scaffold.dart';
|
||||||
import 'package:island/widgets/content/cloud_files.dart';
|
import 'package:island/widgets/content/cloud_files.dart';
|
||||||
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
|
import 'package:palette_generator/palette_generator.dart';
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
|
||||||
@ -38,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()
|
@RoutePage()
|
||||||
class AccountProfileScreen extends HookConsumerWidget {
|
class AccountProfileScreen extends HookConsumerWidget {
|
||||||
final String name;
|
final String name;
|
||||||
@ -48,13 +106,118 @@ class AccountProfileScreen extends HookConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final account = ref.watch(accountProvider(name));
|
final now = DateTime.now();
|
||||||
|
|
||||||
final iconShadow = Shadow(
|
final account = ref.watch(accountProvider(name));
|
||||||
color: Colors.black54,
|
final accountEvents = ref.watch(
|
||||||
blurRadius: 5.0,
|
eventCalendarProvider(
|
||||||
offset: const Offset(1.0, 1.0),
|
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(
|
return account.when(
|
||||||
data:
|
data:
|
||||||
@ -62,26 +225,40 @@ class AccountProfileScreen extends HookConsumerWidget {
|
|||||||
body: CustomScrollView(
|
body: CustomScrollView(
|
||||||
slivers: [
|
slivers: [
|
||||||
SliverAppBar(
|
SliverAppBar(
|
||||||
|
foregroundColor: appbarColor.value,
|
||||||
expandedHeight: 180,
|
expandedHeight: 180,
|
||||||
pinned: true,
|
pinned: true,
|
||||||
leading: PageBackButton(shadows: [iconShadow]),
|
leading: PageBackButton(
|
||||||
flexibleSpace: FlexibleSpaceBar(
|
color: appbarColor.value,
|
||||||
background:
|
shadows: [appbarShadow],
|
||||||
data.profile.background?.id != null
|
),
|
||||||
? CloudImageWidget(
|
flexibleSpace: Stack(
|
||||||
fileId: data.profile.background!.id,
|
children: [
|
||||||
)
|
Positioned.fill(
|
||||||
: Container(
|
child:
|
||||||
color:
|
data.profile.background?.id != null
|
||||||
Theme.of(context).appBarTheme.backgroundColor,
|
? CloudImageWidget(
|
||||||
),
|
file: data.profile.background,
|
||||||
title: Text(
|
)
|
||||||
data.nick,
|
: Container(
|
||||||
style: TextStyle(
|
color:
|
||||||
color: Theme.of(context).appBarTheme.foregroundColor,
|
Theme.of(
|
||||||
shadows: [iconShadow],
|
context,
|
||||||
|
).appBarTheme.backgroundColor,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
FlexibleSpaceBar(
|
||||||
|
title: Text(
|
||||||
|
data.nick,
|
||||||
|
style: TextStyle(
|
||||||
|
color:
|
||||||
|
appbarColor.value ??
|
||||||
|
Theme.of(context).appBarTheme.foregroundColor,
|
||||||
|
shadows: [appbarShadow],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
@ -91,7 +268,7 @@ class AccountProfileScreen extends HookConsumerWidget {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
ProfilePictureWidget(
|
ProfilePictureWidget(
|
||||||
fileId: data.profile.picture?.id,
|
file: data.profile.picture,
|
||||||
radius: 32,
|
radius: 32,
|
||||||
),
|
),
|
||||||
const Gap(20),
|
const Gap(20),
|
||||||
@ -124,29 +301,144 @@ class AccountProfileScreen extends HookConsumerWidget {
|
|||||||
child: BadgeList(
|
child: BadgeList(
|
||||||
badges: data.badges,
|
badges: data.badges,
|
||||||
).padding(horizontal: 24, bottom: 24),
|
).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),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -267,5 +267,377 @@ class _AccountBadgesProviderElement
|
|||||||
String get uname => (origin as AccountBadgesProvider).uname;
|
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: type=lint
|
||||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
||||||
|
@ -1,9 +1,16 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
|
import 'dart:math' as math;
|
||||||
|
|
||||||
import 'package:animations/animations.dart';
|
import 'package:animations/animations.dart';
|
||||||
import 'package:auto_route/auto_route.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:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package: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:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
import 'package:island/models/auth.dart';
|
import 'package:island/models/auth.dart';
|
||||||
@ -11,11 +18,14 @@ import 'package:island/pods/config.dart';
|
|||||||
import 'package:island/pods/network.dart';
|
import 'package:island/pods/network.dart';
|
||||||
import 'package:island/pods/userinfo.dart';
|
import 'package:island/pods/userinfo.dart';
|
||||||
import 'package:island/pods/websocket.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/notify.dart';
|
||||||
import 'package:island/services/udid.dart';
|
import 'package:island/services/udid.dart';
|
||||||
import 'package:island/widgets/alert.dart';
|
import 'package:island/widgets/alert.dart';
|
||||||
import 'package:island/widgets/app_scaffold.dart';
|
import 'package:island/widgets/app_scaffold.dart';
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
|
import 'package:sign_in_with_apple/sign_in_with_apple.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
import 'package:url_launcher/url_launcher_string.dart';
|
import 'package:url_launcher/url_launcher_string.dart';
|
||||||
|
|
||||||
@ -24,12 +34,13 @@ import 'captcha.dart';
|
|||||||
final Map<int, (String, String, IconData)> kFactorTypes = {
|
final Map<int, (String, String, IconData)> kFactorTypes = {
|
||||||
0: ('authFactorPassword', 'authFactorPasswordDescription', Symbols.password),
|
0: ('authFactorPassword', 'authFactorPasswordDescription', Symbols.password),
|
||||||
1: ('authFactorEmail', 'authFactorEmailDescription', Symbols.email),
|
1: ('authFactorEmail', 'authFactorEmailDescription', Symbols.email),
|
||||||
2: ('authFactorTOTP', 'authFactorTOTPDescription', Symbols.timer),
|
2: (
|
||||||
3: (
|
|
||||||
'authFactorInAppNotify',
|
'authFactorInAppNotify',
|
||||||
'authFactorInAppNotifyDescription',
|
'authFactorInAppNotifyDescription',
|
||||||
Symbols.notifications_active,
|
Symbols.notifications_active,
|
||||||
),
|
),
|
||||||
|
3: ('authFactorTOTP', 'authFactorTOTPDescription', Symbols.timer),
|
||||||
|
4: ('authFactorPin', 'authFactorPinDescription', Symbols.nest_secure_alarm),
|
||||||
};
|
};
|
||||||
|
|
||||||
@RoutePage()
|
@RoutePage()
|
||||||
@ -38,10 +49,13 @@ class LoginScreen extends HookConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final isBusy = useState(false);
|
||||||
|
|
||||||
final period = useState(0);
|
final period = useState(0);
|
||||||
final currentTicket = useState<SnAuthChallenge?>(null);
|
final currentTicket = useState<SnAuthChallenge?>(null);
|
||||||
final factors = useState<List<SnAuthFactor>>([]);
|
final factors = useState<List<SnAuthFactor>>([]);
|
||||||
final factorPicked = useState<SnAuthFactor?>(null);
|
final factorPicked = useState<SnAuthFactor?>(null);
|
||||||
|
|
||||||
return AppScaffold(
|
return AppScaffold(
|
||||||
noBackground: false,
|
noBackground: false,
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
@ -50,54 +64,87 @@ class LoginScreen extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
body: Theme(
|
body: Theme(
|
||||||
data: Theme.of(context).copyWith(canvasColor: Colors.transparent),
|
data: Theme.of(context).copyWith(canvasColor: Colors.transparent),
|
||||||
child:
|
child: Column(
|
||||||
SingleChildScrollView(
|
children: [
|
||||||
child: PageTransitionSwitcher(
|
if (isBusy.value)
|
||||||
transitionBuilder: (
|
LinearProgressIndicator(
|
||||||
Widget child,
|
minHeight: 4,
|
||||||
Animation<double> primaryAnimation,
|
borderRadius: BorderRadius.zero,
|
||||||
Animation<double> secondaryAnimation,
|
trackGap: 0,
|
||||||
) {
|
stopIndicatorRadius: 0,
|
||||||
return SharedAxisTransition(
|
)
|
||||||
animation: primaryAnimation,
|
else if (currentTicket.value != null)
|
||||||
secondaryAnimation: secondaryAnimation,
|
LinearProgressIndicator(
|
||||||
transitionType: SharedAxisTransitionType.horizontal,
|
minHeight: 4,
|
||||||
child: Container(
|
borderRadius: BorderRadius.zero,
|
||||||
constraints: BoxConstraints(maxWidth: 380),
|
trackGap: 0,
|
||||||
child: child,
|
stopIndicatorRadius: 0,
|
||||||
),
|
value:
|
||||||
);
|
1 -
|
||||||
},
|
(currentTicket.value!.stepRemain /
|
||||||
child: switch (period.value % 3) {
|
currentTicket.value!.stepTotal),
|
||||||
1 => _LoginPickerScreen(
|
)
|
||||||
key: const ValueKey(1),
|
else
|
||||||
ticket: currentTicket.value,
|
const Gap(4),
|
||||||
factors: factors.value,
|
Expanded(
|
||||||
onChallenge:
|
child:
|
||||||
(SnAuthChallenge? p0) => currentTicket.value = p0,
|
SingleChildScrollView(
|
||||||
onPickFactor: (SnAuthFactor p0) => factorPicked.value = p0,
|
child: PageTransitionSwitcher(
|
||||||
onNext: () => period.value++,
|
transitionBuilder: (
|
||||||
),
|
Widget child,
|
||||||
2 => _LoginCheckScreen(
|
Animation<double> primaryAnimation,
|
||||||
key: const ValueKey(2),
|
Animation<double> secondaryAnimation,
|
||||||
challenge: currentTicket.value,
|
) {
|
||||||
factor: factorPicked.value,
|
return SharedAxisTransition(
|
||||||
onChallenge:
|
animation: primaryAnimation,
|
||||||
(SnAuthChallenge? p0) => currentTicket.value = p0,
|
secondaryAnimation: secondaryAnimation,
|
||||||
onNext: () => period.value++,
|
transitionType: SharedAxisTransitionType.horizontal,
|
||||||
),
|
child: Container(
|
||||||
_ => _LoginLookupScreen(
|
constraints: BoxConstraints(maxWidth: 380),
|
||||||
key: const ValueKey(0),
|
child: child,
|
||||||
ticket: currentTicket.value,
|
),
|
||||||
onChallenge:
|
);
|
||||||
(SnAuthChallenge? p0) => currentTicket.value = p0,
|
},
|
||||||
onFactor:
|
child: switch (period.value % 3) {
|
||||||
(List<SnAuthFactor>? p0) => factors.value = p0 ?? [],
|
1 => _LoginPickerScreen(
|
||||||
onNext: () => period.value++,
|
key: const ValueKey(1),
|
||||||
),
|
challenge: currentTicket.value,
|
||||||
},
|
factors: factors.value,
|
||||||
).padding(all: 24),
|
onChallenge:
|
||||||
).center(),
|
(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),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -107,7 +154,8 @@ class _LoginCheckScreen extends HookConsumerWidget {
|
|||||||
final SnAuthChallenge? challenge;
|
final SnAuthChallenge? challenge;
|
||||||
final SnAuthFactor? factor;
|
final SnAuthFactor? factor;
|
||||||
final Function(SnAuthChallenge?) onChallenge;
|
final Function(SnAuthChallenge?) onChallenge;
|
||||||
final Function onNext;
|
final VoidCallback onNext;
|
||||||
|
final Function(bool) onBusy;
|
||||||
|
|
||||||
const _LoginCheckScreen({
|
const _LoginCheckScreen({
|
||||||
super.key,
|
super.key,
|
||||||
@ -115,6 +163,7 @@ class _LoginCheckScreen extends HookConsumerWidget {
|
|||||||
required this.factor,
|
required this.factor,
|
||||||
required this.onChallenge,
|
required this.onChallenge,
|
||||||
required this.onNext,
|
required this.onNext,
|
||||||
|
required this.onBusy,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -122,11 +171,100 @@ class _LoginCheckScreen extends HookConsumerWidget {
|
|||||||
final isBusy = useState(false);
|
final isBusy = useState(false);
|
||||||
final passwordController = useTextEditingController();
|
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 {
|
Future<void> performCheckTicket() async {
|
||||||
final pwd = passwordController.value.text;
|
final pwd = passwordController.value.text;
|
||||||
if (pwd.isEmpty) return;
|
if (pwd.isEmpty) return;
|
||||||
isBusy.value = true;
|
isBusy.value = true;
|
||||||
try {
|
try {
|
||||||
|
// Pass challenge
|
||||||
final client = ref.watch(apiClientProvider);
|
final client = ref.watch(apiClientProvider);
|
||||||
final resp = await client.patch(
|
final resp = await client.patch(
|
||||||
'/auth/challenge/${challenge!.id}',
|
'/auth/challenge/${challenge!.id}',
|
||||||
@ -138,22 +276,8 @@ class _LoginCheckScreen extends HookConsumerWidget {
|
|||||||
onNext();
|
onNext();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final tokenResp = await client.post(
|
|
||||||
'/auth/token',
|
await getToken(code: result.id);
|
||||||
data: {'grant_type': 'authorization_code', 'code': result.id},
|
|
||||||
);
|
|
||||||
final token = tokenResp.data['token'];
|
|
||||||
setToken(ref.watch(sharedPreferencesProvider), token);
|
|
||||||
ref.invalidate(tokenProvider);
|
|
||||||
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);
|
|
||||||
});
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
showErrorAlert(err);
|
showErrorAlert(err);
|
||||||
return;
|
return;
|
||||||
@ -162,6 +286,8 @@ class _LoginCheckScreen extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final width = math.min(380, MediaQuery.of(context).size.width);
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@ -176,24 +302,46 @@ class _LoginCheckScreen extends HookConsumerWidget {
|
|||||||
'loginEnterPassword'.tr(),
|
'loginEnterPassword'.tr(),
|
||||||
style: const TextStyle(fontSize: 28, fontWeight: FontWeight.w900),
|
style: const TextStyle(fontSize: 28, fontWeight: FontWeight.w900),
|
||||||
).padding(left: 4, bottom: 16),
|
).padding(left: 4, bottom: 16),
|
||||||
TextField(
|
if ([0].contains(factor!.type))
|
||||||
autocorrect: false,
|
TextField(
|
||||||
enableSuggestions: false,
|
autocorrect: false,
|
||||||
controller: passwordController,
|
enableSuggestions: false,
|
||||||
obscureText: true,
|
controller: passwordController,
|
||||||
autofillHints: [
|
obscureText: true,
|
||||||
factor!.type == 0
|
autofillHints: [
|
||||||
? AutofillHints.password
|
factor!.type == 0
|
||||||
: AutofillHints.oneTimeCode,
|
? AutofillHints.password
|
||||||
],
|
: AutofillHints.oneTimeCode,
|
||||||
decoration: InputDecoration(
|
],
|
||||||
isDense: true,
|
decoration: InputDecoration(
|
||||||
border: const UnderlineInputBorder(),
|
isDense: true,
|
||||||
labelText: 'password'.tr(),
|
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(),
|
const Gap(12),
|
||||||
onSubmitted: isBusy.value ? null : (_) => performCheckTicket(),
|
ListTile(
|
||||||
).padding(horizontal: 7),
|
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),
|
const Gap(12),
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
@ -216,30 +364,48 @@ class _LoginCheckScreen extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _LoginPickerScreen extends HookConsumerWidget {
|
class _LoginPickerScreen extends HookConsumerWidget {
|
||||||
final SnAuthChallenge? ticket;
|
final SnAuthChallenge? challenge;
|
||||||
final List<SnAuthFactor>? factors;
|
final List<SnAuthFactor>? factors;
|
||||||
final Function(SnAuthChallenge?) onChallenge;
|
final Function(SnAuthChallenge?) onChallenge;
|
||||||
final Function(SnAuthFactor) onPickFactor;
|
final Function(SnAuthFactor) onPickFactor;
|
||||||
final Function onNext;
|
final VoidCallback onNext;
|
||||||
|
final Function(bool) onBusy;
|
||||||
|
|
||||||
const _LoginPickerScreen({
|
const _LoginPickerScreen({
|
||||||
super.key,
|
super.key,
|
||||||
required this.ticket,
|
required this.challenge,
|
||||||
required this.factors,
|
required this.factors,
|
||||||
required this.onChallenge,
|
required this.onChallenge,
|
||||||
required this.onPickFactor,
|
required this.onPickFactor,
|
||||||
required this.onNext,
|
required this.onNext,
|
||||||
|
required this.onBusy,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final isBusy = useState(false);
|
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(
|
final unfocusColor = Theme.of(
|
||||||
context,
|
context,
|
||||||
).colorScheme.onSurface.withAlpha((255 * 0.75).round());
|
).colorScheme.onSurface.withAlpha((255 * 0.75).round());
|
||||||
|
|
||||||
|
final hintController = useTextEditingController();
|
||||||
|
|
||||||
void performGetFactorCode() async {
|
void performGetFactorCode() async {
|
||||||
if (factorPicked.value == null) return;
|
if (factorPicked.value == null) return;
|
||||||
|
|
||||||
@ -247,13 +413,24 @@ class _LoginPickerScreen extends HookConsumerWidget {
|
|||||||
final client = ref.watch(apiClientProvider);
|
final client = ref.watch(apiClientProvider);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Request one-time-password code
|
|
||||||
await client.post(
|
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();
|
onNext();
|
||||||
} catch (err) {
|
} 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);
|
showErrorAlert(err);
|
||||||
return;
|
return;
|
||||||
} finally {
|
} finally {
|
||||||
@ -291,11 +468,11 @@ class _LoginPickerScreen extends HookConsumerWidget {
|
|||||||
kFactorTypes[x.type]?.$3 ?? Symbols.question_mark,
|
kFactorTypes[x.type]?.$3 ?? Symbols.question_mark,
|
||||||
),
|
),
|
||||||
title: Text(kFactorTypes[x.type]?.$1 ?? 'unknown').tr(),
|
title: Text(kFactorTypes[x.type]?.$1 ?? 'unknown').tr(),
|
||||||
enabled: !ticket!.blacklistFactors.contains(x.id),
|
enabled: !challenge!.blacklistFactors.contains(x.id),
|
||||||
value: factorPicked.value == x.id,
|
value: factorPicked.value == x,
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
if (value == true) {
|
if (value == true) {
|
||||||
factorPicked.value = x.id;
|
factorPicked.value = x;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -304,9 +481,19 @@ class _LoginPickerScreen extends HookConsumerWidget {
|
|||||||
List.empty(),
|
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),
|
const Gap(8),
|
||||||
Text(
|
Text(
|
||||||
'loginMultiFactor'.plural(ticket!.stepRemain),
|
'loginMultiFactor'.plural(challenge!.stepRemain),
|
||||||
style: TextStyle(color: unfocusColor, fontSize: 13),
|
style: TextStyle(color: unfocusColor, fontSize: 13),
|
||||||
).padding(horizontal: 16),
|
).padding(horizontal: 16),
|
||||||
const Gap(12),
|
const Gap(12),
|
||||||
@ -334,7 +521,8 @@ class _LoginLookupScreen extends HookConsumerWidget {
|
|||||||
final SnAuthChallenge? ticket;
|
final SnAuthChallenge? ticket;
|
||||||
final Function(SnAuthChallenge?) onChallenge;
|
final Function(SnAuthChallenge?) onChallenge;
|
||||||
final Function(List<SnAuthFactor>?) onFactor;
|
final Function(List<SnAuthFactor>?) onFactor;
|
||||||
final Function onNext;
|
final VoidCallback onNext;
|
||||||
|
final Function(bool) onBusy;
|
||||||
|
|
||||||
const _LoginLookupScreen({
|
const _LoginLookupScreen({
|
||||||
super.key,
|
super.key,
|
||||||
@ -342,6 +530,7 @@ class _LoginLookupScreen extends HookConsumerWidget {
|
|||||||
required this.onChallenge,
|
required this.onChallenge,
|
||||||
required this.onFactor,
|
required this.onFactor,
|
||||||
required this.onNext,
|
required this.onNext,
|
||||||
|
required this.onBusy,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -349,6 +538,11 @@ class _LoginLookupScreen extends HookConsumerWidget {
|
|||||||
final isBusy = useState(false);
|
final isBusy = useState(false);
|
||||||
final usernameController = useTextEditingController();
|
final usernameController = useTextEditingController();
|
||||||
|
|
||||||
|
useEffect(() {
|
||||||
|
onBusy.call(isBusy.value);
|
||||||
|
return null;
|
||||||
|
}, [isBusy]);
|
||||||
|
|
||||||
Future<void> requestResetPassword() async {
|
Future<void> requestResetPassword() async {
|
||||||
final uname = usernameController.value.text;
|
final uname = usernameController.value.text;
|
||||||
if (uname.isEmpty) {
|
if (uname.isEmpty) {
|
||||||
@ -417,6 +611,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, rootNavigator: true).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(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@ -445,7 +705,45 @@ class _LoginLookupScreen extends HookConsumerWidget {
|
|||||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
onSubmitted: isBusy.value ? null : (_) => performNewTicket(),
|
onSubmitted: isBusy.value ? null : (_) => performNewTicket(),
|
||||||
).padding(horizontal: 7),
|
).padding(horizontal: 7),
|
||||||
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(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
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,161 +0,0 @@
|
|||||||
import 'dart:developer';
|
|
||||||
|
|
||||||
import 'package:auto_route/auto_route.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/route.dart';
|
|
||||||
import 'package:island/route.gr.dart';
|
|
||||||
import 'package:island/screens/notification.dart';
|
|
||||||
import 'package:island/services/responsive.dart';
|
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
|
||||||
|
|
||||||
final currentRouteProvider = StateProvider<String?>((ref) => null);
|
|
||||||
|
|
||||||
class TabNavigationObserver extends AutoRouterObserver {
|
|
||||||
Function(String?) onChange;
|
|
||||||
TabNavigationObserver({required this.onChange});
|
|
||||||
|
|
||||||
@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);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@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);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@RoutePage()
|
|
||||||
class TabsNavigationWidget extends HookConsumerWidget {
|
|
||||||
final Widget child;
|
|
||||||
final AppRouter router;
|
|
||||||
const TabsNavigationWidget({
|
|
||||||
super.key,
|
|
||||||
required this.child,
|
|
||||||
required this.router,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
|
||||||
final useHorizontalLayout = isWideScreen(context);
|
|
||||||
final currentRoute = ref.watch(currentRouteProvider);
|
|
||||||
|
|
||||||
final notificationUnreadCount = ref.watch(
|
|
||||||
notificationUnreadCountNotifierProvider,
|
|
||||||
);
|
|
||||||
|
|
||||||
int activeIndex = 0;
|
|
||||||
|
|
||||||
final destinations = [
|
|
||||||
NavigationDestination(
|
|
||||||
label: 'explore'.tr(),
|
|
||||||
icon: const Icon(Symbols.explore),
|
|
||||||
),
|
|
||||||
NavigationDestination(label: 'chat'.tr(), icon: const Icon(Symbols.chat)),
|
|
||||||
NavigationDestination(
|
|
||||||
label: 'realms'.tr(),
|
|
||||||
icon: const Icon(Symbols.workspaces),
|
|
||||||
),
|
|
||||||
NavigationDestination(
|
|
||||||
label: 'account'.tr(),
|
|
||||||
icon: Badge.count(
|
|
||||||
count: notificationUnreadCount.value ?? 0,
|
|
||||||
isLabelVisible: (notificationUnreadCount.value ?? 0) > 0,
|
|
||||||
child: const Icon(Symbols.account_circle),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
];
|
|
||||||
|
|
||||||
final routes = <PageRouteInfo>[
|
|
||||||
ExploreRoute(),
|
|
||||||
ChatListRoute(),
|
|
||||||
RealmListRoute(),
|
|
||||||
AccountRoute(),
|
|
||||||
];
|
|
||||||
final routeNames = [
|
|
||||||
ExploreRoute.name,
|
|
||||||
ExploreShellRoute.name,
|
|
||||||
ChatListRoute.name,
|
|
||||||
RealmListRoute.name,
|
|
||||||
AccountRoute.name,
|
|
||||||
ChatShellRoute.name,
|
|
||||||
AccountShellRoute.name,
|
|
||||||
];
|
|
||||||
|
|
||||||
activeIndex = routes.indexWhere((route) => route.routeName == currentRoute);
|
|
||||||
if (activeIndex == -1) {
|
|
||||||
activeIndex = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
final isTabRoute = routeNames.any((route) {
|
|
||||||
return route == currentRoute;
|
|
||||||
});
|
|
||||||
|
|
||||||
return Scaffold(
|
|
||||||
extendBodyBehindAppBar: true,
|
|
||||||
backgroundColor: Colors.transparent,
|
|
||||||
body:
|
|
||||||
useHorizontalLayout
|
|
||||||
? Row(
|
|
||||||
children: [
|
|
||||||
ColoredBox(
|
|
||||||
color: Theme.of(context).colorScheme.surface,
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
Gap(MediaQuery.of(context).padding.top + 8),
|
|
||||||
Expanded(
|
|
||||||
child: NavigationRail(
|
|
||||||
selectedIndex: activeIndex,
|
|
||||||
onDestinationSelected: (index) {
|
|
||||||
router.replace(routes[index]);
|
|
||||||
},
|
|
||||||
// labelType: NavigationRailLabelType.all,
|
|
||||||
destinations:
|
|
||||||
destinations
|
|
||||||
.map(
|
|
||||||
(d) => NavigationRailDestination(
|
|
||||||
icon: d.icon,
|
|
||||||
label: Text(d.label),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Gap(MediaQuery.of(context).padding.bottom + 8),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
VerticalDivider(
|
|
||||||
color: Theme.of(context).dividerColor,
|
|
||||||
width: 1 / MediaQuery.of(context).devicePixelRatio,
|
|
||||||
),
|
|
||||||
Expanded(child: child),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
: child,
|
|
||||||
bottomNavigationBar:
|
|
||||||
!useHorizontalLayout && isTabRoute
|
|
||||||
? NavigationBar(
|
|
||||||
height: 56,
|
|
||||||
labelBehavior: NavigationDestinationLabelBehavior.alwaysHide,
|
|
||||||
selectedIndex: activeIndex,
|
|
||||||
onDestinationSelected: (index) {
|
|
||||||
router.replace(routes[index]);
|
|
||||||
},
|
|
||||||
destinations: destinations,
|
|
||||||
)
|
|
||||||
: null,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -11,6 +11,7 @@ import 'package:island/widgets/chat/call_button.dart';
|
|||||||
import 'package:island/widgets/chat/call_overlay.dart';
|
import 'package:island/widgets/chat/call_overlay.dart';
|
||||||
import 'package:island/widgets/chat/call_participant_tile.dart';
|
import 'package:island/widgets/chat/call_participant_tile.dart';
|
||||||
import 'package:livekit_client/livekit_client.dart';
|
import 'package:livekit_client/livekit_client.dart';
|
||||||
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
|
||||||
@RoutePage()
|
@RoutePage()
|
||||||
@ -32,37 +33,9 @@ class CallScreen extends HookConsumerWidget {
|
|||||||
final viewMode = useState<String>('grid');
|
final viewMode = useState<String>('grid');
|
||||||
|
|
||||||
return AppScaffold(
|
return AppScaffold(
|
||||||
|
noBackground: false,
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
leading: PageBackButton(
|
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(
|
title: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
@ -83,7 +56,7 @@ class CallScreen extends HookConsumerWidget {
|
|||||||
mainAxisAlignment: MainAxisAlignment.end,
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
children: [
|
children: [
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: Icon(Icons.grid_view),
|
icon: Icon(Symbols.grid_view),
|
||||||
tooltip: 'Grid View',
|
tooltip: 'Grid View',
|
||||||
onPressed: () => viewMode.value = 'grid',
|
onPressed: () => viewMode.value = 'grid',
|
||||||
color:
|
color:
|
||||||
@ -92,7 +65,7 @@ class CallScreen extends HookConsumerWidget {
|
|||||||
: null,
|
: null,
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: Icon(Icons.view_agenda),
|
icon: Icon(Symbols.view_agenda),
|
||||||
tooltip: 'Stage View',
|
tooltip: 'Stage View',
|
||||||
onPressed: () => viewMode.value = 'stage',
|
onPressed: () => viewMode.value = 'stage',
|
||||||
color:
|
color:
|
||||||
|
@ -24,8 +24,10 @@ import 'package:island/widgets/alert.dart';
|
|||||||
import 'package:island/widgets/app_scaffold.dart';
|
import 'package:island/widgets/app_scaffold.dart';
|
||||||
import 'package:island/widgets/chat/call_overlay.dart';
|
import 'package:island/widgets/chat/call_overlay.dart';
|
||||||
import 'package:island/widgets/content/cloud_files.dart';
|
import 'package:island/widgets/content/cloud_files.dart';
|
||||||
|
import 'package:island/widgets/content/sheet.dart';
|
||||||
import 'package:island/widgets/realms/selection_dropdown.dart';
|
import 'package:island/widgets/realms/selection_dropdown.dart';
|
||||||
import 'package:island/widgets/response.dart';
|
import 'package:island/widgets/response.dart';
|
||||||
|
import 'package:island/screens/tabs.dart';
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
import 'package:relative_time/relative_time.dart';
|
import 'package:relative_time/relative_time.dart';
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
@ -240,6 +242,7 @@ class ChatListScreen extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return AppScaffold(
|
return AppScaffold(
|
||||||
|
extendBody: false, // Prevent conflicts with tabs navigation
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text('chat').tr(),
|
title: Text('chat').tr(),
|
||||||
bottom: TabBar(
|
bottom: TabBar(
|
||||||
@ -338,6 +341,7 @@ class ChatListScreen extends HookConsumerWidget {
|
|||||||
},
|
},
|
||||||
child: const Icon(Symbols.add),
|
child: const Icon(Symbols.add),
|
||||||
),
|
),
|
||||||
|
floatingActionButtonLocation: TabbedFabLocation(context),
|
||||||
body: Stack(
|
body: Stack(
|
||||||
children: [
|
children: [
|
||||||
Column(
|
Column(
|
||||||
@ -346,7 +350,11 @@ class ChatListScreen extends HookConsumerWidget {
|
|||||||
builder: (context, ref, _) {
|
builder: (context, ref, _) {
|
||||||
final summaryState = ref.watch(chatSummaryProvider);
|
final summaryState = ref.watch(chatSummaryProvider);
|
||||||
return summaryState.maybeWhen(
|
return summaryState.maybeWhen(
|
||||||
loading: () => const LinearProgressIndicator(),
|
loading:
|
||||||
|
() => const LinearProgressIndicator(
|
||||||
|
minHeight: 2,
|
||||||
|
borderRadius: BorderRadius.zero,
|
||||||
|
),
|
||||||
orElse: () => const SizedBox.shrink(),
|
orElse: () => const SizedBox.shrink(),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -360,10 +368,10 @@ class ChatListScreen extends HookConsumerWidget {
|
|||||||
ref.invalidate(chatroomsJoinedProvider);
|
ref.invalidate(chatroomsJoinedProvider);
|
||||||
}),
|
}),
|
||||||
child: ListView.builder(
|
child: ListView.builder(
|
||||||
padding:
|
padding: getTabbedPadding(
|
||||||
callState.isConnected
|
context,
|
||||||
? EdgeInsets.only(bottom: 96)
|
bottom: callState.isConnected ? 96 : null,
|
||||||
: EdgeInsets.zero,
|
),
|
||||||
itemCount:
|
itemCount:
|
||||||
items
|
items
|
||||||
.where(
|
.where(
|
||||||
@ -423,7 +431,7 @@ class ChatListScreen extends HookConsumerWidget {
|
|||||||
Positioned(
|
Positioned(
|
||||||
left: 0,
|
left: 0,
|
||||||
right: 0,
|
right: 0,
|
||||||
bottom: 0,
|
bottom: getTabbedPadding(context).bottom + 8,
|
||||||
child: const CallOverlayBar().padding(horizontal: 16, vertical: 12),
|
child: const CallOverlayBar().padding(horizontal: 16, vertical: 12),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -714,109 +722,77 @@ class _ChatInvitesSheet extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Container(
|
return SheetScaffold(
|
||||||
constraints: BoxConstraints(
|
titleText: 'invites'.tr(),
|
||||||
maxHeight: MediaQuery.of(context).size.height * 0.8,
|
actions: [
|
||||||
),
|
IconButton(
|
||||||
child: Column(
|
icon: const Icon(Symbols.refresh),
|
||||||
mainAxisSize: MainAxisSize.min,
|
style: IconButton.styleFrom(minimumSize: const Size(36, 36)),
|
||||||
children: [
|
onPressed: () {
|
||||||
Padding(
|
ref.invalidate(realmInvitesProvider);
|
||||||
padding: EdgeInsets.only(top: 16, left: 20, right: 16, bottom: 12),
|
},
|
||||||
child: Row(
|
),
|
||||||
children: [
|
],
|
||||||
Text(
|
child: invites.when(
|
||||||
'invites'.tr(),
|
data:
|
||||||
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
|
(items) =>
|
||||||
fontWeight: FontWeight.w600,
|
items.isEmpty
|
||||||
letterSpacing: -0.5,
|
? Center(
|
||||||
),
|
child:
|
||||||
),
|
Text(
|
||||||
const Spacer(),
|
'invitesEmpty',
|
||||||
IconButton(
|
textAlign: TextAlign.center,
|
||||||
icon: const Icon(Symbols.refresh),
|
).tr(),
|
||||||
style: IconButton.styleFrom(minimumSize: const Size(36, 36)),
|
)
|
||||||
onPressed: () {
|
: ListView.builder(
|
||||||
ref.invalidate(chatroomInvitesProvider);
|
shrinkWrap: true,
|
||||||
},
|
itemCount: items.length,
|
||||||
),
|
itemBuilder: (context, index) {
|
||||||
IconButton(
|
final invite = items[index];
|
||||||
icon: const Icon(Symbols.close),
|
return ChatRoomListTile(
|
||||||
onPressed: () => Navigator.pop(context),
|
room: invite.chatRoom!,
|
||||||
style: IconButton.styleFrom(minimumSize: const Size(36, 36)),
|
isDirect: invite.chatRoom!.type == 1,
|
||||||
),
|
subtitle: Row(
|
||||||
],
|
spacing: 6,
|
||||||
),
|
children: [
|
||||||
),
|
Flexible(
|
||||||
const Divider(height: 1),
|
child:
|
||||||
Expanded(
|
Text(
|
||||||
child: invites.when(
|
invite.role >= 100
|
||||||
data:
|
? 'permissionOwner'
|
||||||
(items) =>
|
: invite.role >= 50
|
||||||
items.isEmpty
|
? 'permissionModerator'
|
||||||
? Center(
|
: 'permissionMember',
|
||||||
child:
|
).tr(),
|
||||||
Text(
|
),
|
||||||
'invitesEmpty',
|
if (invite.chatRoom!.type == 1)
|
||||||
textAlign: TextAlign.center,
|
Badge(
|
||||||
).tr(),
|
label: Text('directMessage').tr(),
|
||||||
)
|
backgroundColor:
|
||||||
: ListView.builder(
|
Theme.of(context).colorScheme.primary,
|
||||||
shrinkWrap: true,
|
textColor:
|
||||||
itemCount: items.length,
|
Theme.of(context).colorScheme.onPrimary,
|
||||||
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()),
|
trailing: Row(
|
||||||
error: (error, stack) => Center(child: Text('Error: $error')),
|
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')),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
import 'package:auto_route/auto_route.dart';
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
@ -319,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;
|
var isLoading = false;
|
||||||
|
|
||||||
// Add scroll listener for pagination
|
// Add scroll listener for pagination
|
||||||
@ -341,6 +383,31 @@ class ChatRoomScreen extends HookConsumerWidget {
|
|||||||
void onMessage(WebSocketPacket pkt) {
|
void onMessage(WebSocketPacket pkt) {
|
||||||
if (!pkt.type.startsWith('messages')) return;
|
if (!pkt.type.startsWith('messages')) return;
|
||||||
if (['messages.read'].contains(pkt.type)) 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!);
|
final message = SnChatMessage.fromJson(pkt.data!);
|
||||||
if (message.chatRoomId != chatRoom.value?.id) return;
|
if (message.chatRoomId != chatRoom.value?.id) return;
|
||||||
switch (pkt.type) {
|
switch (pkt.type) {
|
||||||
@ -414,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 compactHeader = isWideScreen(context);
|
||||||
|
|
||||||
|
final listController = useMemoized(() => ListController(), []);
|
||||||
|
|
||||||
return AppScaffold(
|
return AppScaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
leading: !compactHeader ? const Center(child: PageBackButton()) : null,
|
leading: !compactHeader ? const Center(child: PageBackButton()) : null,
|
||||||
@ -541,11 +622,19 @@ class ChatRoomScreen extends HookConsumerWidget {
|
|||||||
messageList.isEmpty
|
messageList.isEmpty
|
||||||
? Center(child: Text('No messages yet'.tr()))
|
? Center(child: Text('No messages yet'.tr()))
|
||||||
: SuperListView.builder(
|
: SuperListView.builder(
|
||||||
|
listController: listController,
|
||||||
padding: EdgeInsets.symmetric(vertical: 16),
|
padding: EdgeInsets.symmetric(vertical: 16),
|
||||||
controller: scrollController,
|
controller: scrollController,
|
||||||
reverse:
|
reverse:
|
||||||
true, // Show newest messages at the bottom
|
true, // Show newest messages at the bottom
|
||||||
itemCount: messageList.length,
|
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) {
|
itemBuilder: (context, index) {
|
||||||
final message = messageList[index];
|
final message = messageList[index];
|
||||||
final nextMessage =
|
final nextMessage =
|
||||||
@ -602,6 +691,18 @@ class ChatRoomScreen extends HookConsumerWidget {
|
|||||||
message.toRemoteMessage();
|
message.toRemoteMessage();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
onJump: (messageId) {
|
||||||
|
final messageIndex = messageList
|
||||||
|
.indexWhere(
|
||||||
|
(m) => m.id == messageId,
|
||||||
|
);
|
||||||
|
listController.jumpToItem(
|
||||||
|
index: messageIndex,
|
||||||
|
scrollController:
|
||||||
|
scrollController,
|
||||||
|
alignment: 0.5,
|
||||||
|
);
|
||||||
|
},
|
||||||
progress:
|
progress:
|
||||||
attachmentProgress.value[message
|
attachmentProgress.value[message
|
||||||
.id],
|
.id],
|
||||||
@ -614,6 +715,7 @@ class ChatRoomScreen extends HookConsumerWidget {
|
|||||||
onAction: null,
|
onAction: null,
|
||||||
progress: null,
|
progress: null,
|
||||||
showAvatar: false,
|
showAvatar: false,
|
||||||
|
onJump: (_) {},
|
||||||
),
|
),
|
||||||
error: (_, _) => const SizedBox.shrink(),
|
error: (_, _) => const SizedBox.shrink(),
|
||||||
);
|
);
|
||||||
@ -630,55 +732,133 @@ class ChatRoomScreen extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
chatRoom.when(
|
chatRoom.when(
|
||||||
data:
|
data:
|
||||||
(room) => _ChatInput(
|
(room) => Column(
|
||||||
messageController: messageController,
|
mainAxisSize: MainAxisSize.min,
|
||||||
chatRoom: room!,
|
children: [
|
||||||
onSend: sendMessage,
|
AnimatedSwitcher(
|
||||||
onClear: () {
|
duration: const Duration(milliseconds: 150),
|
||||||
if (messageEditingTo.value != null) {
|
switchInCurve: Curves.fastEaseInToSlowEaseOut,
|
||||||
attachments.value.clear();
|
switchOutCurve: Curves.fastEaseInToSlowEaseOut,
|
||||||
messageController.clear();
|
transitionBuilder: (
|
||||||
}
|
Widget child,
|
||||||
messageEditingTo.value = null;
|
Animation<double> animation,
|
||||||
messageReplyingTo.value = null;
|
) {
|
||||||
messageForwardingTo.value = null;
|
return SlideTransition(
|
||||||
},
|
position: Tween<Offset>(
|
||||||
messageEditingTo: messageEditingTo.value,
|
begin: const Offset(0, -0.3),
|
||||||
messageReplyingTo: messageReplyingTo.value,
|
end: Offset.zero,
|
||||||
messageForwardingTo: messageForwardingTo.value,
|
).animate(
|
||||||
onPickFile: (bool isPhoto) {
|
CurvedAnimation(
|
||||||
if (isPhoto) {
|
parent: animation,
|
||||||
pickPhotoMedia();
|
curve: Curves.easeOutCubic,
|
||||||
} else {
|
),
|
||||||
pickVideoMedia();
|
),
|
||||||
}
|
child: SizeTransition(
|
||||||
},
|
sizeFactor: animation,
|
||||||
attachments: attachments.value,
|
axisAlignment: -1.0,
|
||||||
onUploadAttachment: (_) {
|
child: FadeTransition(
|
||||||
// not going to do anything, only upload when send the message
|
opacity: animation,
|
||||||
},
|
child: child,
|
||||||
onDeleteAttachment: (index) async {
|
),
|
||||||
final attachment = attachments.value[index];
|
),
|
||||||
if (attachment.isOnCloud) {
|
);
|
||||||
final client = ref.watch(apiClientProvider);
|
},
|
||||||
await client.delete('/files/${attachment.data.id}');
|
child:
|
||||||
}
|
typingStatuses.value.isNotEmpty
|
||||||
final clone = List.of(attachments.value);
|
? Container(
|
||||||
clone.removeAt(index);
|
key: const ValueKey('typing-indicator'),
|
||||||
attachments.value = clone;
|
width: double.infinity,
|
||||||
},
|
padding: const EdgeInsets.symmetric(
|
||||||
onMoveAttachment: (idx, delta) {
|
horizontal: 16,
|
||||||
if (idx + delta < 0 ||
|
vertical: 4,
|
||||||
idx + delta >= attachments.value.length) {
|
),
|
||||||
return;
|
child: Row(
|
||||||
}
|
children: [
|
||||||
final clone = List.of(attachments.value);
|
const Icon(
|
||||||
clone.insert(idx + delta, clone.removeAt(idx));
|
Symbols.more_horiz,
|
||||||
attachments.value = clone;
|
size: 16,
|
||||||
},
|
).padding(horizontal: 8),
|
||||||
onAttachmentsChanged: (newAttachments) {
|
const Gap(8),
|
||||||
attachments.value = newAttachments;
|
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(),
|
error: (_, _) => const SizedBox.shrink(),
|
||||||
loading: () => const SizedBox.shrink(),
|
loading: () => const SizedBox.shrink(),
|
||||||
@ -697,7 +877,7 @@ class ChatRoomScreen extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ChatInput extends ConsumerWidget {
|
class _ChatInput extends HookConsumerWidget {
|
||||||
final TextEditingController messageController;
|
final TextEditingController messageController;
|
||||||
final SnChatRoom chatRoom;
|
final SnChatRoom chatRoom;
|
||||||
final VoidCallback onSend;
|
final VoidCallback onSend;
|
||||||
@ -728,46 +908,59 @@ class _ChatInput extends ConsumerWidget {
|
|||||||
required this.onAttachmentsChanged,
|
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(appSettingsNotifierProvider).enterToSend;
|
|
||||||
final isEnter = event.logicalKey == LogicalKeyboardKey.enter;
|
|
||||||
|
|
||||||
if (isEnter) {
|
|
||||||
if (enterToSend && !isModifierPressed) {
|
|
||||||
onSend();
|
|
||||||
} else if (!enterToSend && isModifierPressed) {
|
|
||||||
onSend();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final inputFocusNode = useFocusNode();
|
||||||
|
|
||||||
final enterToSend = ref.watch(appSettingsNotifierProvider).enterToSend;
|
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(
|
return Material(
|
||||||
elevation: 8,
|
elevation: 8,
|
||||||
color: Theme.of(context).colorScheme.surface,
|
color: Theme.of(context).colorScheme.surface,
|
||||||
@ -869,12 +1062,23 @@ class _ChatInput extends ConsumerWidget {
|
|||||||
Expanded(
|
Expanded(
|
||||||
child: RawKeyboardListener(
|
child: RawKeyboardListener(
|
||||||
focusNode: FocusNode(),
|
focusNode: FocusNode(),
|
||||||
onKey: (event) => _handleKeyPress(context, ref, event),
|
onKey: (event) => handleKeyPress(context, ref, event),
|
||||||
child: TextField(
|
child: TextField(
|
||||||
|
focusNode: inputFocusNode,
|
||||||
controller: messageController,
|
controller: messageController,
|
||||||
onSubmitted: enterToSend ? (_) => onSend() : null,
|
onSubmitted:
|
||||||
|
(enterToSend && isMobile)
|
||||||
|
? (_) {
|
||||||
|
send();
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
keyboardType:
|
||||||
|
(enterToSend && isMobile)
|
||||||
|
? TextInputType.text
|
||||||
|
: TextInputType.multiline,
|
||||||
|
textInputAction: TextInputAction.send,
|
||||||
inputFormatters: [
|
inputFormatters: [
|
||||||
if (enterToSend)
|
if (enterToSend && !isMobile)
|
||||||
TextInputFormatter.withFunction((oldValue, newValue) {
|
TextInputFormatter.withFunction((oldValue, newValue) {
|
||||||
if (newValue.text.endsWith('\n')) {
|
if (newValue.text.endsWith('\n')) {
|
||||||
return oldValue;
|
return oldValue;
|
||||||
@ -909,7 +1113,7 @@ class _ChatInput extends ConsumerWidget {
|
|||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.send),
|
icon: const Icon(Icons.send),
|
||||||
color: Theme.of(context).colorScheme.primary,
|
color: Theme.of(context).colorScheme.primary,
|
||||||
onPressed: onSend,
|
onPressed: send,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
).padding(bottom: MediaQuery.of(context).padding.bottom),
|
).padding(bottom: MediaQuery.of(context).padding.bottom),
|
||||||
|
@ -14,10 +14,14 @@ import 'package:island/widgets/account/account_picker.dart';
|
|||||||
import 'package:island/widgets/alert.dart';
|
import 'package:island/widgets/alert.dart';
|
||||||
import 'package:island/widgets/app_scaffold.dart';
|
import 'package:island/widgets/app_scaffold.dart';
|
||||||
import 'package:island/widgets/content/cloud_files.dart';
|
import 'package:island/widgets/content/cloud_files.dart';
|
||||||
|
import 'package:island/widgets/content/sheet.dart';
|
||||||
import 'package:material_symbols_icons/symbols.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';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
|
||||||
part 'room_detail.freezed.dart';
|
part 'room_detail.freezed.dart';
|
||||||
|
part 'room_detail.g.dart';
|
||||||
|
|
||||||
@RoutePage()
|
@RoutePage()
|
||||||
class ChatDetailScreen extends HookConsumerWidget {
|
class ChatDetailScreen extends HookConsumerWidget {
|
||||||
@ -27,6 +31,206 @@ class ChatDetailScreen extends HookConsumerWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final roomState = ref.watch(chatroomProvider(id));
|
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(
|
const iconShadow = Shadow(
|
||||||
color: Colors.black54,
|
color: Colors.black54,
|
||||||
@ -110,17 +314,59 @@ class ChatDetailScreen extends HookConsumerWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
child: Padding(
|
child: Column(
|
||||||
padding: const EdgeInsets.all(16.0),
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
child: Column(
|
children: [
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
Text(
|
||||||
children: [
|
currentRoom.description ?? 'descriptionNone'.tr(),
|
||||||
Text(
|
style: const TextStyle(fontSize: 16),
|
||||||
currentRoom.description ?? 'descriptionNone'.tr(),
|
).padding(all: 24),
|
||||||
style: const TextStyle(fontSize: 16),
|
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(),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -287,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 {
|
class _ChatMemberListSheet extends HookConsumerWidget {
|
||||||
final String roomId;
|
final String roomId;
|
||||||
const _ChatMemberListSheet({required this.roomId});
|
const _ChatMemberListSheet({required this.roomId});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
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 memberState = ref.watch(chatMemberStateProvider(roomId));
|
||||||
final memberNotifier = ref.read(chatMemberStateProvider(roomId).notifier);
|
final memberNotifier = ref.read(chatMemberStateProvider(roomId).notifier);
|
||||||
|
|
||||||
@ -318,8 +603,10 @@ class _ChatMemberListSheet extends HookConsumerWidget {
|
|||||||
'/chat/invites/$roomId',
|
'/chat/invites/$roomId',
|
||||||
data: {'related_user_id': result.id, 'role': 0},
|
data: {'related_user_id': result.id, 'role': 0},
|
||||||
);
|
);
|
||||||
|
// Refresh both providers
|
||||||
memberNotifier.reset();
|
memberNotifier.reset();
|
||||||
await memberNotifier.loadMore();
|
await memberNotifier.loadMore();
|
||||||
|
ref.invalidate(memberListProvider);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
showErrorAlert(err);
|
showErrorAlert(err);
|
||||||
}
|
}
|
||||||
@ -351,8 +638,10 @@ class _ChatMemberListSheet extends HookConsumerWidget {
|
|||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Symbols.refresh),
|
icon: const Icon(Symbols.refresh),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
|
// Refresh both providers
|
||||||
memberNotifier.reset();
|
memberNotifier.reset();
|
||||||
memberNotifier.loadMore();
|
memberNotifier.loadMore();
|
||||||
|
ref.invalidate(memberListProvider);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
@ -365,108 +654,103 @@ class _ChatMemberListSheet extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
const Divider(height: 1),
|
const Divider(height: 1),
|
||||||
Expanded(
|
Expanded(
|
||||||
child:
|
child: PagingHelperView(
|
||||||
memberState.error != null
|
provider: memberListProvider,
|
||||||
? Center(child: Text(memberState.error!))
|
futureRefreshable: memberListProvider.future,
|
||||||
: ListView.builder(
|
notifierRefreshable: memberListProvider.notifier,
|
||||||
itemCount: memberState.members.length + 1,
|
contentBuilder: (data, widgetCount, endItemView) {
|
||||||
itemBuilder: (context, index) {
|
return ListView.builder(
|
||||||
if (index == memberState.members.length) {
|
itemCount: widgetCount,
|
||||||
if (memberState.isLoading) {
|
itemBuilder: (context, index) {
|
||||||
return const Center(
|
if (index == data.items.length) {
|
||||||
child: Padding(
|
return endItemView;
|
||||||
padding: EdgeInsets.all(16.0),
|
}
|
||||||
child: CircularProgressIndicator(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (memberState.members.length < memberState.total) {
|
|
||||||
memberNotifier.loadMore(
|
|
||||||
offset: memberState.members.length,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return const SizedBox.shrink();
|
|
||||||
}
|
|
||||||
|
|
||||||
final member = memberState.members[index];
|
final member = data.items[index];
|
||||||
return ListTile(
|
return ListTile(
|
||||||
contentPadding: EdgeInsets.only(left: 16, right: 12),
|
contentPadding: EdgeInsets.only(left: 16, right: 12),
|
||||||
leading: ProfilePictureWidget(
|
leading: ProfilePictureWidget(
|
||||||
fileId: member.account.profile.picture?.id,
|
fileId: member.account.profile.picture?.id,
|
||||||
),
|
),
|
||||||
title: Row(
|
title: Row(
|
||||||
spacing: 6,
|
spacing: 6,
|
||||||
children: [
|
children: [
|
||||||
Flexible(child: Text(member.account.nick)),
|
Flexible(child: Text(member.account.nick)),
|
||||||
if (member.joinedAt == null)
|
if (member.joinedAt == null)
|
||||||
const Icon(Symbols.pending_actions, size: 20),
|
const Icon(Symbols.pending_actions, size: 20),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
subtitle: Row(
|
subtitle: Row(
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
member.role >= 100
|
member.role >= 100
|
||||||
? 'permissionOwner'
|
? 'permissionOwner'
|
||||||
: member.role >= 50
|
: member.role >= 50
|
||||||
? 'permissionModerator'
|
? 'permissionModerator'
|
||||||
: 'permissionMember',
|
: 'permissionMember',
|
||||||
).tr(),
|
).tr(),
|
||||||
Text('·').bold().padding(horizontal: 6),
|
Text('·').bold().padding(horizontal: 6),
|
||||||
Expanded(child: Text("@${member.account.name}")),
|
Expanded(child: Text("@${member.account.name}")),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
trailing: Row(
|
trailing: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
if ((roomIdentity.value?.role ?? 0) >= 50)
|
if ((roomIdentity.value?.role ?? 0) >= 50)
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Symbols.edit),
|
icon: const Icon(Symbols.edit),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
isScrollControlled: true,
|
isScrollControlled: true,
|
||||||
context: context,
|
context: context,
|
||||||
builder:
|
builder:
|
||||||
(context) => _ChatMemberRoleSheet(
|
(context) => _ChatMemberRoleSheet(
|
||||||
roomId: roomId,
|
roomId: roomId,
|
||||||
member: member,
|
member: member,
|
||||||
),
|
),
|
||||||
).then((value) {
|
).then((value) {
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
memberNotifier.reset();
|
// Refresh both providers
|
||||||
memberNotifier.loadMore();
|
memberNotifier.reset();
|
||||||
}
|
memberNotifier.loadMore();
|
||||||
});
|
ref.invalidate(memberListProvider);
|
||||||
},
|
}
|
||||||
),
|
});
|
||||||
if ((roomIdentity.value?.role ?? 0) >= 50)
|
},
|
||||||
IconButton(
|
),
|
||||||
icon: const Icon(Symbols.delete),
|
if ((roomIdentity.value?.role ?? 0) >= 50)
|
||||||
onPressed: () {
|
IconButton(
|
||||||
showConfirmAlert(
|
icon: const Icon(Symbols.delete),
|
||||||
'removeChatMemberHint'.tr(),
|
onPressed: () {
|
||||||
'removeChatMember'.tr(),
|
showConfirmAlert(
|
||||||
).then((confirm) async {
|
'removeChatMemberHint'.tr(),
|
||||||
if (confirm != true) return;
|
'removeChatMember'.tr(),
|
||||||
try {
|
).then((confirm) async {
|
||||||
final apiClient = ref.watch(
|
if (confirm != true) return;
|
||||||
apiClientProvider,
|
try {
|
||||||
);
|
final apiClient = ref.watch(
|
||||||
await apiClient.delete(
|
apiClientProvider,
|
||||||
'/chat/$roomId/members/${member.accountId}',
|
);
|
||||||
);
|
await apiClient.delete(
|
||||||
memberNotifier.reset();
|
'/chat/$roomId/members/${member.accountId}',
|
||||||
memberNotifier.loadMore();
|
);
|
||||||
} catch (err) {
|
// Refresh both providers
|
||||||
showErrorAlert(err);
|
memberNotifier.reset();
|
||||||
}
|
memberNotifier.loadMore();
|
||||||
});
|
ref.invalidate(memberListProvider);
|
||||||
},
|
} catch (err) {
|
||||||
),
|
showErrorAlert(err);
|
||||||
],
|
}
|
||||||
),
|
});
|
||||||
);
|
},
|
||||||
},
|
),
|
||||||
),
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
180
lib/screens/chat/room_detail.g.dart
Normal file
180
lib/screens/chat/room_detail.g.dart
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'room_detail.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// RiverpodGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
String _$chatMemberListNotifierHash() =>
|
||||||
|
r'f2191a631ba00ae3de39ccac10e4cdd065ffee17';
|
||||||
|
|
||||||
|
/// Copied from Dart SDK
|
||||||
|
class _SystemHash {
|
||||||
|
_SystemHash._();
|
||||||
|
|
||||||
|
static int combine(int hash, int value) {
|
||||||
|
// ignore: parameter_assignments
|
||||||
|
hash = 0x1fffffff & (hash + value);
|
||||||
|
// ignore: parameter_assignments
|
||||||
|
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
|
||||||
|
return hash ^ (hash >> 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int finish(int hash) {
|
||||||
|
// ignore: parameter_assignments
|
||||||
|
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
|
||||||
|
// ignore: parameter_assignments
|
||||||
|
hash = hash ^ (hash >> 11);
|
||||||
|
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class _$ChatMemberListNotifier
|
||||||
|
extends BuildlessAutoDisposeAsyncNotifier<CursorPagingData<SnChatMember>> {
|
||||||
|
late final String roomId;
|
||||||
|
|
||||||
|
FutureOr<CursorPagingData<SnChatMember>> build(String roomId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See also [ChatMemberListNotifier].
|
||||||
|
@ProviderFor(ChatMemberListNotifier)
|
||||||
|
const chatMemberListNotifierProvider = ChatMemberListNotifierFamily();
|
||||||
|
|
||||||
|
/// See also [ChatMemberListNotifier].
|
||||||
|
class ChatMemberListNotifierFamily
|
||||||
|
extends Family<AsyncValue<CursorPagingData<SnChatMember>>> {
|
||||||
|
/// See also [ChatMemberListNotifier].
|
||||||
|
const ChatMemberListNotifierFamily();
|
||||||
|
|
||||||
|
/// See also [ChatMemberListNotifier].
|
||||||
|
ChatMemberListNotifierProvider call(String roomId) {
|
||||||
|
return ChatMemberListNotifierProvider(roomId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
ChatMemberListNotifierProvider getProviderOverride(
|
||||||
|
covariant ChatMemberListNotifierProvider provider,
|
||||||
|
) {
|
||||||
|
return call(provider.roomId);
|
||||||
|
}
|
||||||
|
|
||||||
|
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'chatMemberListNotifierProvider';
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See also [ChatMemberListNotifier].
|
||||||
|
class ChatMemberListNotifierProvider
|
||||||
|
extends
|
||||||
|
AutoDisposeAsyncNotifierProviderImpl<
|
||||||
|
ChatMemberListNotifier,
|
||||||
|
CursorPagingData<SnChatMember>
|
||||||
|
> {
|
||||||
|
/// See also [ChatMemberListNotifier].
|
||||||
|
ChatMemberListNotifierProvider(String roomId)
|
||||||
|
: this._internal(
|
||||||
|
() => ChatMemberListNotifier()..roomId = roomId,
|
||||||
|
from: chatMemberListNotifierProvider,
|
||||||
|
name: r'chatMemberListNotifierProvider',
|
||||||
|
debugGetCreateSourceHash:
|
||||||
|
const bool.fromEnvironment('dart.vm.product')
|
||||||
|
? null
|
||||||
|
: _$chatMemberListNotifierHash,
|
||||||
|
dependencies: ChatMemberListNotifierFamily._dependencies,
|
||||||
|
allTransitiveDependencies:
|
||||||
|
ChatMemberListNotifierFamily._allTransitiveDependencies,
|
||||||
|
roomId: roomId,
|
||||||
|
);
|
||||||
|
|
||||||
|
ChatMemberListNotifierProvider._internal(
|
||||||
|
super._createNotifier, {
|
||||||
|
required super.name,
|
||||||
|
required super.dependencies,
|
||||||
|
required super.allTransitiveDependencies,
|
||||||
|
required super.debugGetCreateSourceHash,
|
||||||
|
required super.from,
|
||||||
|
required this.roomId,
|
||||||
|
}) : super.internal();
|
||||||
|
|
||||||
|
final String roomId;
|
||||||
|
|
||||||
|
@override
|
||||||
|
FutureOr<CursorPagingData<SnChatMember>> runNotifierBuild(
|
||||||
|
covariant ChatMemberListNotifier notifier,
|
||||||
|
) {
|
||||||
|
return notifier.build(roomId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Override overrideWith(ChatMemberListNotifier Function() create) {
|
||||||
|
return ProviderOverride(
|
||||||
|
origin: this,
|
||||||
|
override: ChatMemberListNotifierProvider._internal(
|
||||||
|
() => create()..roomId = roomId,
|
||||||
|
from: from,
|
||||||
|
name: null,
|
||||||
|
dependencies: null,
|
||||||
|
allTransitiveDependencies: null,
|
||||||
|
debugGetCreateSourceHash: null,
|
||||||
|
roomId: roomId,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
AutoDisposeAsyncNotifierProviderElement<
|
||||||
|
ChatMemberListNotifier,
|
||||||
|
CursorPagingData<SnChatMember>
|
||||||
|
>
|
||||||
|
createElement() {
|
||||||
|
return _ChatMemberListNotifierProviderElement(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return other is ChatMemberListNotifierProvider && other.roomId == roomId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode {
|
||||||
|
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||||
|
hash = _SystemHash.combine(hash, roomId.hashCode);
|
||||||
|
|
||||||
|
return _SystemHash.finish(hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||||
|
// ignore: unused_element
|
||||||
|
mixin ChatMemberListNotifierRef
|
||||||
|
on AutoDisposeAsyncNotifierProviderRef<CursorPagingData<SnChatMember>> {
|
||||||
|
/// The parameter `roomId` of this provider.
|
||||||
|
String get roomId;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ChatMemberListNotifierProviderElement
|
||||||
|
extends
|
||||||
|
AutoDisposeAsyncNotifierProviderElement<
|
||||||
|
ChatMemberListNotifier,
|
||||||
|
CursorPagingData<SnChatMember>
|
||||||
|
>
|
||||||
|
with ChatMemberListNotifierRef {
|
||||||
|
_ChatMemberListNotifierProviderElement(super.provider);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get roomId => (origin as ChatMemberListNotifierProvider).roomId;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
@ -263,6 +263,13 @@ class CreatorHubScreen extends HookConsumerWidget {
|
|||||||
contentPadding: EdgeInsets.symmetric(
|
contentPadding: EdgeInsets.symmetric(
|
||||||
horizontal: 24,
|
horizontal: 24,
|
||||||
),
|
),
|
||||||
|
onTap: () {
|
||||||
|
context.router.push(
|
||||||
|
CreatorPostListRoute(
|
||||||
|
pubName: currentPublisher.value!.name,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
Divider(height: 1).padding(vertical: 8),
|
Divider(height: 1).padding(vertical: 8),
|
||||||
ListTile(
|
ListTile(
|
||||||
|
79
lib/screens/creators/posts/list.dart
Normal file
79
lib/screens/creators/posts/list.dart
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
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:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:island/widgets/app_scaffold.dart';
|
||||||
|
import 'package:island/widgets/content/sheet.dart';
|
||||||
|
import 'package:island/widgets/post/post_list.dart';
|
||||||
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
|
|
||||||
|
@RoutePage()
|
||||||
|
class CreatorPostListScreen extends HookConsumerWidget {
|
||||||
|
final String pubName;
|
||||||
|
const CreatorPostListScreen({
|
||||||
|
super.key,
|
||||||
|
@PathParam('name') required this.pubName,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final refreshKey = useState(0);
|
||||||
|
|
||||||
|
void showCreatePostSheet() {
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
builder:
|
||||||
|
(context) => SheetScaffold(
|
||||||
|
titleText: 'create'.tr(),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Symbols.edit),
|
||||||
|
title: Text('postContent'.tr()),
|
||||||
|
subtitle: Text('Create a regular post'),
|
||||||
|
onTap: () async {
|
||||||
|
Navigator.pop(context);
|
||||||
|
final result = await context.router.pushPath(
|
||||||
|
'/posts/compose?type=0',
|
||||||
|
);
|
||||||
|
if (result == true) {
|
||||||
|
refreshKey.value++;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Symbols.article),
|
||||||
|
title: Text('Article'),
|
||||||
|
subtitle: Text('Create a detailed article'),
|
||||||
|
onTap: () async {
|
||||||
|
Navigator.pop(context);
|
||||||
|
final result = await context.router.pushPath(
|
||||||
|
'/posts/compose?type=1',
|
||||||
|
);
|
||||||
|
if (result == true) {
|
||||||
|
refreshKey.value++;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return AppScaffold(
|
||||||
|
appBar: AppBar(title: Text('posts').tr()),
|
||||||
|
body: CustomScrollView(
|
||||||
|
key: ValueKey(refreshKey.value),
|
||||||
|
slivers: [
|
||||||
|
SliverPostList(pubName: pubName, itemType: PostItemType.creator),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
floatingActionButton: FloatingActionButton(
|
||||||
|
onPressed: showCreatePostSheet,
|
||||||
|
child: const Icon(Symbols.add),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -148,7 +148,7 @@ class _StickerPackProviderElement
|
|||||||
}
|
}
|
||||||
|
|
||||||
String _$stickerPacksNotifierHash() =>
|
String _$stickerPacksNotifierHash() =>
|
||||||
r'2feff50a7896eb8759fe91e9626b0409354d9fee';
|
r'dc0cc4ec27fdd6d5da28f982ff10c852f8107a18';
|
||||||
|
|
||||||
abstract class _$StickerPacksNotifier
|
abstract class _$StickerPacksNotifier
|
||||||
extends BuildlessAutoDisposeAsyncNotifier<CursorPagingData<SnStickerPack>> {
|
extends BuildlessAutoDisposeAsyncNotifier<CursorPagingData<SnStickerPack>> {
|
||||||
|
@ -1,18 +1,19 @@
|
|||||||
import 'package:auto_route/auto_route.dart';
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:island/models/activity.dart';
|
import 'package:island/models/activity.dart';
|
||||||
import 'package:island/pods/userinfo.dart';
|
import 'package:island/pods/userinfo.dart';
|
||||||
import 'package:island/route.gr.dart';
|
import 'package:island/route.gr.dart';
|
||||||
import 'package:island/services/responsive.dart';
|
import 'package:island/services/responsive.dart';
|
||||||
import 'package:island/widgets/account/status.dart';
|
|
||||||
import 'package:island/widgets/app_scaffold.dart';
|
import 'package:island/widgets/app_scaffold.dart';
|
||||||
import 'package:island/models/post.dart';
|
import 'package:island/models/post.dart';
|
||||||
import 'package:island/widgets/check_in.dart';
|
import 'package:island/widgets/check_in.dart';
|
||||||
import 'package:island/widgets/post/post_item.dart';
|
import 'package:island/widgets/post/post_item.dart';
|
||||||
import 'package:island/widgets/tour/tour.dart';
|
import 'package:island/widgets/tour/tour.dart';
|
||||||
|
import 'package:island/screens/tabs.dart';
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
|
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
|
||||||
@ -47,7 +48,7 @@ class ExploreShellScreen extends ConsumerWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@RoutePage()
|
@RoutePage()
|
||||||
class ExploreScreen extends ConsumerWidget {
|
class ExploreScreen extends HookConsumerWidget {
|
||||||
final bool isAside;
|
final bool isAside;
|
||||||
const ExploreScreen({super.key, this.isAside = false});
|
const ExploreScreen({super.key, this.isAside = false});
|
||||||
|
|
||||||
@ -58,11 +59,67 @@ class ExploreScreen extends ConsumerWidget {
|
|||||||
return const EmptyPageHolder();
|
return const EmptyPageHolder();
|
||||||
}
|
}
|
||||||
|
|
||||||
final activitiesNotifier = ref.watch(activityListNotifierProvider.notifier);
|
final tabController = useTabController(initialLength: 3);
|
||||||
|
final currentFilter = useState<String?>(null);
|
||||||
|
|
||||||
|
useEffect(() {
|
||||||
|
void listener() {
|
||||||
|
switch (tabController.index) {
|
||||||
|
case 0:
|
||||||
|
currentFilter.value = null;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
currentFilter.value = 'subscriptions';
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
currentFilter.value = 'friends';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tabController.addListener(listener);
|
||||||
|
return () => tabController.removeListener(listener);
|
||||||
|
}, [tabController]);
|
||||||
|
|
||||||
|
final activitiesNotifier = ref.watch(
|
||||||
|
activityListNotifierProvider(currentFilter.value).notifier,
|
||||||
|
);
|
||||||
|
|
||||||
return TourTriggerWidget(
|
return TourTriggerWidget(
|
||||||
child: AppScaffold(
|
child: AppScaffold(
|
||||||
appBar: AppBar(title: const Text('explore').tr()),
|
extendBody: false, // Prevent conflicts with tabs navigation
|
||||||
|
appBar: AppBar(
|
||||||
|
toolbarHeight: 0,
|
||||||
|
bottom: TabBar(
|
||||||
|
controller: tabController,
|
||||||
|
tabs: [
|
||||||
|
Tab(
|
||||||
|
child: Text(
|
||||||
|
'explore'.tr(),
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context).appBarTheme.foregroundColor!,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Tab(
|
||||||
|
child: Text(
|
||||||
|
'exploreFilterSubscriptions'.tr(),
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context).appBarTheme.foregroundColor!,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Tab(
|
||||||
|
child: Text(
|
||||||
|
'exploreFilterFriends'.tr(),
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context).appBarTheme.foregroundColor!,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
floatingActionButton: FloatingActionButton(
|
floatingActionButton: FloatingActionButton(
|
||||||
heroTag: Key("explore-page-fab"),
|
heroTag: Key("explore-page-fab"),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
@ -74,33 +131,50 @@ class ExploreScreen extends ConsumerWidget {
|
|||||||
},
|
},
|
||||||
child: const Icon(Symbols.edit),
|
child: const Icon(Symbols.edit),
|
||||||
),
|
),
|
||||||
floatingActionButtonLocation: FloatingActionButtonLocation.endFloat,
|
floatingActionButtonLocation: TabbedFabLocation(context),
|
||||||
body: RefreshIndicator(
|
body: TabBarView(
|
||||||
onRefresh: () => Future.sync(activitiesNotifier.forceRefresh),
|
controller: tabController,
|
||||||
child: PagingHelperView(
|
children: [
|
||||||
provider: activityListNotifierProvider,
|
_buildActivityList(ref, null),
|
||||||
futureRefreshable: activityListNotifierProvider.future,
|
_buildActivityList(ref, 'subscriptions'),
|
||||||
notifierRefreshable: activityListNotifierProvider.notifier,
|
_buildActivityList(ref, 'friends'),
|
||||||
contentBuilder:
|
],
|
||||||
(data, widgetCount, endItemView) => Center(
|
|
||||||
child: _ActivityListView(
|
|
||||||
data: data,
|
|
||||||
widgetCount: widgetCount,
|
|
||||||
endItemView: endItemView,
|
|
||||||
activitiesNotifier: activitiesNotifier,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildActivityList(WidgetRef ref, String? filter) {
|
||||||
|
final activitiesNotifier = ref.watch(
|
||||||
|
activityListNotifierProvider(filter).notifier,
|
||||||
|
);
|
||||||
|
|
||||||
|
return RefreshIndicator(
|
||||||
|
onRefresh: () => Future.sync(activitiesNotifier.forceRefresh),
|
||||||
|
child: PagingHelperView(
|
||||||
|
provider: activityListNotifierProvider(filter),
|
||||||
|
futureRefreshable: activityListNotifierProvider(filter).future,
|
||||||
|
notifierRefreshable: activityListNotifierProvider(filter).notifier,
|
||||||
|
contentBuilder:
|
||||||
|
(data, widgetCount, endItemView) => Center(
|
||||||
|
child: _ActivityListView(
|
||||||
|
data: data,
|
||||||
|
widgetCount: widgetCount,
|
||||||
|
endItemView: endItemView,
|
||||||
|
activitiesNotifier: activitiesNotifier,
|
||||||
|
contentOnly: filter != null,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ActivityListView extends HookConsumerWidget {
|
class _ActivityListView extends HookConsumerWidget {
|
||||||
final CursorPagingData<SnActivity> data;
|
final CursorPagingData<SnActivity> data;
|
||||||
final int widgetCount;
|
final int widgetCount;
|
||||||
final Widget endItemView;
|
final Widget endItemView;
|
||||||
|
final bool contentOnly;
|
||||||
final ActivityListNotifier activitiesNotifier;
|
final ActivityListNotifier activitiesNotifier;
|
||||||
|
|
||||||
const _ActivityListView({
|
const _ActivityListView({
|
||||||
@ -108,6 +182,7 @@ class _ActivityListView extends HookConsumerWidget {
|
|||||||
required this.widgetCount,
|
required this.widgetCount,
|
||||||
required this.endItemView,
|
required this.endItemView,
|
||||||
required this.activitiesNotifier,
|
required this.activitiesNotifier,
|
||||||
|
this.contentOnly = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -116,7 +191,8 @@ class _ActivityListView extends HookConsumerWidget {
|
|||||||
|
|
||||||
return CustomScrollView(
|
return CustomScrollView(
|
||||||
slivers: [
|
slivers: [
|
||||||
if (user.hasValue) SliverToBoxAdapter(child: CheckInWidget()),
|
if (user.hasValue && !contentOnly)
|
||||||
|
SliverToBoxAdapter(child: CheckInWidget()),
|
||||||
SliverList.builder(
|
SliverList.builder(
|
||||||
itemCount: widgetCount,
|
itemCount: widgetCount,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
@ -168,12 +244,6 @@ class _ActivityListView extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'accounts.check-in':
|
|
||||||
itemWidget = CheckInActivityWidget(item: item);
|
|
||||||
break;
|
|
||||||
case 'accounts.status':
|
|
||||||
itemWidget = StatusActivityWidget(item: item);
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
itemWidget = const Placeholder();
|
itemWidget = const Placeholder();
|
||||||
}
|
}
|
||||||
@ -181,7 +251,7 @@ class _ActivityListView extends HookConsumerWidget {
|
|||||||
return Column(children: [itemWidget, const Divider(height: 1)]);
|
return Column(children: [itemWidget, const Divider(height: 1)]);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
SliverGap(MediaQuery.of(context).padding.bottom + 16),
|
SliverGap(getTabbedPadding(context).bottom),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -191,17 +261,23 @@ class _ActivityListView extends HookConsumerWidget {
|
|||||||
class ActivityListNotifier extends _$ActivityListNotifier
|
class ActivityListNotifier extends _$ActivityListNotifier
|
||||||
with CursorPagingNotifierMixin<SnActivity> {
|
with CursorPagingNotifierMixin<SnActivity> {
|
||||||
@override
|
@override
|
||||||
Future<CursorPagingData<SnActivity>> build() => fetch(cursor: null);
|
Future<CursorPagingData<SnActivity>> build(String? filter) =>
|
||||||
|
fetch(cursor: null);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<CursorPagingData<SnActivity>> fetch({required String? cursor}) async {
|
Future<CursorPagingData<SnActivity>> fetch({required String? cursor}) async {
|
||||||
final client = ref.read(apiClientProvider);
|
final client = ref.read(apiClientProvider);
|
||||||
final offset = cursor == null ? 0 : int.parse(cursor);
|
|
||||||
final take = 20;
|
final take = 20;
|
||||||
|
|
||||||
|
final queryParameters = {
|
||||||
|
if (cursor != null) 'cursor': cursor,
|
||||||
|
'take': take,
|
||||||
|
if (filter != null) 'filter': filter,
|
||||||
|
};
|
||||||
|
|
||||||
final response = await client.get(
|
final response = await client.get(
|
||||||
'/activities',
|
'/activities',
|
||||||
queryParameters: {'offset': offset, 'take': take},
|
queryParameters: queryParameters,
|
||||||
);
|
);
|
||||||
|
|
||||||
final List<SnActivity> items =
|
final List<SnActivity> items =
|
||||||
@ -209,9 +285,14 @@ class ActivityListNotifier extends _$ActivityListNotifier
|
|||||||
.map((e) => SnActivity.fromJson(e as Map<String, dynamic>))
|
.map((e) => SnActivity.fromJson(e as Map<String, dynamic>))
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
final total = int.tryParse(response.headers['x-total']?.first ?? '') ?? 0;
|
final hasMore = (items.firstOrNull?.type ?? 'empty') != 'empty';
|
||||||
final hasMore = offset + items.length < total;
|
final nextCursor =
|
||||||
final nextCursor = hasMore ? (offset + items.length).toString() : null;
|
items
|
||||||
|
.map((x) => x.createdAt)
|
||||||
|
.lastOrNull
|
||||||
|
?.toUtc()
|
||||||
|
.toIso8601String()
|
||||||
|
.toString();
|
||||||
|
|
||||||
return CursorPagingData(
|
return CursorPagingData(
|
||||||
items: items,
|
items: items,
|
||||||
|
@ -7,25 +7,174 @@ part of 'explore.dart';
|
|||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
String _$activityListNotifierHash() =>
|
String _$activityListNotifierHash() =>
|
||||||
r'8a67d302e828408c7c4cf724d84c2c5958f2dc7e';
|
r'14ec2f211c86e1e64a9a34b142d0e8f78ff6361a';
|
||||||
|
|
||||||
|
/// Copied from Dart SDK
|
||||||
|
class _SystemHash {
|
||||||
|
_SystemHash._();
|
||||||
|
|
||||||
|
static int combine(int hash, int value) {
|
||||||
|
// ignore: parameter_assignments
|
||||||
|
hash = 0x1fffffff & (hash + value);
|
||||||
|
// ignore: parameter_assignments
|
||||||
|
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
|
||||||
|
return hash ^ (hash >> 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int finish(int hash) {
|
||||||
|
// ignore: parameter_assignments
|
||||||
|
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
|
||||||
|
// ignore: parameter_assignments
|
||||||
|
hash = hash ^ (hash >> 11);
|
||||||
|
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class _$ActivityListNotifier
|
||||||
|
extends BuildlessAutoDisposeAsyncNotifier<CursorPagingData<SnActivity>> {
|
||||||
|
late final String? filter;
|
||||||
|
|
||||||
|
FutureOr<CursorPagingData<SnActivity>> build(String? filter);
|
||||||
|
}
|
||||||
|
|
||||||
/// See also [ActivityListNotifier].
|
/// See also [ActivityListNotifier].
|
||||||
@ProviderFor(ActivityListNotifier)
|
@ProviderFor(ActivityListNotifier)
|
||||||
final activityListNotifierProvider = AutoDisposeAsyncNotifierProvider<
|
const activityListNotifierProvider = ActivityListNotifierFamily();
|
||||||
ActivityListNotifier,
|
|
||||||
CursorPagingData<SnActivity>
|
/// See also [ActivityListNotifier].
|
||||||
>.internal(
|
class ActivityListNotifierFamily
|
||||||
ActivityListNotifier.new,
|
extends Family<AsyncValue<CursorPagingData<SnActivity>>> {
|
||||||
name: r'activityListNotifierProvider',
|
/// See also [ActivityListNotifier].
|
||||||
debugGetCreateSourceHash:
|
const ActivityListNotifierFamily();
|
||||||
const bool.fromEnvironment('dart.vm.product')
|
|
||||||
? null
|
/// See also [ActivityListNotifier].
|
||||||
: _$activityListNotifierHash,
|
ActivityListNotifierProvider call(String? filter) {
|
||||||
dependencies: null,
|
return ActivityListNotifierProvider(filter);
|
||||||
allTransitiveDependencies: null,
|
}
|
||||||
);
|
|
||||||
|
@override
|
||||||
|
ActivityListNotifierProvider getProviderOverride(
|
||||||
|
covariant ActivityListNotifierProvider provider,
|
||||||
|
) {
|
||||||
|
return call(provider.filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
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'activityListNotifierProvider';
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See also [ActivityListNotifier].
|
||||||
|
class ActivityListNotifierProvider
|
||||||
|
extends
|
||||||
|
AutoDisposeAsyncNotifierProviderImpl<
|
||||||
|
ActivityListNotifier,
|
||||||
|
CursorPagingData<SnActivity>
|
||||||
|
> {
|
||||||
|
/// See also [ActivityListNotifier].
|
||||||
|
ActivityListNotifierProvider(String? filter)
|
||||||
|
: this._internal(
|
||||||
|
() => ActivityListNotifier()..filter = filter,
|
||||||
|
from: activityListNotifierProvider,
|
||||||
|
name: r'activityListNotifierProvider',
|
||||||
|
debugGetCreateSourceHash:
|
||||||
|
const bool.fromEnvironment('dart.vm.product')
|
||||||
|
? null
|
||||||
|
: _$activityListNotifierHash,
|
||||||
|
dependencies: ActivityListNotifierFamily._dependencies,
|
||||||
|
allTransitiveDependencies:
|
||||||
|
ActivityListNotifierFamily._allTransitiveDependencies,
|
||||||
|
filter: filter,
|
||||||
|
);
|
||||||
|
|
||||||
|
ActivityListNotifierProvider._internal(
|
||||||
|
super._createNotifier, {
|
||||||
|
required super.name,
|
||||||
|
required super.dependencies,
|
||||||
|
required super.allTransitiveDependencies,
|
||||||
|
required super.debugGetCreateSourceHash,
|
||||||
|
required super.from,
|
||||||
|
required this.filter,
|
||||||
|
}) : super.internal();
|
||||||
|
|
||||||
|
final String? filter;
|
||||||
|
|
||||||
|
@override
|
||||||
|
FutureOr<CursorPagingData<SnActivity>> runNotifierBuild(
|
||||||
|
covariant ActivityListNotifier notifier,
|
||||||
|
) {
|
||||||
|
return notifier.build(filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Override overrideWith(ActivityListNotifier Function() create) {
|
||||||
|
return ProviderOverride(
|
||||||
|
origin: this,
|
||||||
|
override: ActivityListNotifierProvider._internal(
|
||||||
|
() => create()..filter = filter,
|
||||||
|
from: from,
|
||||||
|
name: null,
|
||||||
|
dependencies: null,
|
||||||
|
allTransitiveDependencies: null,
|
||||||
|
debugGetCreateSourceHash: null,
|
||||||
|
filter: filter,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
AutoDisposeAsyncNotifierProviderElement<
|
||||||
|
ActivityListNotifier,
|
||||||
|
CursorPagingData<SnActivity>
|
||||||
|
>
|
||||||
|
createElement() {
|
||||||
|
return _ActivityListNotifierProviderElement(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return other is ActivityListNotifierProvider && other.filter == filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode {
|
||||||
|
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||||
|
hash = _SystemHash.combine(hash, filter.hashCode);
|
||||||
|
|
||||||
|
return _SystemHash.finish(hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||||
|
// ignore: unused_element
|
||||||
|
mixin ActivityListNotifierRef
|
||||||
|
on AutoDisposeAsyncNotifierProviderRef<CursorPagingData<SnActivity>> {
|
||||||
|
/// The parameter `filter` of this provider.
|
||||||
|
String? get filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ActivityListNotifierProviderElement
|
||||||
|
extends
|
||||||
|
AutoDisposeAsyncNotifierProviderElement<
|
||||||
|
ActivityListNotifier,
|
||||||
|
CursorPagingData<SnActivity>
|
||||||
|
>
|
||||||
|
with ActivityListNotifierRef {
|
||||||
|
_ActivityListNotifierProviderElement(super.provider);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String? get filter => (origin as ActivityListNotifierProvider).filter;
|
||||||
|
}
|
||||||
|
|
||||||
typedef _$ActivityListNotifier =
|
|
||||||
AutoDisposeAsyncNotifier<CursorPagingData<SnActivity>>;
|
|
||||||
// ignore_for_file: type=lint
|
// ignore_for_file: type=lint
|
||||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
||||||
|
@ -47,8 +47,9 @@ class NotificationUnreadCountNotifier
|
|||||||
void _subscribeToWebSocket() {
|
void _subscribeToWebSocket() {
|
||||||
final webSocketService = ref.read(websocketProvider);
|
final webSocketService = ref.read(websocketProvider);
|
||||||
_subscription = webSocketService.dataStream.listen((packet) {
|
_subscription = webSocketService.dataStream.listen((packet) {
|
||||||
if (packet.type == 'notifications.new') {
|
if (packet.type == 'notifications.new' && packet.data != null) {
|
||||||
_incrementCounter();
|
final notification = SnNotification.fromJson(packet.data!);
|
||||||
|
if (notification.topic != 'messages.new') _incrementCounter();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@ part of 'notification.dart';
|
|||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
String _$notificationUnreadCountNotifierHash() =>
|
String _$notificationUnreadCountNotifierHash() =>
|
||||||
r'372a2cc259d7d838cd4f33a9129f7396ef31dbb9';
|
r'0d5b07caa625c24575c5581d5fcd3089effc2844';
|
||||||
|
|
||||||
/// See also [NotificationUnreadCountNotifier].
|
/// See also [NotificationUnreadCountNotifier].
|
||||||
@ProviderFor(NotificationUnreadCountNotifier)
|
@ProviderFor(NotificationUnreadCountNotifier)
|
||||||
|
File diff suppressed because it is too large
Load Diff
417
lib/screens/posts/compose_article.dart
Normal file
417
lib/screens/posts/compose_article.dart
Normal file
@ -0,0 +1,417 @@
|
|||||||
|
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/models/file.dart';
|
||||||
|
import 'package:island/models/post.dart';
|
||||||
|
import 'package:island/screens/creators/publishers.dart';
|
||||||
|
import 'package:island/services/responsive.dart';
|
||||||
|
|
||||||
|
import 'package:island/widgets/app_scaffold.dart';
|
||||||
|
import 'package:island/screens/posts/detail.dart';
|
||||||
|
import 'package:island/widgets/content/attachment_preview.dart';
|
||||||
|
import 'package:island/widgets/post/compose_shared.dart';
|
||||||
|
import 'package:island/widgets/post/publishers_modal.dart';
|
||||||
|
import 'package:island/widgets/content/cloud_files.dart';
|
||||||
|
import 'package:island/widgets/post/compose_settings_sheet.dart';
|
||||||
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
|
||||||
|
@RoutePage()
|
||||||
|
class ArticleEditScreen extends HookConsumerWidget {
|
||||||
|
final String id;
|
||||||
|
const ArticleEditScreen({super.key, @PathParam('id') required this.id});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final post = ref.watch(postProvider(id));
|
||||||
|
return post.when(
|
||||||
|
data: (post) => ArticleComposeScreen(originalPost: post),
|
||||||
|
loading:
|
||||||
|
() => AppScaffold(
|
||||||
|
appBar: AppBar(leading: const PageBackButton()),
|
||||||
|
body: const Center(child: CircularProgressIndicator()),
|
||||||
|
),
|
||||||
|
error:
|
||||||
|
(e, _) => AppScaffold(
|
||||||
|
appBar: AppBar(leading: const PageBackButton()),
|
||||||
|
body: Text('Error: $e', textAlign: TextAlign.center),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@RoutePage()
|
||||||
|
class ArticleComposeScreen extends HookConsumerWidget {
|
||||||
|
final SnPost? originalPost;
|
||||||
|
|
||||||
|
const ArticleComposeScreen({super.key, this.originalPost});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
final colorScheme = theme.colorScheme;
|
||||||
|
|
||||||
|
final publishers = ref.watch(publishersManagedProvider);
|
||||||
|
final state = useMemoized(
|
||||||
|
() => ComposeLogic.createState(originalPost: originalPost),
|
||||||
|
[originalPost],
|
||||||
|
);
|
||||||
|
|
||||||
|
final showPreview = useState(false);
|
||||||
|
|
||||||
|
// Initialize publisher once when data is available
|
||||||
|
useEffect(() {
|
||||||
|
if (publishers.value?.isNotEmpty ?? false) {
|
||||||
|
state.currentPublisher.value = publishers.value!.first;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}, [publishers]);
|
||||||
|
|
||||||
|
// Dispose state when widget is disposed
|
||||||
|
useEffect(() {
|
||||||
|
return () => ComposeLogic.dispose(state);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Helper methods
|
||||||
|
void showSettingsSheet() {
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
isScrollControlled: true,
|
||||||
|
builder:
|
||||||
|
(context) => ComposeSettingsSheet(
|
||||||
|
titleController: state.titleController,
|
||||||
|
descriptionController: state.descriptionController,
|
||||||
|
visibility: state.visibility,
|
||||||
|
onVisibilityChanged: () {
|
||||||
|
// Trigger rebuild if needed
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void showKeyboardShortcutsDialog() {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder:
|
||||||
|
(context) => AlertDialog(
|
||||||
|
title: Text('keyboard_shortcuts'.tr()),
|
||||||
|
content: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text('Ctrl/Cmd + Enter: ${'submit'.tr()}'),
|
||||||
|
Text('Ctrl/Cmd + V: ${'paste'.tr()}'),
|
||||||
|
Text('Ctrl/Cmd + I: ${'add_image'.tr()}'),
|
||||||
|
Text('Ctrl/Cmd + Shift + V: ${'add_video'.tr()}'),
|
||||||
|
Text('Ctrl/Cmd + P: ${'toggle_preview'.tr()}'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
child: Text('close'.tr()),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget buildPreviewPane() {
|
||||||
|
return Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(color: colorScheme.outline.withOpacity(0.3)),
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: colorScheme.surfaceVariant.withOpacity(0.3),
|
||||||
|
borderRadius: const BorderRadius.only(
|
||||||
|
topLeft: Radius.circular(8),
|
||||||
|
topRight: Radius.circular(8),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Icon(Symbols.preview, size: 20),
|
||||||
|
const Gap(8),
|
||||||
|
Text('preview'.tr(), style: theme.textTheme.titleMedium),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
if (state.titleController.text.isNotEmpty) ...[
|
||||||
|
Text(
|
||||||
|
state.titleController.text,
|
||||||
|
style: theme.textTheme.headlineSmall?.copyWith(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Gap(16),
|
||||||
|
],
|
||||||
|
if (state.descriptionController.text.isNotEmpty) ...[
|
||||||
|
Text(
|
||||||
|
state.descriptionController.text,
|
||||||
|
style: theme.textTheme.bodyLarge?.copyWith(
|
||||||
|
color: colorScheme.onSurface.withOpacity(0.7),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Gap(16),
|
||||||
|
],
|
||||||
|
if (state.contentController.text.isNotEmpty)
|
||||||
|
Text(
|
||||||
|
state.contentController.text,
|
||||||
|
style: theme.textTheme.bodyMedium,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget buildEditorPane() {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
// Publisher row
|
||||||
|
Card(
|
||||||
|
elevation: 1,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
GestureDetector(
|
||||||
|
child: ProfilePictureWidget(
|
||||||
|
fileId: state.currentPublisher.value?.picture?.id,
|
||||||
|
radius: 20,
|
||||||
|
fallbackIcon:
|
||||||
|
state.currentPublisher.value == null
|
||||||
|
? Symbols.question_mark
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
showModalBottomSheet(
|
||||||
|
isScrollControlled: true,
|
||||||
|
context: context,
|
||||||
|
builder: (context) => const PublisherModal(),
|
||||||
|
).then((value) {
|
||||||
|
if (value != null) {
|
||||||
|
state.currentPublisher.value = value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const Gap(12),
|
||||||
|
Text(
|
||||||
|
state.currentPublisher.value?.name ??
|
||||||
|
'postPublisherUnselected'.tr(),
|
||||||
|
style: theme.textTheme.bodyMedium,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Content field with keyboard listener
|
||||||
|
Expanded(
|
||||||
|
child: RawKeyboardListener(
|
||||||
|
focusNode: FocusNode(),
|
||||||
|
onKey:
|
||||||
|
(event) => ComposeLogic.handleKeyPress(
|
||||||
|
event,
|
||||||
|
state,
|
||||||
|
ref,
|
||||||
|
context,
|
||||||
|
originalPost: originalPost,
|
||||||
|
postType: 1, // Article type
|
||||||
|
),
|
||||||
|
child: TextField(
|
||||||
|
controller: state.contentController,
|
||||||
|
style: theme.textTheme.bodyMedium,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
border: InputBorder.none,
|
||||||
|
hintText: 'postContent'.tr(),
|
||||||
|
contentPadding: const EdgeInsets.all(8),
|
||||||
|
),
|
||||||
|
maxLines: null,
|
||||||
|
expands: true,
|
||||||
|
textAlignVertical: TextAlignVertical.top,
|
||||||
|
onTapOutside:
|
||||||
|
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Attachments preview
|
||||||
|
ValueListenableBuilder<List<UniversalFile>>(
|
||||||
|
valueListenable: state.attachments,
|
||||||
|
builder: (context, attachments, _) {
|
||||||
|
if (attachments.isEmpty) return const SizedBox.shrink();
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
const Gap(16),
|
||||||
|
ValueListenableBuilder<Map<int, double>>(
|
||||||
|
valueListenable: state.attachmentProgress,
|
||||||
|
builder: (context, progressMap, _) {
|
||||||
|
return Wrap(
|
||||||
|
spacing: 8,
|
||||||
|
runSpacing: 8,
|
||||||
|
children: [
|
||||||
|
for (var idx = 0; idx < attachments.length; idx++)
|
||||||
|
SizedBox(
|
||||||
|
width: 120,
|
||||||
|
height: 120,
|
||||||
|
child: AttachmentPreview(
|
||||||
|
item: attachments[idx],
|
||||||
|
progress: progressMap[idx],
|
||||||
|
onRequestUpload:
|
||||||
|
() => ComposeLogic.uploadAttachment(ref, state, idx),
|
||||||
|
onDelete:
|
||||||
|
() => ComposeLogic.deleteAttachment(ref, state, idx),
|
||||||
|
onMove: (delta) {
|
||||||
|
state.attachments.value = ComposeLogic.moveAttachment(
|
||||||
|
state.attachments.value,
|
||||||
|
idx,
|
||||||
|
delta,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return AppScaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
leading: const PageBackButton(),
|
||||||
|
actions: [
|
||||||
|
// Info banner for article compose
|
||||||
|
const SizedBox.shrink(),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Symbols.settings),
|
||||||
|
onPressed: showSettingsSheet,
|
||||||
|
tooltip: 'postSettings'.tr(),
|
||||||
|
),
|
||||||
|
Tooltip(
|
||||||
|
message: 'togglePreview'.tr(),
|
||||||
|
child: IconButton(
|
||||||
|
icon: Icon(showPreview.value ? Symbols.edit : Symbols.preview),
|
||||||
|
onPressed: () => showPreview.value = !showPreview.value,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (isWideScreen(context))
|
||||||
|
Tooltip(
|
||||||
|
message: 'keyboard_shortcuts'.tr(),
|
||||||
|
child: IconButton(
|
||||||
|
icon: const Icon(Symbols.keyboard),
|
||||||
|
onPressed: showKeyboardShortcutsDialog,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ValueListenableBuilder<bool>(
|
||||||
|
valueListenable: state.submitting,
|
||||||
|
builder: (context, submitting, _) {
|
||||||
|
return IconButton(
|
||||||
|
onPressed:
|
||||||
|
submitting
|
||||||
|
? null
|
||||||
|
: () => ComposeLogic.performAction(
|
||||||
|
ref,
|
||||||
|
state,
|
||||||
|
context,
|
||||||
|
originalPost: originalPost,
|
||||||
|
postType: 1, // Article type
|
||||||
|
),
|
||||||
|
icon:
|
||||||
|
submitting
|
||||||
|
? SizedBox(
|
||||||
|
width: 28,
|
||||||
|
height: 28,
|
||||||
|
child: const CircularProgressIndicator(
|
||||||
|
color: Colors.white,
|
||||||
|
strokeWidth: 2.5,
|
||||||
|
),
|
||||||
|
).center()
|
||||||
|
: Icon(
|
||||||
|
originalPost != null ? Symbols.edit : Symbols.upload,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const Gap(8),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
body: Column(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child:
|
||||||
|
isWideScreen(context)
|
||||||
|
? Row(
|
||||||
|
spacing: 16,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
flex: showPreview.value ? 1 : 2,
|
||||||
|
child: buildEditorPane(),
|
||||||
|
),
|
||||||
|
if (showPreview.value)
|
||||||
|
Expanded(child: buildPreviewPane()),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
: showPreview.value
|
||||||
|
? buildPreviewPane()
|
||||||
|
: buildEditorPane(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Bottom toolbar
|
||||||
|
Material(
|
||||||
|
elevation: 4,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
onPressed: () => ComposeLogic.pickPhotoMedia(ref, state),
|
||||||
|
icon: const Icon(Symbols.add_a_photo),
|
||||||
|
color: colorScheme.primary,
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
onPressed: () => ComposeLogic.pickVideoMedia(ref, state),
|
||||||
|
icon: const Icon(Symbols.videocam),
|
||||||
|
color: colorScheme.primary,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).padding(
|
||||||
|
bottom: MediaQuery.of(context).padding.bottom + 16,
|
||||||
|
horizontal: 16,
|
||||||
|
top: 8,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -4,6 +4,7 @@ import 'package:gap/gap.dart';
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:island/models/post.dart';
|
import 'package:island/models/post.dart';
|
||||||
import 'package:island/pods/network.dart';
|
import 'package:island/pods/network.dart';
|
||||||
|
import 'package:island/pods/userinfo.dart';
|
||||||
import 'package:island/services/responsive.dart';
|
import 'package:island/services/responsive.dart';
|
||||||
import 'package:island/widgets/app_scaffold.dart';
|
import 'package:island/widgets/app_scaffold.dart';
|
||||||
import 'package:island/widgets/post/post_item.dart';
|
import 'package:island/widgets/post/post_item.dart';
|
||||||
@ -29,6 +30,7 @@ class PostDetailScreen extends HookConsumerWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final post = ref.watch(postProvider(id));
|
final post = ref.watch(postProvider(id));
|
||||||
|
final user = ref.watch(userInfoProvider);
|
||||||
|
|
||||||
final isWide = isWideScreen(context);
|
final isWide = isWideScreen(context);
|
||||||
|
|
||||||
@ -47,6 +49,7 @@ class PostDetailScreen extends HookConsumerWidget {
|
|||||||
PostItem(
|
PostItem(
|
||||||
item: post!,
|
item: post!,
|
||||||
isOpenable: false,
|
isOpenable: false,
|
||||||
|
isFullPost: true,
|
||||||
backgroundColor: isWide ? Colors.transparent : null,
|
backgroundColor: isWide ? Colors.transparent : null,
|
||||||
),
|
),
|
||||||
const Divider(height: 1),
|
const Divider(height: 1),
|
||||||
@ -57,25 +60,25 @@ class PostDetailScreen extends HookConsumerWidget {
|
|||||||
SliverGap(MediaQuery.of(context).padding.bottom + 80),
|
SliverGap(MediaQuery.of(context).padding.bottom + 80),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Positioned(
|
if (user.value != null)
|
||||||
bottom: 0,
|
Positioned(
|
||||||
left: 0,
|
bottom: 0,
|
||||||
right: 0,
|
left: 0,
|
||||||
child: Material(
|
right: 0,
|
||||||
elevation: 2,
|
child: Material(
|
||||||
color: Colors.transparent,
|
elevation: 2,
|
||||||
child: PostQuickReply(
|
child: PostQuickReply(
|
||||||
parent: post,
|
parent: post,
|
||||||
onPosted: () {
|
onPosted: () {
|
||||||
ref.invalidate(postRepliesNotifierProvider(id));
|
ref.invalidate(postRepliesNotifierProvider(id));
|
||||||
},
|
},
|
||||||
).padding(
|
).padding(
|
||||||
bottom: MediaQuery.of(context).padding.bottom + 16,
|
bottom: MediaQuery.of(context).padding.bottom + 16,
|
||||||
top: 16,
|
top: 16,
|
||||||
horizontal: 16,
|
horizontal: 16,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -7,13 +7,18 @@ import 'package:gap/gap.dart';
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:island/models/post.dart';
|
import 'package:island/models/post.dart';
|
||||||
import 'package:island/models/user.dart';
|
import 'package:island/models/user.dart';
|
||||||
|
import 'package:island/pods/config.dart';
|
||||||
import 'package:island/pods/network.dart';
|
import 'package:island/pods/network.dart';
|
||||||
|
import 'package:island/services/color.dart';
|
||||||
|
import 'package:island/widgets/account/account_name.dart';
|
||||||
import 'package:island/widgets/account/badge.dart';
|
import 'package:island/widgets/account/badge.dart';
|
||||||
import 'package:island/widgets/account/status.dart';
|
import 'package:island/widgets/account/status.dart';
|
||||||
import 'package:island/widgets/alert.dart';
|
import 'package:island/widgets/alert.dart';
|
||||||
import 'package:island/widgets/app_scaffold.dart';
|
import 'package:island/widgets/app_scaffold.dart';
|
||||||
import 'package:island/widgets/content/cloud_files.dart';
|
import 'package:island/widgets/content/cloud_files.dart';
|
||||||
import 'package:island/widgets/post/post_list.dart';
|
import 'package:island/widgets/post/post_list.dart';
|
||||||
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
|
import 'package:palette_generator/palette_generator.dart';
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
|
||||||
@ -47,6 +52,21 @@ Future<SnSubscriptionStatus> publisherSubscriptionStatus(
|
|||||||
return SnSubscriptionStatus.fromJson(resp.data);
|
return SnSubscriptionStatus.fromJson(resp.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@riverpod
|
||||||
|
Future<Color?> publisherAppbarForcegroundColor(Ref ref, String pubName) async {
|
||||||
|
final publisher = await ref.watch(publisherProvider(pubName).future);
|
||||||
|
if (publisher.background == null) return null;
|
||||||
|
final palette = await PaletteGenerator.fromImageProvider(
|
||||||
|
CloudImageWidget.provider(
|
||||||
|
fileId: publisher.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;
|
||||||
|
}
|
||||||
|
|
||||||
@RoutePage()
|
@RoutePage()
|
||||||
class PublisherProfileScreen extends HookConsumerWidget {
|
class PublisherProfileScreen extends HookConsumerWidget {
|
||||||
final String name;
|
final String name;
|
||||||
@ -60,6 +80,9 @@ class PublisherProfileScreen extends HookConsumerWidget {
|
|||||||
final publisher = ref.watch(publisherProvider(name));
|
final publisher = ref.watch(publisherProvider(name));
|
||||||
final badges = ref.watch(publisherBadgesProvider(name));
|
final badges = ref.watch(publisherBadgesProvider(name));
|
||||||
final subStatus = ref.watch(publisherSubscriptionStatusProvider(name));
|
final subStatus = ref.watch(publisherSubscriptionStatusProvider(name));
|
||||||
|
final appbarColor = ref.watch(
|
||||||
|
publisherAppbarForcegroundColorProvider(name),
|
||||||
|
);
|
||||||
|
|
||||||
final subscribing = useState(false);
|
final subscribing = useState(false);
|
||||||
|
|
||||||
@ -91,8 +114,8 @@ class PublisherProfileScreen extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final iconShadow = Shadow(
|
final appbarShadow = Shadow(
|
||||||
color: Colors.black54,
|
color: appbarColor.value?.invert ?? Colors.transparent,
|
||||||
blurRadius: 5.0,
|
blurRadius: 5.0,
|
||||||
offset: Offset(1.0, 1.0),
|
offset: Offset(1.0, 1.0),
|
||||||
);
|
);
|
||||||
@ -103,69 +126,68 @@ class PublisherProfileScreen extends HookConsumerWidget {
|
|||||||
body: CustomScrollView(
|
body: CustomScrollView(
|
||||||
slivers: [
|
slivers: [
|
||||||
SliverAppBar(
|
SliverAppBar(
|
||||||
|
foregroundColor: appbarColor.value,
|
||||||
expandedHeight: 180,
|
expandedHeight: 180,
|
||||||
pinned: true,
|
pinned: true,
|
||||||
leading: PageBackButton(shadows: [iconShadow]),
|
leading: PageBackButton(
|
||||||
flexibleSpace: FlexibleSpaceBar(
|
color: appbarColor.value,
|
||||||
background:
|
shadows: [appbarShadow],
|
||||||
data.background?.id != null
|
|
||||||
? CloudImageWidget(fileId: data.background!.id)
|
|
||||||
: Container(
|
|
||||||
color:
|
|
||||||
Theme.of(context).appBarTheme.backgroundColor,
|
|
||||||
),
|
|
||||||
title: Text(
|
|
||||||
data.nick,
|
|
||||||
style: TextStyle(
|
|
||||||
color: Theme.of(context).appBarTheme.foregroundColor,
|
|
||||||
shadows: [iconShadow],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
actions: [
|
flexibleSpace: Stack(
|
||||||
subStatus.when(
|
children: [
|
||||||
data:
|
Positioned.fill(
|
||||||
(status) => IconButton(
|
child:
|
||||||
onPressed:
|
data.background?.id != null
|
||||||
subscribing.value
|
? CloudImageWidget(file: data.background)
|
||||||
? null
|
: Container(
|
||||||
: (status.isSubscribed
|
color:
|
||||||
? unsubscribe
|
Theme.of(
|
||||||
: subscribe),
|
context,
|
||||||
icon: Icon(
|
).appBarTheme.backgroundColor,
|
||||||
status.isSubscribed
|
|
||||||
? Icons.remove_circle
|
|
||||||
: Icons.add_circle,
|
|
||||||
shadows: [iconShadow],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
error: (_, _) => const SizedBox(),
|
|
||||||
loading:
|
|
||||||
() => const SizedBox(
|
|
||||||
width: 48,
|
|
||||||
height: 48,
|
|
||||||
child: Center(
|
|
||||||
child: SizedBox(
|
|
||||||
width: 20,
|
|
||||||
height: 20,
|
|
||||||
child: CircularProgressIndicator(
|
|
||||||
strokeWidth: 2,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
FlexibleSpaceBar(
|
||||||
|
title: Text(
|
||||||
|
data.nick,
|
||||||
|
style: TextStyle(
|
||||||
|
color:
|
||||||
|
appbarColor.value ??
|
||||||
|
Theme.of(context).appBarTheme.foregroundColor,
|
||||||
|
shadows: [appbarShadow],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Gap(8),
|
background:
|
||||||
],
|
Container(), // Empty container since background is handled by Stack
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
child: Row(
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
spacing: 20,
|
spacing: 20,
|
||||||
children: [
|
children: [
|
||||||
ProfilePictureWidget(
|
GestureDetector(
|
||||||
fileId: data.picture!.id,
|
child: Badge(
|
||||||
radius: 32,
|
isLabelVisible: data.type == 0,
|
||||||
|
padding: EdgeInsets.all(4),
|
||||||
|
label: Icon(
|
||||||
|
Symbols.launch,
|
||||||
|
size: 16,
|
||||||
|
color: Theme.of(context).colorScheme.onPrimary,
|
||||||
|
),
|
||||||
|
backgroundColor:
|
||||||
|
Theme.of(context).colorScheme.primary,
|
||||||
|
offset: Offset(0, 48),
|
||||||
|
child: ProfilePictureWidget(
|
||||||
|
file: data.picture,
|
||||||
|
radius: 32,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
Navigator.pop(context, true);
|
||||||
|
context.router.pushPath('/account/${data.name}');
|
||||||
|
},
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
@ -175,62 +197,115 @@ class PublisherProfileScreen extends HookConsumerWidget {
|
|||||||
spacing: 6,
|
spacing: 6,
|
||||||
children: [
|
children: [
|
||||||
Text(data.nick).fontSize(20),
|
Text(data.nick).fontSize(20),
|
||||||
|
if (data.verification != null)
|
||||||
|
VerificationMark(mark: data.verification!),
|
||||||
Text(
|
Text(
|
||||||
'@${data.name}',
|
'@${data.name}',
|
||||||
).fontSize(14).opacity(0.85),
|
).fontSize(14).opacity(0.85),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (data.type == 0 && data.account != null)
|
if (data.type == 0 && data.account != null)
|
||||||
InkWell(
|
Row(
|
||||||
onTap: () {
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
context.router.pushPath(
|
spacing: 6,
|
||||||
'/account/${data.account!.name}',
|
children: [
|
||||||
);
|
Icon(
|
||||||
},
|
data.type == 0
|
||||||
child: Row(
|
? Symbols.person
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
: Symbols.workspaces,
|
||||||
spacing: 4,
|
fill: 1,
|
||||||
children: [
|
size: 17,
|
||||||
Text(
|
),
|
||||||
'publisherVisitAccountPage'.tr(
|
Text(
|
||||||
args: ['@${data.account!.name}'],
|
'publisherBelongsTo'.tr(
|
||||||
),
|
args: ['@${data.account!.name}'],
|
||||||
).fontSize(14),
|
),
|
||||||
Icon(Icons.launch, size: 14),
|
).fontSize(14),
|
||||||
],
|
],
|
||||||
).opacity(0.85),
|
).opacity(0.85).padding(bottom: 6),
|
||||||
).padding(bottom: 6),
|
|
||||||
if (data.type == 0 && data.account != null)
|
if (data.type == 0 && data.account != null)
|
||||||
AccountStatusWidget(
|
AccountStatusWidget(
|
||||||
uname: data.account!.name,
|
uname: data.account!.name,
|
||||||
padding: EdgeInsets.zero,
|
padding: EdgeInsets.zero,
|
||||||
),
|
),
|
||||||
|
subStatus
|
||||||
|
.when(
|
||||||
|
data:
|
||||||
|
(status) => FilledButton.icon(
|
||||||
|
onPressed:
|
||||||
|
subscribing.value
|
||||||
|
? null
|
||||||
|
: (status.isSubscribed
|
||||||
|
? unsubscribe
|
||||||
|
: subscribe),
|
||||||
|
icon: Icon(
|
||||||
|
status.isSubscribed
|
||||||
|
? Symbols.remove_circle
|
||||||
|
: Symbols.add_circle,
|
||||||
|
),
|
||||||
|
label:
|
||||||
|
Text(
|
||||||
|
status.isSubscribed
|
||||||
|
? 'unsubscribe'
|
||||||
|
: 'subscribe',
|
||||||
|
).tr(),
|
||||||
|
style: ButtonStyle(
|
||||||
|
visualDensity: VisualDensity(
|
||||||
|
vertical: -2,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
error: (_, _) => const SizedBox(),
|
||||||
|
loading:
|
||||||
|
() => const SizedBox(
|
||||||
|
height: 36,
|
||||||
|
child: Center(
|
||||||
|
child: SizedBox(
|
||||||
|
width: 20,
|
||||||
|
height: 20,
|
||||||
|
child: CircularProgressIndicator(
|
||||||
|
strokeWidth: 2,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.padding(top: 8),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
).padding(horizontal: 24, top: 24, bottom: 24),
|
).padding(horizontal: 24, top: 24),
|
||||||
|
),
|
||||||
|
SliverToBoxAdapter(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
if (badges.value?.isNotEmpty ?? false)
|
||||||
|
BadgeList(badges: badges.value!).padding(top: 16),
|
||||||
|
if (data.verification != null)
|
||||||
|
VerificationStatusCard(
|
||||||
|
mark: data.verification!,
|
||||||
|
).padding(top: 16),
|
||||||
|
],
|
||||||
|
).padding(horizontal: 24),
|
||||||
|
),
|
||||||
|
SliverToBoxAdapter(
|
||||||
|
child: const Divider(height: 1).padding(vertical: 24),
|
||||||
|
),
|
||||||
|
SliverToBoxAdapter(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
Text('bio').tr().bold(),
|
||||||
|
Text(
|
||||||
|
data.bio.isEmpty ? 'descriptionNone'.tr() : data.bio,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).padding(horizontal: 24),
|
||||||
|
),
|
||||||
|
SliverToBoxAdapter(
|
||||||
|
child: const Divider(height: 1).padding(top: 24),
|
||||||
),
|
),
|
||||||
if (badges.value?.isNotEmpty ?? false)
|
|
||||||
SliverToBoxAdapter(
|
|
||||||
child: BadgeList(
|
|
||||||
badges: badges.value!,
|
|
||||||
).padding(horizontal: 24, bottom: 24),
|
|
||||||
)
|
|
||||||
else
|
|
||||||
const SliverGap(16),
|
|
||||||
SliverToBoxAdapter(child: const Divider(height: 1)),
|
|
||||||
if (data.bio.isNotEmpty)
|
|
||||||
SliverToBoxAdapter(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
||||||
children: [Text('bio').tr().bold(), Text(data.bio)],
|
|
||||||
).padding(horizontal: 24, top: 24),
|
|
||||||
),
|
|
||||||
if (data.bio.isNotEmpty)
|
|
||||||
SliverToBoxAdapter(
|
|
||||||
child: const Divider(height: 1).padding(top: 24),
|
|
||||||
),
|
|
||||||
SliverPostList(pubName: name),
|
SliverPostList(pubName: name),
|
||||||
SliverGap(MediaQuery.of(context).padding.bottom + 16),
|
SliverGap(MediaQuery.of(context).padding.bottom + 16),
|
||||||
],
|
],
|
||||||
|
@ -399,5 +399,136 @@ class _PublisherSubscriptionStatusProviderElement
|
|||||||
String get pubName => (origin as PublisherSubscriptionStatusProvider).pubName;
|
String get pubName => (origin as PublisherSubscriptionStatusProvider).pubName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String _$publisherAppbarForcegroundColorHash() =>
|
||||||
|
r'3ff2eebb48d3f3af1907052f471e648f5b14b13c';
|
||||||
|
|
||||||
|
/// See also [publisherAppbarForcegroundColor].
|
||||||
|
@ProviderFor(publisherAppbarForcegroundColor)
|
||||||
|
const publisherAppbarForcegroundColorProvider =
|
||||||
|
PublisherAppbarForcegroundColorFamily();
|
||||||
|
|
||||||
|
/// See also [publisherAppbarForcegroundColor].
|
||||||
|
class PublisherAppbarForcegroundColorFamily extends Family<AsyncValue<Color?>> {
|
||||||
|
/// See also [publisherAppbarForcegroundColor].
|
||||||
|
const PublisherAppbarForcegroundColorFamily();
|
||||||
|
|
||||||
|
/// See also [publisherAppbarForcegroundColor].
|
||||||
|
PublisherAppbarForcegroundColorProvider call(String pubName) {
|
||||||
|
return PublisherAppbarForcegroundColorProvider(pubName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
PublisherAppbarForcegroundColorProvider getProviderOverride(
|
||||||
|
covariant PublisherAppbarForcegroundColorProvider provider,
|
||||||
|
) {
|
||||||
|
return call(provider.pubName);
|
||||||
|
}
|
||||||
|
|
||||||
|
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'publisherAppbarForcegroundColorProvider';
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See also [publisherAppbarForcegroundColor].
|
||||||
|
class PublisherAppbarForcegroundColorProvider
|
||||||
|
extends AutoDisposeFutureProvider<Color?> {
|
||||||
|
/// See also [publisherAppbarForcegroundColor].
|
||||||
|
PublisherAppbarForcegroundColorProvider(String pubName)
|
||||||
|
: this._internal(
|
||||||
|
(ref) => publisherAppbarForcegroundColor(
|
||||||
|
ref as PublisherAppbarForcegroundColorRef,
|
||||||
|
pubName,
|
||||||
|
),
|
||||||
|
from: publisherAppbarForcegroundColorProvider,
|
||||||
|
name: r'publisherAppbarForcegroundColorProvider',
|
||||||
|
debugGetCreateSourceHash:
|
||||||
|
const bool.fromEnvironment('dart.vm.product')
|
||||||
|
? null
|
||||||
|
: _$publisherAppbarForcegroundColorHash,
|
||||||
|
dependencies: PublisherAppbarForcegroundColorFamily._dependencies,
|
||||||
|
allTransitiveDependencies:
|
||||||
|
PublisherAppbarForcegroundColorFamily._allTransitiveDependencies,
|
||||||
|
pubName: pubName,
|
||||||
|
);
|
||||||
|
|
||||||
|
PublisherAppbarForcegroundColorProvider._internal(
|
||||||
|
super._createNotifier, {
|
||||||
|
required super.name,
|
||||||
|
required super.dependencies,
|
||||||
|
required super.allTransitiveDependencies,
|
||||||
|
required super.debugGetCreateSourceHash,
|
||||||
|
required super.from,
|
||||||
|
required this.pubName,
|
||||||
|
}) : super.internal();
|
||||||
|
|
||||||
|
final String pubName;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Override overrideWith(
|
||||||
|
FutureOr<Color?> Function(PublisherAppbarForcegroundColorRef provider)
|
||||||
|
create,
|
||||||
|
) {
|
||||||
|
return ProviderOverride(
|
||||||
|
origin: this,
|
||||||
|
override: PublisherAppbarForcegroundColorProvider._internal(
|
||||||
|
(ref) => create(ref as PublisherAppbarForcegroundColorRef),
|
||||||
|
from: from,
|
||||||
|
name: null,
|
||||||
|
dependencies: null,
|
||||||
|
allTransitiveDependencies: null,
|
||||||
|
debugGetCreateSourceHash: null,
|
||||||
|
pubName: pubName,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
AutoDisposeFutureProviderElement<Color?> createElement() {
|
||||||
|
return _PublisherAppbarForcegroundColorProviderElement(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return other is PublisherAppbarForcegroundColorProvider &&
|
||||||
|
other.pubName == pubName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode {
|
||||||
|
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||||
|
hash = _SystemHash.combine(hash, pubName.hashCode);
|
||||||
|
|
||||||
|
return _SystemHash.finish(hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||||
|
// ignore: unused_element
|
||||||
|
mixin PublisherAppbarForcegroundColorRef
|
||||||
|
on AutoDisposeFutureProviderRef<Color?> {
|
||||||
|
/// The parameter `pubName` of this provider.
|
||||||
|
String get pubName;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PublisherAppbarForcegroundColorProviderElement
|
||||||
|
extends AutoDisposeFutureProviderElement<Color?>
|
||||||
|
with PublisherAppbarForcegroundColorRef {
|
||||||
|
_PublisherAppbarForcegroundColorProviderElement(super.provider);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get pubName =>
|
||||||
|
(origin as PublisherAppbarForcegroundColorProvider).pubName;
|
||||||
|
}
|
||||||
|
|
||||||
// ignore_for_file: type=lint
|
// ignore_for_file: type=lint
|
||||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
||||||
|
@ -15,6 +15,7 @@ import 'package:island/widgets/app_scaffold.dart';
|
|||||||
import 'package:island/widgets/content/cloud_files.dart';
|
import 'package:island/widgets/content/cloud_files.dart';
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
|
||||||
part 'detail.g.dart';
|
part 'detail.g.dart';
|
||||||
@ -250,6 +251,42 @@ class _RealmActionMenu extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@riverpod
|
||||||
|
class RealmMemberListNotifier extends _$RealmMemberListNotifier
|
||||||
|
with CursorPagingNotifierMixin<SnRealmMember> {
|
||||||
|
static const int _pageSize = 20;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<CursorPagingData<SnRealmMember>> build(String realmSlug) async {
|
||||||
|
return fetch();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<CursorPagingData<SnRealmMember>> fetch({String? cursor}) async {
|
||||||
|
final apiClient = ref.read(apiClientProvider);
|
||||||
|
final offset = cursor != null ? int.parse(cursor) : 0;
|
||||||
|
|
||||||
|
final response = await apiClient.get(
|
||||||
|
'/realms/$realmSlug/members',
|
||||||
|
queryParameters: {'offset': offset, 'take': _pageSize},
|
||||||
|
);
|
||||||
|
|
||||||
|
final total = int.parse(response.headers.value('X-Total') ?? '0');
|
||||||
|
final List<dynamic> data = response.data;
|
||||||
|
final members = data.map((e) => SnRealmMember.fromJson(e)).toList();
|
||||||
|
|
||||||
|
final hasMore = offset + members.length < total;
|
||||||
|
final nextCursor = hasMore ? (offset + members.length).toString() : null;
|
||||||
|
|
||||||
|
return CursorPagingData(
|
||||||
|
items: members,
|
||||||
|
hasMore: hasMore,
|
||||||
|
nextCursor: nextCursor,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep the old provider for backward compatibility
|
||||||
final realmMemberStateProvider =
|
final realmMemberStateProvider =
|
||||||
StateNotifierProvider.family<RealmMemberNotifier, RealmMemberState, String>(
|
StateNotifierProvider.family<RealmMemberNotifier, RealmMemberState, String>(
|
||||||
(ref, realmSlug) {
|
(ref, realmSlug) {
|
||||||
@ -302,13 +339,15 @@ class _RealmMemberListSheet extends HookConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final realmIdentity = ref.watch(realmIdentityProvider(realmSlug));
|
||||||
|
final memberListProvider = realmMemberListNotifierProvider(realmSlug);
|
||||||
|
|
||||||
|
// For backward compatibility and to show total count in the header
|
||||||
final memberState = ref.watch(realmMemberStateProvider(realmSlug));
|
final memberState = ref.watch(realmMemberStateProvider(realmSlug));
|
||||||
final memberNotifier = ref.read(
|
final memberNotifier = ref.read(
|
||||||
realmMemberStateProvider(realmSlug).notifier,
|
realmMemberStateProvider(realmSlug).notifier,
|
||||||
);
|
);
|
||||||
|
|
||||||
final realmIdentity = ref.watch(realmIdentityProvider(realmSlug));
|
|
||||||
|
|
||||||
useEffect(() {
|
useEffect(() {
|
||||||
Future(() {
|
Future(() {
|
||||||
memberNotifier.loadMore();
|
memberNotifier.loadMore();
|
||||||
@ -329,8 +368,10 @@ class _RealmMemberListSheet extends HookConsumerWidget {
|
|||||||
'/realms/invites/$realmSlug',
|
'/realms/invites/$realmSlug',
|
||||||
data: {'related_user_id': result.id, 'role': 0},
|
data: {'related_user_id': result.id, 'role': 0},
|
||||||
);
|
);
|
||||||
|
// Refresh both providers
|
||||||
memberNotifier.reset();
|
memberNotifier.reset();
|
||||||
await memberNotifier.loadMore();
|
await memberNotifier.loadMore();
|
||||||
|
ref.invalidate(memberListProvider);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
showErrorAlert(err);
|
showErrorAlert(err);
|
||||||
}
|
}
|
||||||
@ -362,8 +403,10 @@ class _RealmMemberListSheet extends HookConsumerWidget {
|
|||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Symbols.refresh),
|
icon: const Icon(Symbols.refresh),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
|
// Refresh both providers
|
||||||
memberNotifier.reset();
|
memberNotifier.reset();
|
||||||
memberNotifier.loadMore();
|
memberNotifier.loadMore();
|
||||||
|
ref.invalidate(memberListProvider);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
@ -376,108 +419,103 @@ class _RealmMemberListSheet extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
const Divider(height: 1),
|
const Divider(height: 1),
|
||||||
Expanded(
|
Expanded(
|
||||||
child:
|
child: PagingHelperView(
|
||||||
memberState.error != null
|
provider: memberListProvider,
|
||||||
? Center(child: Text(memberState.error!))
|
futureRefreshable: memberListProvider.future,
|
||||||
: ListView.builder(
|
notifierRefreshable: memberListProvider.notifier,
|
||||||
itemCount: memberState.members.length + 1,
|
contentBuilder: (data, widgetCount, endItemView) {
|
||||||
itemBuilder: (context, index) {
|
return ListView.builder(
|
||||||
if (index == memberState.members.length) {
|
itemCount: widgetCount,
|
||||||
if (memberState.isLoading) {
|
itemBuilder: (context, index) {
|
||||||
return const Center(
|
if (index == data.items.length) {
|
||||||
child: Padding(
|
return endItemView;
|
||||||
padding: EdgeInsets.all(16.0),
|
}
|
||||||
child: CircularProgressIndicator(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (memberState.members.length < memberState.total) {
|
|
||||||
memberNotifier.loadMore(
|
|
||||||
offset: memberState.members.length,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return const SizedBox.shrink();
|
|
||||||
}
|
|
||||||
|
|
||||||
final member = memberState.members[index];
|
final member = data.items[index];
|
||||||
return ListTile(
|
return ListTile(
|
||||||
contentPadding: EdgeInsets.only(left: 16, right: 12),
|
contentPadding: EdgeInsets.only(left: 16, right: 12),
|
||||||
leading: ProfilePictureWidget(
|
leading: ProfilePictureWidget(
|
||||||
fileId: member.account!.profile.picture?.id,
|
fileId: member.account!.profile.picture?.id,
|
||||||
),
|
),
|
||||||
title: Row(
|
title: Row(
|
||||||
spacing: 6,
|
spacing: 6,
|
||||||
children: [
|
children: [
|
||||||
Flexible(child: Text(member.account!.nick)),
|
Flexible(child: Text(member.account!.nick)),
|
||||||
if (member.joinedAt == null)
|
if (member.joinedAt == null)
|
||||||
const Icon(Symbols.pending_actions, size: 20),
|
const Icon(Symbols.pending_actions, size: 20),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
subtitle: Row(
|
subtitle: Row(
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
member.role >= 100
|
member.role >= 100
|
||||||
? 'permissionOwner'
|
? 'permissionOwner'
|
||||||
: member.role >= 50
|
: member.role >= 50
|
||||||
? 'permissionModerator'
|
? 'permissionModerator'
|
||||||
: 'permissionMember',
|
: 'permissionMember',
|
||||||
).tr(),
|
).tr(),
|
||||||
Text('·').bold().padding(horizontal: 6),
|
Text('·').bold().padding(horizontal: 6),
|
||||||
Expanded(child: Text("@${member.account!.name}")),
|
Expanded(child: Text("@${member.account!.name}")),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
trailing: Row(
|
trailing: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
if ((realmIdentity.value?.role ?? 0) >= 50)
|
if ((realmIdentity.value?.role ?? 0) >= 50)
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Symbols.edit),
|
icon: const Icon(Symbols.edit),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
isScrollControlled: true,
|
isScrollControlled: true,
|
||||||
context: context,
|
context: context,
|
||||||
builder:
|
builder:
|
||||||
(context) => _RealmMemberRoleSheet(
|
(context) => _RealmMemberRoleSheet(
|
||||||
realmSlug: realmSlug,
|
realmSlug: realmSlug,
|
||||||
member: member,
|
member: member,
|
||||||
),
|
),
|
||||||
).then((value) {
|
).then((value) {
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
memberNotifier.reset();
|
// Refresh both providers
|
||||||
memberNotifier.loadMore();
|
memberNotifier.reset();
|
||||||
}
|
memberNotifier.loadMore();
|
||||||
});
|
ref.invalidate(memberListProvider);
|
||||||
},
|
}
|
||||||
),
|
});
|
||||||
if ((realmIdentity.value?.role ?? 0) >= 50)
|
},
|
||||||
IconButton(
|
),
|
||||||
icon: const Icon(Symbols.delete),
|
if ((realmIdentity.value?.role ?? 0) >= 50)
|
||||||
onPressed: () {
|
IconButton(
|
||||||
showConfirmAlert(
|
icon: const Icon(Symbols.delete),
|
||||||
'removeRealmMemberHint'.tr(),
|
onPressed: () {
|
||||||
'removeRealmMember'.tr(),
|
showConfirmAlert(
|
||||||
).then((confirm) async {
|
'removeRealmMemberHint'.tr(),
|
||||||
if (confirm != true) return;
|
'removeRealmMember'.tr(),
|
||||||
try {
|
).then((confirm) async {
|
||||||
final apiClient = ref.watch(
|
if (confirm != true) return;
|
||||||
apiClientProvider,
|
try {
|
||||||
);
|
final apiClient = ref.watch(
|
||||||
await apiClient.delete(
|
apiClientProvider,
|
||||||
'/realms/$realmSlug/members/${member.accountId}',
|
);
|
||||||
);
|
await apiClient.delete(
|
||||||
memberNotifier.reset();
|
'/realms/$realmSlug/members/${member.accountId}',
|
||||||
memberNotifier.loadMore();
|
);
|
||||||
} catch (err) {
|
// Refresh both providers
|
||||||
showErrorAlert(err);
|
memberNotifier.reset();
|
||||||
}
|
memberNotifier.loadMore();
|
||||||
});
|
ref.invalidate(memberListProvider);
|
||||||
},
|
} catch (err) {
|
||||||
),
|
showErrorAlert(err);
|
||||||
],
|
}
|
||||||
),
|
});
|
||||||
);
|
},
|
||||||
},
|
),
|
||||||
),
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -148,5 +148,155 @@ class _RealmIdentityProviderElement
|
|||||||
String get realmSlug => (origin as RealmIdentityProvider).realmSlug;
|
String get realmSlug => (origin as RealmIdentityProvider).realmSlug;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String _$realmMemberListNotifierHash() =>
|
||||||
|
r'b2e3eefc62a597f45df9470b2058fdda62f8853f';
|
||||||
|
|
||||||
|
abstract class _$RealmMemberListNotifier
|
||||||
|
extends BuildlessAutoDisposeAsyncNotifier<CursorPagingData<SnRealmMember>> {
|
||||||
|
late final String realmSlug;
|
||||||
|
|
||||||
|
FutureOr<CursorPagingData<SnRealmMember>> build(String realmSlug);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See also [RealmMemberListNotifier].
|
||||||
|
@ProviderFor(RealmMemberListNotifier)
|
||||||
|
const realmMemberListNotifierProvider = RealmMemberListNotifierFamily();
|
||||||
|
|
||||||
|
/// See also [RealmMemberListNotifier].
|
||||||
|
class RealmMemberListNotifierFamily
|
||||||
|
extends Family<AsyncValue<CursorPagingData<SnRealmMember>>> {
|
||||||
|
/// See also [RealmMemberListNotifier].
|
||||||
|
const RealmMemberListNotifierFamily();
|
||||||
|
|
||||||
|
/// See also [RealmMemberListNotifier].
|
||||||
|
RealmMemberListNotifierProvider call(String realmSlug) {
|
||||||
|
return RealmMemberListNotifierProvider(realmSlug);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
RealmMemberListNotifierProvider getProviderOverride(
|
||||||
|
covariant RealmMemberListNotifierProvider provider,
|
||||||
|
) {
|
||||||
|
return call(provider.realmSlug);
|
||||||
|
}
|
||||||
|
|
||||||
|
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'realmMemberListNotifierProvider';
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See also [RealmMemberListNotifier].
|
||||||
|
class RealmMemberListNotifierProvider
|
||||||
|
extends
|
||||||
|
AutoDisposeAsyncNotifierProviderImpl<
|
||||||
|
RealmMemberListNotifier,
|
||||||
|
CursorPagingData<SnRealmMember>
|
||||||
|
> {
|
||||||
|
/// See also [RealmMemberListNotifier].
|
||||||
|
RealmMemberListNotifierProvider(String realmSlug)
|
||||||
|
: this._internal(
|
||||||
|
() => RealmMemberListNotifier()..realmSlug = realmSlug,
|
||||||
|
from: realmMemberListNotifierProvider,
|
||||||
|
name: r'realmMemberListNotifierProvider',
|
||||||
|
debugGetCreateSourceHash:
|
||||||
|
const bool.fromEnvironment('dart.vm.product')
|
||||||
|
? null
|
||||||
|
: _$realmMemberListNotifierHash,
|
||||||
|
dependencies: RealmMemberListNotifierFamily._dependencies,
|
||||||
|
allTransitiveDependencies:
|
||||||
|
RealmMemberListNotifierFamily._allTransitiveDependencies,
|
||||||
|
realmSlug: realmSlug,
|
||||||
|
);
|
||||||
|
|
||||||
|
RealmMemberListNotifierProvider._internal(
|
||||||
|
super._createNotifier, {
|
||||||
|
required super.name,
|
||||||
|
required super.dependencies,
|
||||||
|
required super.allTransitiveDependencies,
|
||||||
|
required super.debugGetCreateSourceHash,
|
||||||
|
required super.from,
|
||||||
|
required this.realmSlug,
|
||||||
|
}) : super.internal();
|
||||||
|
|
||||||
|
final String realmSlug;
|
||||||
|
|
||||||
|
@override
|
||||||
|
FutureOr<CursorPagingData<SnRealmMember>> runNotifierBuild(
|
||||||
|
covariant RealmMemberListNotifier notifier,
|
||||||
|
) {
|
||||||
|
return notifier.build(realmSlug);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Override overrideWith(RealmMemberListNotifier Function() create) {
|
||||||
|
return ProviderOverride(
|
||||||
|
origin: this,
|
||||||
|
override: RealmMemberListNotifierProvider._internal(
|
||||||
|
() => create()..realmSlug = realmSlug,
|
||||||
|
from: from,
|
||||||
|
name: null,
|
||||||
|
dependencies: null,
|
||||||
|
allTransitiveDependencies: null,
|
||||||
|
debugGetCreateSourceHash: null,
|
||||||
|
realmSlug: realmSlug,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
AutoDisposeAsyncNotifierProviderElement<
|
||||||
|
RealmMemberListNotifier,
|
||||||
|
CursorPagingData<SnRealmMember>
|
||||||
|
>
|
||||||
|
createElement() {
|
||||||
|
return _RealmMemberListNotifierProviderElement(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return other is RealmMemberListNotifierProvider &&
|
||||||
|
other.realmSlug == realmSlug;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode {
|
||||||
|
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||||
|
hash = _SystemHash.combine(hash, realmSlug.hashCode);
|
||||||
|
|
||||||
|
return _SystemHash.finish(hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||||
|
// ignore: unused_element
|
||||||
|
mixin RealmMemberListNotifierRef
|
||||||
|
on AutoDisposeAsyncNotifierProviderRef<CursorPagingData<SnRealmMember>> {
|
||||||
|
/// The parameter `realmSlug` of this provider.
|
||||||
|
String get realmSlug;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _RealmMemberListNotifierProviderElement
|
||||||
|
extends
|
||||||
|
AutoDisposeAsyncNotifierProviderElement<
|
||||||
|
RealmMemberListNotifier,
|
||||||
|
CursorPagingData<SnRealmMember>
|
||||||
|
>
|
||||||
|
with RealmMemberListNotifierRef {
|
||||||
|
_RealmMemberListNotifierProviderElement(super.provider);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get realmSlug => (origin as RealmMemberListNotifierProvider).realmSlug;
|
||||||
|
}
|
||||||
|
|
||||||
// ignore_for_file: type=lint
|
// ignore_for_file: type=lint
|
||||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
||||||
|
@ -13,10 +13,13 @@ import 'package:island/pods/config.dart';
|
|||||||
import 'package:island/pods/network.dart';
|
import 'package:island/pods/network.dart';
|
||||||
import 'package:island/route.gr.dart';
|
import 'package:island/route.gr.dart';
|
||||||
import 'package:island/services/file.dart';
|
import 'package:island/services/file.dart';
|
||||||
|
import 'package:island/services/responsive.dart';
|
||||||
import 'package:island/widgets/alert.dart';
|
import 'package:island/widgets/alert.dart';
|
||||||
import 'package:island/widgets/app_scaffold.dart';
|
import 'package:island/widgets/app_scaffold.dart';
|
||||||
import 'package:island/widgets/content/cloud_files.dart';
|
import 'package:island/widgets/content/cloud_files.dart';
|
||||||
|
import 'package:island/widgets/content/sheet.dart';
|
||||||
import 'package:island/widgets/response.dart';
|
import 'package:island/widgets/response.dart';
|
||||||
|
import 'package:island/screens/tabs.dart';
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
@ -40,6 +43,7 @@ class RealmListScreen extends HookConsumerWidget {
|
|||||||
final realmInvites = ref.watch(realmInvitesProvider);
|
final realmInvites = ref.watch(realmInvitesProvider);
|
||||||
|
|
||||||
return AppScaffold(
|
return AppScaffold(
|
||||||
|
extendBody: false, // Prevent conflicts with tabs navigation
|
||||||
noBackground: false,
|
noBackground: false,
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: const Text('realms').tr(),
|
title: const Text('realms').tr(),
|
||||||
@ -82,6 +86,7 @@ class RealmListScreen extends HookConsumerWidget {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
floatingActionButtonLocation: TabbedFabLocation(context),
|
||||||
body: RefreshIndicator(
|
body: RefreshIndicator(
|
||||||
child: realms.when(
|
child: realms.when(
|
||||||
data:
|
data:
|
||||||
@ -89,9 +94,7 @@ class RealmListScreen extends HookConsumerWidget {
|
|||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: ListView.builder(
|
child: ListView.builder(
|
||||||
padding: EdgeInsets.only(
|
padding: getTabbedPadding(context),
|
||||||
bottom: MediaQuery.of(context).padding.bottom,
|
|
||||||
),
|
|
||||||
itemCount: value.length,
|
itemCount: value.length,
|
||||||
itemBuilder: (context, item) {
|
itemBuilder: (context, item) {
|
||||||
return ListTile(
|
return ListTile(
|
||||||
@ -397,97 +400,69 @@ class _RealmInviteSheet extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Container(
|
return SheetScaffold(
|
||||||
constraints: BoxConstraints(
|
titleText: 'invites'.tr(),
|
||||||
maxHeight: MediaQuery.of(context).size.height * 0.8,
|
actions: [
|
||||||
),
|
IconButton(
|
||||||
child: Column(
|
icon: const Icon(Symbols.refresh),
|
||||||
mainAxisSize: MainAxisSize.min,
|
style: IconButton.styleFrom(minimumSize: const Size(36, 36)),
|
||||||
children: [
|
onPressed: () {
|
||||||
Padding(
|
ref.invalidate(realmInvitesProvider);
|
||||||
padding: EdgeInsets.only(top: 16, left: 20, right: 16, bottom: 12),
|
},
|
||||||
child: Row(
|
),
|
||||||
children: [
|
],
|
||||||
Text(
|
child: invites.when(
|
||||||
'invites'.tr(),
|
data:
|
||||||
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
|
(items) =>
|
||||||
fontWeight: FontWeight.w600,
|
items.isEmpty
|
||||||
letterSpacing: -0.5,
|
? Center(
|
||||||
),
|
child:
|
||||||
),
|
Text(
|
||||||
const Spacer(),
|
'invitesEmpty',
|
||||||
IconButton(
|
textAlign: TextAlign.center,
|
||||||
icon: const Icon(Symbols.refresh),
|
).tr(),
|
||||||
style: IconButton.styleFrom(minimumSize: const Size(36, 36)),
|
)
|
||||||
onPressed: () {
|
: ListView.builder(
|
||||||
ref.invalidate(realmInvitesProvider);
|
shrinkWrap: true,
|
||||||
},
|
itemCount: items.length,
|
||||||
),
|
itemBuilder: (context, index) {
|
||||||
IconButton(
|
final invite = items[index];
|
||||||
icon: const Icon(Symbols.close),
|
return ListTile(
|
||||||
onPressed: () => Navigator.pop(context),
|
leading: ProfilePictureWidget(
|
||||||
style: IconButton.styleFrom(minimumSize: const Size(36, 36)),
|
fileId: invite.realm!.picture?.id,
|
||||||
),
|
fallbackIcon: Symbols.group,
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
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 ListTile(
|
|
||||||
leading: ProfilePictureWidget(
|
|
||||||
fileId: invite.realm!.picture?.id,
|
|
||||||
fallbackIcon: Symbols.group,
|
|
||||||
),
|
|
||||||
title: Text(invite.realm!.name),
|
|
||||||
subtitle:
|
|
||||||
Text(
|
|
||||||
invite.role >= 100
|
|
||||||
? 'permissionOwner'
|
|
||||||
: invite.role >= 50
|
|
||||||
? 'permissionModerator'
|
|
||||||
: 'permissionMember',
|
|
||||||
).tr(),
|
|
||||||
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()),
|
title: Text(invite.realm!.name),
|
||||||
error:
|
subtitle:
|
||||||
(error, _) => ResponseErrorWidget(
|
Text(
|
||||||
error: error,
|
invite.role >= 100
|
||||||
onRetry: () => ref.invalidate(realmInvitesProvider),
|
? 'permissionOwner'
|
||||||
),
|
: invite.role >= 50
|
||||||
|
? 'permissionModerator'
|
||||||
|
: 'permissionMember',
|
||||||
|
).tr(),
|
||||||
|
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, _) => ResponseErrorWidget(
|
||||||
|
error: error,
|
||||||
|
onRetry: () => ref.invalidate(realmInvitesProvider),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@ import 'package:island/services/responsive.dart';
|
|||||||
import 'package:island/widgets/alert.dart';
|
import 'package:island/widgets/alert.dart';
|
||||||
import 'package:island/widgets/app_scaffold.dart';
|
import 'package:island/widgets/app_scaffold.dart';
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
|
import 'package:palette_generator/palette_generator.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
import 'package:island/pods/config.dart';
|
import 'package:island/pods/config.dart';
|
||||||
@ -147,6 +148,7 @@ class SettingsScreen extends HookConsumerWidget {
|
|||||||
title: Text('settingsColorScheme').tr(),
|
title: Text('settingsColorScheme').tr(),
|
||||||
content: SingleChildScrollView(
|
content: SingleChildScrollView(
|
||||||
child: ColorPicker(
|
child: ColorPicker(
|
||||||
|
paletteType: PaletteType.rgbWithBlue,
|
||||||
enableAlpha: false,
|
enableAlpha: false,
|
||||||
pickerColor: selectedColor,
|
pickerColor: selectedColor,
|
||||||
onColorChanged: (color) {
|
onColorChanged: (color) {
|
||||||
@ -157,7 +159,7 @@ class SettingsScreen extends HookConsumerWidget {
|
|||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
child: Text('Cancel').tr(),
|
child: Text('cancel').tr(),
|
||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
@ -166,7 +168,7 @@ class SettingsScreen extends HookConsumerWidget {
|
|||||||
.setAppColorScheme(selectedColor.value);
|
.setAppColorScheme(selectedColor.value);
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
child: Text('Confirm').tr(),
|
child: Text('confirm').tr(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
@ -174,8 +176,9 @@ class SettingsScreen extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: Container(
|
child: Container(
|
||||||
width: 40,
|
width: 24,
|
||||||
height: 40,
|
height: 24,
|
||||||
|
margin: EdgeInsets.symmetric(horizontal: 2, vertical: 8),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color:
|
color:
|
||||||
settings.appColorScheme != null
|
settings.appColorScheme != null
|
||||||
@ -198,18 +201,7 @@ class SettingsScreen extends HookConsumerWidget {
|
|||||||
title: Text('settingsBackgroundImage').tr(),
|
title: Text('settingsBackgroundImage').tr(),
|
||||||
contentPadding: const EdgeInsets.only(left: 24, right: 17),
|
contentPadding: const EdgeInsets.only(left: 24, right: 17),
|
||||||
leading: const Icon(Symbols.image),
|
leading: const Icon(Symbols.image),
|
||||||
trailing: Row(
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
if (isDesktop)
|
|
||||||
Tooltip(
|
|
||||||
message: 'settingsBackgroundImageTooltip'.tr(),
|
|
||||||
padding: EdgeInsets.only(left: 8),
|
|
||||||
child: const Icon(Symbols.info, size: 18),
|
|
||||||
),
|
|
||||||
const Icon(Symbols.chevron_right),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
final imagePicker = ref.read(imagePickerProvider);
|
final imagePicker = ref.read(imagePickerProvider);
|
||||||
final image = await imagePicker.pickImage(
|
final image = await imagePicker.pickImage(
|
||||||
@ -241,7 +233,7 @@ class SettingsScreen extends HookConsumerWidget {
|
|||||||
return ListTile(
|
return ListTile(
|
||||||
minLeadingWidth: 48,
|
minLeadingWidth: 48,
|
||||||
title: Text('settingsBackgroundImageClear').tr(),
|
title: Text('settingsBackgroundImageClear').tr(),
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
contentPadding: const EdgeInsets.only(left: 24, right: 17),
|
||||||
leading: const Icon(Symbols.texture),
|
leading: const Icon(Symbols.texture),
|
||||||
trailing: const Icon(Symbols.chevron_right),
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
@ -257,6 +249,53 @@ class SettingsScreen extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
||||||
|
if (!kIsWeb && docBasepath.value != null)
|
||||||
|
FutureBuilder(
|
||||||
|
future:
|
||||||
|
File('${docBasepath.value}/$kAppBackgroundImagePath').exists(),
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (!snapshot.hasData || !snapshot.data!) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
|
||||||
|
return ListTile(
|
||||||
|
minLeadingWidth: 48,
|
||||||
|
title: Text('settingsBackgroundGenerateColor').tr(),
|
||||||
|
contentPadding: const EdgeInsets.only(left: 24, right: 17),
|
||||||
|
leading: const Icon(Symbols.format_color_fill),
|
||||||
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
|
onTap: () async {
|
||||||
|
showLoadingModal(context);
|
||||||
|
final palette = await PaletteGenerator.fromImageProvider(
|
||||||
|
FileImage(
|
||||||
|
File('${docBasepath.value}/$kAppBackgroundImagePath'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (palette.darkVibrantColor == null ||
|
||||||
|
palette.lightVibrantColor == null) {
|
||||||
|
if (context.mounted) hideLoadingModal(context);
|
||||||
|
showErrorAlert(
|
||||||
|
'Unable to calculate the domiant color of the background image.',
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!context.mounted) return;
|
||||||
|
final color =
|
||||||
|
MediaQuery.of(context).platformBrightness == Brightness.dark
|
||||||
|
? palette.darkVibrantColor!.color
|
||||||
|
: palette.lightVibrantColor!.color;
|
||||||
|
ref
|
||||||
|
.read(appSettingsNotifierProvider.notifier)
|
||||||
|
.setAppColorScheme(color.value);
|
||||||
|
if (context.mounted) {
|
||||||
|
hideLoadingModal(context);
|
||||||
|
showSnackBar(context, 'settingsApplied'.tr());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
final serverSettings = [
|
final serverSettings = [
|
||||||
@ -411,19 +450,24 @@ class SettingsScreen extends HookConsumerWidget {
|
|||||||
children: [
|
children: [
|
||||||
_ShortcutRow(
|
_ShortcutRow(
|
||||||
shortcut: 'Ctrl+F',
|
shortcut: 'Ctrl+F',
|
||||||
description: 'Search',
|
description:
|
||||||
|
'settingsKeyboardShortcutSearch'.tr(),
|
||||||
),
|
),
|
||||||
_ShortcutRow(
|
_ShortcutRow(
|
||||||
shortcut: 'Ctrl+,',
|
shortcut: 'Ctrl+,',
|
||||||
description: 'Settings',
|
description:
|
||||||
|
'settingsKeyboardShortcutSettings'.tr(),
|
||||||
),
|
),
|
||||||
_ShortcutRow(
|
_ShortcutRow(
|
||||||
shortcut: 'Ctrl+N',
|
shortcut: 'Ctrl+N',
|
||||||
description: 'New Message',
|
description:
|
||||||
|
'settingsKeyboardShortcutNewMessage'.tr(),
|
||||||
),
|
),
|
||||||
_ShortcutRow(
|
_ShortcutRow(
|
||||||
shortcut: 'Esc',
|
shortcut: 'Esc',
|
||||||
description: 'Close Dialog',
|
description:
|
||||||
|
'settingsKeyboardShortcutCloseDialog'
|
||||||
|
.tr(),
|
||||||
),
|
),
|
||||||
// Add more shortcuts as needed
|
// Add more shortcuts as needed
|
||||||
],
|
],
|
||||||
@ -432,7 +476,7 @@ class SettingsScreen extends HookConsumerWidget {
|
|||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
child: Text('Close').tr(),
|
child: Text('close').tr(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -454,10 +498,13 @@ class SettingsScreen extends HookConsumerWidget {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
_SettingsSection(
|
_SettingsSection(
|
||||||
title: 'Appearance',
|
title: 'settingsAppearance'.tr(),
|
||||||
children: appearanceSettings,
|
children: appearanceSettings,
|
||||||
),
|
),
|
||||||
_SettingsSection(title: 'Server', children: serverSettings),
|
_SettingsSection(
|
||||||
|
title: 'settingsServer'.tr(),
|
||||||
|
children: serverSettings,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -466,12 +513,12 @@ class SettingsScreen extends HookConsumerWidget {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
_SettingsSection(
|
_SettingsSection(
|
||||||
title: 'Behavior',
|
title: 'settingsBehavior'.tr(),
|
||||||
children: behaviorSettings,
|
children: behaviorSettings,
|
||||||
),
|
),
|
||||||
if (desktopSettings.isNotEmpty)
|
if (desktopSettings.isNotEmpty)
|
||||||
_SettingsSection(
|
_SettingsSection(
|
||||||
title: 'Desktop',
|
title: 'settingsDesktop'.tr(),
|
||||||
children: desktopSettings,
|
children: desktopSettings,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -484,11 +531,23 @@ class SettingsScreen extends HookConsumerWidget {
|
|||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
_SettingsSection(title: 'Appearance', children: appearanceSettings),
|
_SettingsSection(
|
||||||
_SettingsSection(title: 'Server', children: serverSettings),
|
title: 'settingsAppearance'.tr(),
|
||||||
_SettingsSection(title: 'Behavior', children: behaviorSettings),
|
children: appearanceSettings,
|
||||||
|
),
|
||||||
|
_SettingsSection(
|
||||||
|
title: 'settingsServer'.tr(),
|
||||||
|
children: serverSettings,
|
||||||
|
),
|
||||||
|
_SettingsSection(
|
||||||
|
title: 'settingsBehavior'.tr(),
|
||||||
|
children: behaviorSettings,
|
||||||
|
),
|
||||||
if (desktopSettings.isNotEmpty)
|
if (desktopSettings.isNotEmpty)
|
||||||
_SettingsSection(title: 'Desktop', children: desktopSettings),
|
_SettingsSection(
|
||||||
|
title: 'settingsDesktop'.tr(),
|
||||||
|
children: desktopSettings,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -497,7 +556,7 @@ class SettingsScreen extends HookConsumerWidget {
|
|||||||
return AppScaffold(
|
return AppScaffold(
|
||||||
noBackground: false,
|
noBackground: false,
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text('Settings').tr(),
|
title: Text('settings').tr(),
|
||||||
actions:
|
actions:
|
||||||
isDesktop
|
isDesktop
|
||||||
? [
|
? [
|
||||||
@ -514,7 +573,7 @@ class SettingsScreen extends HookConsumerWidget {
|
|||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
child: Text('Close').tr(),
|
child: Text('close').tr(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
152
lib/screens/tabs.dart
Normal file
152
lib/screens/tabs.dart
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
import 'dart:ui';
|
||||||
|
import 'package:auto_route/auto_route.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:island/route.gr.dart';
|
||||||
|
import 'package:island/screens/notification.dart';
|
||||||
|
import 'package:island/services/responsive.dart';
|
||||||
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
|
|
||||||
|
@RoutePage()
|
||||||
|
class TabsScreen extends HookConsumerWidget {
|
||||||
|
const TabsScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final useHorizontalLayout = isWideScreen(context);
|
||||||
|
|
||||||
|
final notificationUnreadCount = ref.watch(
|
||||||
|
notificationUnreadCountNotifierProvider,
|
||||||
|
);
|
||||||
|
|
||||||
|
final destinations = [
|
||||||
|
NavigationDestination(
|
||||||
|
label: 'explore'.tr(),
|
||||||
|
icon: const Icon(Symbols.explore),
|
||||||
|
),
|
||||||
|
NavigationDestination(label: 'chat'.tr(), icon: const Icon(Symbols.chat)),
|
||||||
|
NavigationDestination(
|
||||||
|
label: 'realms'.tr(),
|
||||||
|
icon: const Icon(Symbols.workspaces),
|
||||||
|
),
|
||||||
|
NavigationDestination(
|
||||||
|
label: 'account'.tr(),
|
||||||
|
icon: Badge.count(
|
||||||
|
count: notificationUnreadCount.value ?? 0,
|
||||||
|
isLabelVisible: (notificationUnreadCount.value ?? 0) > 0,
|
||||||
|
child: const Icon(Symbols.account_circle),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
final routes = <PageRouteInfo>[
|
||||||
|
ExploreRoute(),
|
||||||
|
ChatListRoute(),
|
||||||
|
RealmListRoute(),
|
||||||
|
AccountRoute(),
|
||||||
|
];
|
||||||
|
|
||||||
|
return AutoTabsRouter.tabBar(
|
||||||
|
routes: routes,
|
||||||
|
scrollDirection: useHorizontalLayout ? Axis.vertical : Axis.horizontal,
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
builder: (context, child, _) {
|
||||||
|
final tabsRouter = AutoTabsRouter.of(context);
|
||||||
|
|
||||||
|
if (isWideScreen(context)) {
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
NavigationRail(
|
||||||
|
destinations:
|
||||||
|
destinations
|
||||||
|
.map(
|
||||||
|
(e) => NavigationRailDestination(
|
||||||
|
icon: e.icon,
|
||||||
|
label: Text(e.label),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
selectedIndex: tabsRouter.activeIndex,
|
||||||
|
onDestinationSelected: tabsRouter.setActiveIndex,
|
||||||
|
),
|
||||||
|
const VerticalDivider(width: 1),
|
||||||
|
Expanded(child: child),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Stack(
|
||||||
|
children: [
|
||||||
|
Positioned.fill(child: child),
|
||||||
|
Positioned(
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
child: ClipRRect(
|
||||||
|
child: BackdropFilter(
|
||||||
|
filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10),
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(
|
||||||
|
context,
|
||||||
|
).colorScheme.surface.withOpacity(0.8),
|
||||||
|
),
|
||||||
|
child: MediaQuery.removePadding(
|
||||||
|
context: context,
|
||||||
|
removeTop: true,
|
||||||
|
child: NavigationBar(
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
shadowColor: Colors.transparent,
|
||||||
|
overlayColor: WidgetStatePropertyAll(
|
||||||
|
Colors.transparent,
|
||||||
|
),
|
||||||
|
surfaceTintColor: Colors.transparent,
|
||||||
|
height: 56,
|
||||||
|
labelBehavior:
|
||||||
|
NavigationDestinationLabelBehavior.alwaysHide,
|
||||||
|
selectedIndex: tabsRouter.activeIndex,
|
||||||
|
onDestinationSelected: tabsRouter.setActiveIndex,
|
||||||
|
destinations: destinations,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TabbedFabLocation extends FloatingActionButtonLocation {
|
||||||
|
final BuildContext context;
|
||||||
|
|
||||||
|
const TabbedFabLocation(this.context);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Offset getOffset(ScaffoldPrelayoutGeometry scaffoldGeometry) {
|
||||||
|
final mediaQuery = MediaQuery.of(context);
|
||||||
|
final safeAreaPadding = mediaQuery.padding;
|
||||||
|
|
||||||
|
// Calculate position with proper safe area considerations
|
||||||
|
final double fabX =
|
||||||
|
scaffoldGeometry.scaffoldSize.width -
|
||||||
|
scaffoldGeometry.floatingActionButtonSize.width -
|
||||||
|
16.0 -
|
||||||
|
safeAreaPadding.right;
|
||||||
|
|
||||||
|
// Use safe area bottom padding + navigation bar height (typically 80px)
|
||||||
|
final double fabY =
|
||||||
|
scaffoldGeometry.scaffoldSize.height -
|
||||||
|
scaffoldGeometry.floatingActionButtonSize.height -
|
||||||
|
scaffoldGeometry.bottomSheetSize.height -
|
||||||
|
safeAreaPadding.bottom -
|
||||||
|
80.0 +
|
||||||
|
16;
|
||||||
|
|
||||||
|
return Offset(fabX, fabY);
|
||||||
|
}
|
||||||
|
}
|
7
lib/services/color.dart
Normal file
7
lib/services/color.dart
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
|
extension ColorInversion on Color {
|
||||||
|
Color get invert {
|
||||||
|
return Color.fromARGB(alpha, 255 - red, 255 - green, 255 - blue);
|
||||||
|
}
|
||||||
|
}
|
@ -8,6 +8,7 @@ import 'package:cross_file/cross_file.dart';
|
|||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:island/models/file.dart';
|
import 'package:island/models/file.dart';
|
||||||
|
import 'package:native_exif/native_exif.dart';
|
||||||
import 'package:tus_client_dart/tus_client_dart.dart';
|
import 'package:tus_client_dart/tus_client_dart.dart';
|
||||||
|
|
||||||
Future<XFile?> cropImage(
|
Future<XFile?> cropImage(
|
||||||
@ -46,7 +47,91 @@ Completer<SnCloudFile?> putMediaToCloud({
|
|||||||
String? mimetype,
|
String? mimetype,
|
||||||
Function(double progress, Duration estimate)? onProgress,
|
Function(double progress, Duration estimate)? onProgress,
|
||||||
}) {
|
}) {
|
||||||
XFile file;
|
final completer = Completer<SnCloudFile?>();
|
||||||
|
|
||||||
|
// Process the image to remove GPS EXIF data if needed
|
||||||
|
if (fileData.isOnDevice && fileData.type == UniversalFileType.image) {
|
||||||
|
final data = fileData.data;
|
||||||
|
if (data is XFile && !kIsWeb && (Platform.isIOS || Platform.isAndroid)) {
|
||||||
|
// Use native_exif to selectively remove GPS data
|
||||||
|
Exif.fromPath(data.path)
|
||||||
|
.then((exif) {
|
||||||
|
// Remove GPS-related attributes
|
||||||
|
final gpsAttributes = [
|
||||||
|
'GPSLatitude',
|
||||||
|
'GPSLatitudeRef',
|
||||||
|
'GPSLongitude',
|
||||||
|
'GPSLongitudeRef',
|
||||||
|
'GPSAltitude',
|
||||||
|
'GPSAltitudeRef',
|
||||||
|
'GPSTimeStamp',
|
||||||
|
'GPSProcessingMethod',
|
||||||
|
'GPSDateStamp',
|
||||||
|
];
|
||||||
|
|
||||||
|
// Create a map of attributes to clear
|
||||||
|
final clearAttributes = <String, String>{};
|
||||||
|
for (final attr in gpsAttributes) {
|
||||||
|
clearAttributes[attr] = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write empty values to remove GPS data
|
||||||
|
return exif.writeAttributes(clearAttributes);
|
||||||
|
})
|
||||||
|
.then((_) {
|
||||||
|
// Continue with upload after GPS data is removed
|
||||||
|
_processUpload(
|
||||||
|
fileData,
|
||||||
|
atk,
|
||||||
|
baseUrl,
|
||||||
|
filename,
|
||||||
|
mimetype,
|
||||||
|
onProgress,
|
||||||
|
completer,
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.catchError((e) {
|
||||||
|
// If there's an error, continue with the original file
|
||||||
|
debugPrint('Error removing GPS EXIF data: $e');
|
||||||
|
_processUpload(
|
||||||
|
fileData,
|
||||||
|
atk,
|
||||||
|
baseUrl,
|
||||||
|
filename,
|
||||||
|
mimetype,
|
||||||
|
onProgress,
|
||||||
|
completer,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return completer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If not an image or on web, continue with normal upload
|
||||||
|
_processUpload(
|
||||||
|
fileData,
|
||||||
|
atk,
|
||||||
|
baseUrl,
|
||||||
|
filename,
|
||||||
|
mimetype,
|
||||||
|
onProgress,
|
||||||
|
completer,
|
||||||
|
);
|
||||||
|
return completer;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper method to process the upload after any EXIF processing
|
||||||
|
Completer<SnCloudFile?> _processUpload(
|
||||||
|
UniversalFile fileData,
|
||||||
|
String atk,
|
||||||
|
String baseUrl,
|
||||||
|
String? filename,
|
||||||
|
String? mimetype,
|
||||||
|
Function(double progress, Duration estimate)? onProgress,
|
||||||
|
Completer<SnCloudFile?> completer,
|
||||||
|
) {
|
||||||
|
late XFile file;
|
||||||
String actualFilename = filename ?? 'randomly_file';
|
String actualFilename = filename ?? 'randomly_file';
|
||||||
String actualMimetype = mimetype ?? '';
|
String actualMimetype = mimetype ?? '';
|
||||||
Uint8List? byteData;
|
Uint8List? byteData;
|
||||||
@ -63,16 +148,23 @@ Completer<SnCloudFile?> putMediaToCloud({
|
|||||||
actualFilename = filename ?? 'uploaded_file';
|
actualFilename = filename ?? 'uploaded_file';
|
||||||
actualMimetype = mimetype ?? 'application/octet-stream';
|
actualMimetype = mimetype ?? 'application/octet-stream';
|
||||||
if (mimetype == null) {
|
if (mimetype == null) {
|
||||||
throw ArgumentError('Mimetype is required when providing raw bytes.');
|
completer.completeError(
|
||||||
|
ArgumentError('Mimetype is required when providing raw bytes.'),
|
||||||
|
);
|
||||||
|
return completer;
|
||||||
}
|
}
|
||||||
file = XFile.fromData(byteData!, mimeType: actualMimetype);
|
file = XFile.fromData(byteData!, mimeType: actualMimetype);
|
||||||
} else if (data is SnCloudFile) {
|
} else if (data is SnCloudFile) {
|
||||||
// If the file is already on the cloud, just return it
|
// If the file is already on the cloud, just return it
|
||||||
return Completer<SnCloudFile?>()..complete(data);
|
completer.complete(data);
|
||||||
|
return completer;
|
||||||
} else {
|
} else {
|
||||||
throw ArgumentError(
|
completer.completeError(
|
||||||
'Invalid fileData type. Expected data to be XFile, List<int>, Uint8List, or SnCloudFile.',
|
ArgumentError(
|
||||||
|
'Invalid fileData type. Expected data to be XFile, List<int>, Uint8List, or SnCloudFile.',
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
return completer;
|
||||||
}
|
}
|
||||||
|
|
||||||
final Map<String, String> metadata = {
|
final Map<String, String> metadata = {
|
||||||
@ -80,8 +172,6 @@ Completer<SnCloudFile?> putMediaToCloud({
|
|||||||
'content-type': actualMimetype,
|
'content-type': actualMimetype,
|
||||||
};
|
};
|
||||||
|
|
||||||
final completer = Completer<SnCloudFile?>();
|
|
||||||
|
|
||||||
final client = TusClient(file);
|
final client = TusClient(file);
|
||||||
client
|
client
|
||||||
.upload(
|
.upload(
|
||||||
|
@ -15,3 +15,32 @@ bool isWiderScreen(BuildContext context) {
|
|||||||
bool isWidestScreen(BuildContext context) {
|
bool isWidestScreen(BuildContext context) {
|
||||||
return MediaQuery.of(context).size.width > kWidescreenWidth;
|
return MediaQuery.of(context).size.width > kWidescreenWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EdgeInsets getTabbedPadding(
|
||||||
|
BuildContext context, {
|
||||||
|
double? horizontal,
|
||||||
|
double? vertical,
|
||||||
|
double? left,
|
||||||
|
double? right,
|
||||||
|
double? top,
|
||||||
|
double? bottom,
|
||||||
|
}) {
|
||||||
|
if (isWideScreen(context)) {
|
||||||
|
return EdgeInsets.only(
|
||||||
|
left: left ?? horizontal ?? 0,
|
||||||
|
right: right ?? horizontal ?? 0,
|
||||||
|
top: top ?? vertical ?? 0,
|
||||||
|
bottom: bottom ?? vertical ?? 0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
final effectiveBottom = bottom ?? vertical;
|
||||||
|
return EdgeInsets.only(
|
||||||
|
left: left ?? horizontal ?? 0,
|
||||||
|
right: right ?? horizontal ?? 0,
|
||||||
|
top: top ?? vertical ?? 0,
|
||||||
|
bottom:
|
||||||
|
effectiveBottom != null
|
||||||
|
? effectiveBottom + MediaQuery.of(context).padding.bottom + 56
|
||||||
|
: MediaQuery.of(context).padding.bottom + 56,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
14
lib/services/text.dart
Normal file
14
lib/services/text.dart
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
extension StringExtension on String {
|
||||||
|
String capitalizeEachWord() {
|
||||||
|
if (isEmpty) return this;
|
||||||
|
|
||||||
|
return split(' ')
|
||||||
|
.map(
|
||||||
|
(word) =>
|
||||||
|
word.isNotEmpty
|
||||||
|
? '${word[0].toUpperCase()}${word.substring(1).toLowerCase()}'
|
||||||
|
: '',
|
||||||
|
)
|
||||||
|
.join(' ');
|
||||||
|
}
|
||||||
|
}
|
76
lib/services/time.dart
Normal file
76
lib/services/time.dart
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:relative_time/relative_time.dart';
|
||||||
|
|
||||||
|
extension DurationFormatter on Duration {
|
||||||
|
String formatDuration() {
|
||||||
|
final isNegative = inMicroseconds < 0;
|
||||||
|
final positiveDuration = isNegative ? -this : this;
|
||||||
|
|
||||||
|
final hours = positiveDuration.inHours.toString().padLeft(2, '0');
|
||||||
|
final minutes = (positiveDuration.inMinutes % 60).toString().padLeft(
|
||||||
|
2,
|
||||||
|
'0',
|
||||||
|
);
|
||||||
|
final seconds = (positiveDuration.inSeconds % 60).toString().padLeft(
|
||||||
|
2,
|
||||||
|
'0',
|
||||||
|
);
|
||||||
|
|
||||||
|
return '${isNegative ? '-' : ''}$hours:$minutes:$seconds';
|
||||||
|
}
|
||||||
|
|
||||||
|
String formatOffset() {
|
||||||
|
final isNegative = inMicroseconds < 0;
|
||||||
|
final positiveDuration = isNegative ? -this : this;
|
||||||
|
|
||||||
|
final hours = positiveDuration.inHours.toString().padLeft(2, '0');
|
||||||
|
final minutes = (positiveDuration.inMinutes % 60).toString().padLeft(
|
||||||
|
2,
|
||||||
|
'0',
|
||||||
|
);
|
||||||
|
|
||||||
|
return '${isNegative ? '-' : '+'}$hours:$minutes';
|
||||||
|
}
|
||||||
|
|
||||||
|
String formatOffsetLocal() {
|
||||||
|
// Get the local timezone offset
|
||||||
|
final localOffset = DateTime.now().timeZoneOffset;
|
||||||
|
|
||||||
|
// Add the local offset to the input duration
|
||||||
|
final totalOffset = this - localOffset;
|
||||||
|
|
||||||
|
final isNegative = totalOffset.inMicroseconds < 0;
|
||||||
|
final positiveDuration = isNegative ? -totalOffset : totalOffset;
|
||||||
|
|
||||||
|
final hours = positiveDuration.inHours.toString().padLeft(2, '0');
|
||||||
|
final minutes = (positiveDuration.inMinutes % 60).toString().padLeft(
|
||||||
|
2,
|
||||||
|
'0',
|
||||||
|
);
|
||||||
|
|
||||||
|
return '${isNegative ? '-' : '+'}$hours:$minutes';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension DateTimeFormatter on DateTime {
|
||||||
|
String formatSystem() {
|
||||||
|
return DateFormat.yMd().add_jm().format(toLocal());
|
||||||
|
}
|
||||||
|
|
||||||
|
String formatCustom(String pattern) {
|
||||||
|
return DateFormat(pattern).format(toLocal());
|
||||||
|
}
|
||||||
|
|
||||||
|
String formatCustomGlobal(String pattern) {
|
||||||
|
return DateFormat(pattern).format(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
String formatWithLocale(String locale) {
|
||||||
|
return DateFormat.yMd().add_jm().format(toLocal()).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
String formatRelative(BuildContext context) {
|
||||||
|
return RelativeTime(context).format(toLocal());
|
||||||
|
}
|
||||||
|
}
|
1
lib/services/timezone.dart
Normal file
1
lib/services/timezone.dart
Normal file
@ -0,0 +1 @@
|
|||||||
|
export 'timezone/native.dart' if (dart.library.html) 'timezone/web.dart';
|
22
lib/services/timezone/native.dart
Normal file
22
lib/services/timezone/native.dart
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import 'package:flutter_timezone/flutter_timezone.dart';
|
||||||
|
import 'package:timezone/standalone.dart' as tz;
|
||||||
|
import 'package:timezone/data/latest_all.dart' as tzdb;
|
||||||
|
|
||||||
|
Future<void> initializeTzdb() async {
|
||||||
|
tzdb.initializeTimeZones();
|
||||||
|
}
|
||||||
|
|
||||||
|
(Duration offset, DateTime now) getTzInfo(String name) {
|
||||||
|
final location = tz.getLocation(name);
|
||||||
|
final now = tz.TZDateTime.now(location);
|
||||||
|
final offset = now.timeZoneOffset;
|
||||||
|
return (offset, now);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String> getMachineTz() async {
|
||||||
|
return await FlutterTimezone.getLocalTimezone();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> getAvailableTz() {
|
||||||
|
return tz.timeZoneDatabase.locations.keys.toList();
|
||||||
|
}
|
21
lib/services/timezone/web.dart
Normal file
21
lib/services/timezone/web.dart
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import 'package:flutter_timezone/flutter_timezone.dart';
|
||||||
|
import 'package:timezone/browser.dart' as tz;
|
||||||
|
|
||||||
|
Future<void> initializeTzdb() async {
|
||||||
|
await tz.initializeTimeZone();
|
||||||
|
}
|
||||||
|
|
||||||
|
(Duration offset, DateTime now) getTzInfo(String name) {
|
||||||
|
final location = tz.getLocation(name);
|
||||||
|
final now = tz.TZDateTime.now(location);
|
||||||
|
final offset = now.timeZoneOffset;
|
||||||
|
return (offset, now);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String> getMachineTz() async {
|
||||||
|
return await FlutterTimezone.getLocalTimezone();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> getAvailableTz() {
|
||||||
|
return tz.timeZoneDatabase.locations.keys.toList();
|
||||||
|
}
|
99
lib/widgets/account/account_name.dart
Normal file
99
lib/widgets/account/account_name.dart
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:gap/gap.dart';
|
||||||
|
import 'package:island/models/user.dart';
|
||||||
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
|
||||||
|
const kVerificationMarkColors = [
|
||||||
|
Colors.teal,
|
||||||
|
Colors.blue,
|
||||||
|
Colors.amber,
|
||||||
|
Colors.blueGrey,
|
||||||
|
Colors.lightBlue,
|
||||||
|
];
|
||||||
|
|
||||||
|
class AccountName extends StatelessWidget {
|
||||||
|
final SnAccount account;
|
||||||
|
final TextStyle? style;
|
||||||
|
const AccountName({super.key, required this.account, this.style});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
spacing: 4,
|
||||||
|
children: [
|
||||||
|
Flexible(child: Text(account.nick, style: style)),
|
||||||
|
if (account.profile.verification != null)
|
||||||
|
VerificationMark(mark: account.profile.verification!),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class VerificationMark extends StatelessWidget {
|
||||||
|
final SnVerificationMark mark;
|
||||||
|
const VerificationMark({super.key, required this.mark});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Tooltip(
|
||||||
|
richMessage: TextSpan(
|
||||||
|
text: mark.title ?? 'No title',
|
||||||
|
children: [
|
||||||
|
TextSpan(text: '\n'),
|
||||||
|
TextSpan(
|
||||||
|
text: mark.description ?? 'descriptionNone'.tr(),
|
||||||
|
style: TextStyle(fontWeight: FontWeight.normal),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
style: TextStyle(fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
child: Icon(
|
||||||
|
mark.type == 4
|
||||||
|
? Symbols.play_circle
|
||||||
|
: mark.type == 0
|
||||||
|
? Symbols.build_circle
|
||||||
|
: Symbols.verified,
|
||||||
|
size: 16,
|
||||||
|
color: kVerificationMarkColors[mark.type],
|
||||||
|
fill: 1,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class VerificationStatusCard extends StatelessWidget {
|
||||||
|
final SnVerificationMark mark;
|
||||||
|
const VerificationStatusCard({super.key, required this.mark});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Card(
|
||||||
|
margin: EdgeInsets.zero,
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
mark.type == 4
|
||||||
|
? Symbols.play_circle
|
||||||
|
: mark.type == 0
|
||||||
|
? Symbols.build_circle
|
||||||
|
: Symbols.verified,
|
||||||
|
size: 32,
|
||||||
|
color: kVerificationMarkColors[mark.type],
|
||||||
|
fill: 1,
|
||||||
|
),
|
||||||
|
const Gap(8),
|
||||||
|
Text(mark.title ?? 'No title').bold(),
|
||||||
|
Text(mark.description ?? 'descriptionNone'.tr()),
|
||||||
|
const Gap(6),
|
||||||
|
Text(
|
||||||
|
'Verified by\n${mark.verifiedBy ?? 'No one verified it'}',
|
||||||
|
).fontSize(11).opacity(0.8),
|
||||||
|
],
|
||||||
|
).padding(horizontal: 24, vertical: 16),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
218
lib/widgets/account/account_nameplate.dart
Normal file
218
lib/widgets/account/account_nameplate.dart
Normal file
@ -0,0 +1,218 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:island/widgets/account/account_name.dart';
|
||||||
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:island/screens/account/profile.dart';
|
||||||
|
import 'package:island/widgets/content/cloud_files.dart';
|
||||||
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
|
||||||
|
class AccountNameplate extends HookConsumerWidget {
|
||||||
|
final String name;
|
||||||
|
final bool isOutlined;
|
||||||
|
final EdgeInsetsGeometry padding;
|
||||||
|
|
||||||
|
const AccountNameplate({
|
||||||
|
super.key,
|
||||||
|
required this.name,
|
||||||
|
this.isOutlined = true,
|
||||||
|
this.padding = const EdgeInsets.all(16),
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final user = ref.watch(accountProvider(name));
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
decoration:
|
||||||
|
isOutlined
|
||||||
|
? BoxDecoration(
|
||||||
|
border: Border.all(
|
||||||
|
width: 1 / MediaQuery.of(context).devicePixelRatio,
|
||||||
|
color: Theme.of(context).dividerColor,
|
||||||
|
),
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
margin: padding,
|
||||||
|
child: Card(
|
||||||
|
margin: EdgeInsets.zero,
|
||||||
|
elevation: 0,
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: user.when(
|
||||||
|
data:
|
||||||
|
(account) =>
|
||||||
|
account.profile.background != null
|
||||||
|
? AspectRatio(
|
||||||
|
aspectRatio: 16 / 9,
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
// Background image
|
||||||
|
Positioned.fill(
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
child: CloudFileWidget(
|
||||||
|
item: account.profile.background!,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Gradient overlay for text readability
|
||||||
|
Positioned.fill(
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
gradient: LinearGradient(
|
||||||
|
begin: Alignment.bottomCenter,
|
||||||
|
end: Alignment.topCenter,
|
||||||
|
colors: [
|
||||||
|
Colors.black.withOpacity(0.8),
|
||||||
|
Colors.black.withOpacity(0.1),
|
||||||
|
Colors.transparent,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Content positioned at the bottom
|
||||||
|
Positioned(
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 16.0,
|
||||||
|
vertical: 8.0,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
// Profile picture (equivalent to leading)
|
||||||
|
ProfilePictureWidget(
|
||||||
|
fileId: account.profile.picture?.id,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
// Text content (equivalent to title and subtitle)
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment:
|
||||||
|
CrossAxisAlignment.start,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
AccountName(
|
||||||
|
account: account,
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
'@${account.name}',
|
||||||
|
).textColor(Colors.white70),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 16.0,
|
||||||
|
vertical: 8.0,
|
||||||
|
),
|
||||||
|
decoration:
|
||||||
|
isOutlined
|
||||||
|
? BoxDecoration(
|
||||||
|
border: Border.all(
|
||||||
|
color:
|
||||||
|
Theme.of(context).colorScheme.outline,
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
// Profile picture (equivalent to leading)
|
||||||
|
ProfilePictureWidget(
|
||||||
|
fileId: account.profile.picture?.id,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
// Text content (equivalent to title and subtitle)
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
AccountName(
|
||||||
|
account: account,
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text('@${account.name}'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
loading:
|
||||||
|
() => Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 16.0,
|
||||||
|
vertical: 8.0,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
// Loading indicator (equivalent to leading)
|
||||||
|
const CircularProgressIndicator(),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
// Loading text content (equivalent to title and subtitle)
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const Text('loading').bold().tr(),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
const Text('...'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
error:
|
||||||
|
(error, stackTrace) => Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 16.0,
|
||||||
|
vertical: 8.0,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
// Error icon (equivalent to leading)
|
||||||
|
const Icon(Symbols.error),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
// Error text content (equivalent to title and subtitle)
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text('somethingWentWrong').tr().bold(),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(error.toString()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
169
lib/widgets/account/account_pfc.dart
Normal file
169
lib/widgets/account/account_pfc.dart
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
import 'dart:math' as math;
|
||||||
|
|
||||||
|
import 'package:auto_route/auto_route.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_popup_card/flutter_popup_card.dart';
|
||||||
|
import 'package:gap/gap.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:island/screens/account/profile.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/leveling_progress.dart';
|
||||||
|
import 'package:island/widgets/account/status.dart';
|
||||||
|
import 'package:island/widgets/content/cloud_files.dart';
|
||||||
|
import 'package:island/widgets/response.dart';
|
||||||
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
|
||||||
|
class AccountProfileCard extends HookConsumerWidget {
|
||||||
|
final String uname;
|
||||||
|
const AccountProfileCard({super.key, required this.uname});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final account = ref.watch(accountProvider(uname));
|
||||||
|
final width =
|
||||||
|
math.max(MediaQuery.of(context).size.width - 80, 360).toDouble();
|
||||||
|
return PopupCard(
|
||||||
|
elevation: 8,
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12.0)),
|
||||||
|
child: SizedBox(
|
||||||
|
width: width,
|
||||||
|
child: account.when(
|
||||||
|
data:
|
||||||
|
(data) => Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
if (data.profile.background != null)
|
||||||
|
ClipRRect(
|
||||||
|
borderRadius: const BorderRadius.vertical(
|
||||||
|
top: Radius.circular(12),
|
||||||
|
),
|
||||||
|
child: AspectRatio(
|
||||||
|
aspectRatio: 16 / 9,
|
||||||
|
child: CloudImageWidget(file: data.profile.background),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
ProfilePictureWidget(file: data.profile.picture),
|
||||||
|
const Gap(12),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
AccountName(
|
||||||
|
account: data,
|
||||||
|
style: TextStyle(fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
Text('@${data.name}').fontSize(12),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const Gap(12),
|
||||||
|
AccountStatusWidget(
|
||||||
|
uname: data.name,
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
),
|
||||||
|
if (data.profile.timeZone.isNotEmpty)
|
||||||
|
Row(
|
||||||
|
spacing: 6,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Symbols.alarm,
|
||||||
|
size: 17,
|
||||||
|
fill: 1,
|
||||||
|
).padding(right: 2),
|
||||||
|
Text(
|
||||||
|
getTzInfo(
|
||||||
|
data.profile.timeZone,
|
||||||
|
).$2.formatCustomGlobal('HH:mm'),
|
||||||
|
).fontSize(12),
|
||||||
|
Text(
|
||||||
|
getTzInfo(
|
||||||
|
data.profile.timeZone,
|
||||||
|
).$1.formatOffsetLocal(),
|
||||||
|
).fontSize(12),
|
||||||
|
],
|
||||||
|
).padding(top: 2),
|
||||||
|
if (data.badges.isNotEmpty)
|
||||||
|
BadgeList(badges: data.badges).padding(top: 12),
|
||||||
|
LevelingProgressCard(
|
||||||
|
level: data.profile.level,
|
||||||
|
experience: data.profile.experience,
|
||||||
|
progress: data.profile.levelingProgress,
|
||||||
|
).padding(top: 12),
|
||||||
|
FilledButton.tonalIcon(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
context.router.pushPath('/account/${data.name}');
|
||||||
|
},
|
||||||
|
icon: const Icon(Symbols.launch),
|
||||||
|
label: Text('accountProfileView').tr(),
|
||||||
|
).padding(top: 12, horizontal: 2),
|
||||||
|
],
|
||||||
|
).padding(horizontal: 24, vertical: 16),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
error:
|
||||||
|
(err, _) => ResponseErrorWidget(
|
||||||
|
error: err,
|
||||||
|
onRetry: () => ref.invalidate(accountProvider(uname)),
|
||||||
|
),
|
||||||
|
loading:
|
||||||
|
() => SizedBox(
|
||||||
|
width: width,
|
||||||
|
height: width,
|
||||||
|
child:
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(24),
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
).center(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AccountPfcGestureDetector extends StatelessWidget {
|
||||||
|
final String uname;
|
||||||
|
final Widget child;
|
||||||
|
const AccountPfcGestureDetector({
|
||||||
|
super.key,
|
||||||
|
required this.uname,
|
||||||
|
required this.child,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return GestureDetector(
|
||||||
|
child: child,
|
||||||
|
onTapDown: (details) {
|
||||||
|
showAccountProfileCard(context, uname, offset: details.localPosition);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> showAccountProfileCard(
|
||||||
|
BuildContext context,
|
||||||
|
String uname, {
|
||||||
|
Offset? offset,
|
||||||
|
}) async {
|
||||||
|
await showPopupCard<void>(
|
||||||
|
offset: offset ?? Offset.zero,
|
||||||
|
context: context,
|
||||||
|
builder: (context) => AccountProfileCard(uname: uname),
|
||||||
|
dimBackground: true,
|
||||||
|
);
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user