Compare commits

...

32 Commits

Author SHA1 Message Date
0bbfa6ddde 🚀 Launch 3.0.0+104 2025-06-09 01:10:43 +08:00
1f46f89056 👽 Support new activity API 2025-06-09 01:06:52 +08:00
7740cf7830 🐛 Fixes members' loading bugs 2025-06-08 22:33:47 +08:00
d165e488ad 💄 Optimized image loading and error 2025-06-08 20:31:32 +08:00
37f5c61905 🐛 Fixes on video caching policy 2025-06-08 20:22:46 +08:00
723e17ff47 Account contact settings 2025-06-08 20:02:57 +08:00
20e6cc4283 Typing indicator 2025-06-08 01:16:48 +08:00
b2a118bbd0 🐛 Fixes on enter to send 2025-06-07 23:31:02 +08:00
3dd7c8a5b2 Account settings auth devices 2025-06-07 22:56:25 +08:00
d832729278 💄 Optimized the auth experience 2025-06-07 12:14:57 +08:00
dff8532229 💄 Better otp input 2025-06-07 02:50:39 +08:00
bf77bfce64 Auth factor settings 2025-06-06 01:21:22 +08:00
accb99bb24 Merge pull request #22 from Texas0295/v3 2025-06-05 18:54:03 +08:00
e67429e513 [FIX] fix: 🐛 fix workflow file icon location error 2025-06-05 13:16:15 +08:00
a87f5d61e6 [FIX] fix: 🐛 fix appimage build error 2025-06-05 12:51:20 +08:00
b84fafb53c 🚀 Launch 3.0.0+103 2025-06-04 02:03:33 +08:00
9e9d8b6fab 🐛 Bug fixes, and fixes 2025-06-04 01:59:17 +08:00
088cb4d5a2 🚀 Launch 3.0.0+102 2025-06-02 22:52:19 +08:00
33e84805d7 :drunk: I have no idea what did I did 2025-06-02 22:51:52 +08:00
9aca6eb674 Post show replies / forwareded 2025-06-02 02:43:57 +08:00
e431a54a89 🐛 Fix NSE, get token from flutter side in native iOS 2025-06-02 02:23:59 +08:00
c00987dfdc 🚀 Launch 3.0.0+100 2025-06-02 01:08:13 +08:00
4187ceb248 👽 Support new attachment reference system 2025-06-02 01:08:04 +08:00
152e076d44 🐛 Trying to fix NSE 2025-06-01 11:36:50 +08:00
7f36c86c55 New NSE and hold notification to reply 2025-06-01 03:29:37 +08:00
311420e1f7 🚀 Launch 3.0.0+97 w/ NSE 2025-05-31 19:16:47 +08:00
afe1c700eb More customize options are back 2025-05-30 01:23:24 +08:00
b0c1981c9a 🐛 Fix android building 2025-05-29 21:59:27 +08:00
d943275ed5 🔨 Upgrade android gradle project 2025-05-29 21:40:34 +08:00
010a49251c 🚀 Launch 3.0.0+96 2025-05-29 01:45:51 +08:00
bdc13978c3 👽 Support the new Dyson Token 2025-05-28 23:21:13 +08:00
5d8c73e468 🐛 Fix replies activities didn't rendered 2025-05-28 23:08:53 +08:00
129 changed files with 7221 additions and 2432 deletions

83
.github/workflows/build.yml vendored Normal file
View File

@ -0,0 +1,83 @@
name: Build Release
on:
push:
tags:
- '*'
workflow_dispatch:
jobs:
build-web:
runs-on: ubuntu-latest
steps:
- name: Clone repository
uses: actions/checkout@v4
- name: Set up Flutter
uses: subosito/flutter-action@v2
with:
channel: stable
cache: true
- run: flutter pub get
- run: flutter build web --release
- name: Archive production artifacts
uses: actions/upload-artifact@v4
with:
name: build-output-web
path: build/web
build-exe:
runs-on: windows-latest
steps:
- name: Clone repository
uses: actions/checkout@v4
- name: Set up Flutter
uses: subosito/flutter-action@v2
with:
channel: stable
cache: true
- run: flutter pub get
- run: flutter build windows
- name: Archive production artifacts
uses: actions/upload-artifact@v4
with:
name: build-output-windows
path: build/windows/x64/runner/Release
build-linux:
runs-on: ubuntu-latest
steps:
- name: Clone repository
uses: actions/checkout@v4
- name: Set up Flutter
uses: subosito/flutter-action@v2
with:
channel: stable
- run: |
sudo apt-get update -y
sudo apt-get install -y ninja-build libgtk-3-dev
sudo apt-get install -y libmpv-dev mpv
sudo apt-get install -y libayatana-appindicator3-dev
sudo apt-get install -y keybinder-3.0
sudo apt-get install -y libnotify-dev
sudo apt-get install -y libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev
sudo apt-get install -y gstreamer-1.0
- run: flutter pub get
- run: flutter build linux
- name: Archive production artifacts
uses: actions/upload-artifact@v4
with:
name: build-output-linux
path: build/linux/x64/release/bundle
- name: Build AppImage
run: |
rm -r Solian.AppDir | true
mkdir Solian.AppDir
cp -r build/linux/x64/release/bundle/* Solian.AppDir
cp -r buildtools/appimage_config/* Solian.AppDir
cp assets/icons/icon-padded.png Solian.AppDir
sudo chmod +x buildtools/appimagetool-x86_64.AppImage
sudo chmod +x Solian.AppDir/AppRun
./buildtools/appimagetool-x86_64.AppImage Solian.AppDir
- name: Archive production artifacts
uses: actions/upload-artifact@v4
with:
name: build-output-linux-appimage
path: './*.AppImage*'

1
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1 @@
{}

View File

@ -11,7 +11,7 @@ plugins {
android {
namespace = "dev.solsynth.solian"
compileSdk = flutter.compileSdkVersion
ndkVersion = "27.0.12077973"
ndkVersion = "29.0.13113456"
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17

View File

@ -34,19 +34,22 @@
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<provider
android:name="com.superlist.super_native_extensions.DataProvider"
android:authorities="dev.solsynth.solian.SuperClipboardDataProvider"
android:exported="true"
android:grantUriPermissions="true" >
android:name="androidx.core.content.FileProvider"
android:authorities="dev.solsynth.solian.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
</provider>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
@ -61,8 +64,8 @@
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT"/>
<data android:mimeType="text/plain"/>
<action android:name="android.intent.action.PROCESS_TEXT" />
<data android:mimeType="text/plain" />
</intent>
</queries>
</manifest>
</manifest>

View File

@ -1,5 +0,0 @@
package dev.solsynth.solian
import io.flutter.embedding.android.FlutterActivity
class MainActivity : FlutterActivity()

View File

@ -0,0 +1,14 @@
package dev.solsynth.solian
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugins.sharedpreferences.LegacySharedPreferencesPlugin
class MainActivity : FlutterActivity()
{
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
// https://github.com/flutter/flutter/issues/153075#issuecomment-2693189362
flutterEngine.plugins.add(LegacySharedPreferencesPlugin())
}
}

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path
name="external_files"
path="." />
</paths>

File diff suppressed because one or more lines are too long

View File

@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-all.zip

View File

@ -18,7 +18,7 @@ pluginManagement {
plugins {
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
id("com.android.application") version "8.7.0" apply false
id("com.android.application") version "8.10.1" apply false
// START: FlutterFire Configuration
id("com.google.gms.google-services") version("4.3.15") apply false
// END: FlutterFire Configuration

View File

@ -99,6 +99,16 @@
"permissionMember": "Member",
"reply": "Reply",
"forward": "Forward",
"repliedTo": "Replied to",
"forwarded": "Forwarded",
"hasAttachments": {
"one": "{} attachment",
"other": "{} attachments"
},
"postHasAttachments": {
"one": "{} attachment",
"other": "{} attachments"
},
"edited": "Edited",
"addVideo": "Add video",
"addPhoto": "Add photo",
@ -155,7 +165,7 @@
"status": "Status",
"statusActivityTitle": "{} is {} {}",
"statusActivityEndedTitle": "{} is {} {} until {}",
"appSettings": "App settings",
"appSettings": "App Settings",
"accountSettings": "Account Settings",
"settings": "Settings",
"language": "Language",
@ -279,6 +289,10 @@
"settingsSoundEffects": "Sound Effects",
"settingsAprilFoolFeatures": "April Fool Features",
"settingsEnterToSend": "Enter to Send",
"settingsTransparentAppBar": "Transparent App Bar",
"settingsCustomFonts": "Custom Fonts",
"settingsCustomFontsHint": "Custom fonts will be used for all text in the app. Make sure it is installed on your device.",
"settingsColorScheme": "Color Scheme",
"postTitle": "Title",
"postDescription": "Description",
"call": "Call",
@ -288,25 +302,99 @@
"accountDeletionHint": "Are you sure to delete your account? If you confirmed, we will send an confirmation email to your primary email address, you can continue the deletion process by follow the insturctions in the email.",
"accountDeletionSent": "Account deletion confirmation email sent, please check your email inbox.",
"accountSecurityTitle": "Security",
"accountPrivacyTitle": "Privacy",
"accountDangerZoneTitle": "Danger Zone",
"accountPassword": "Password",
"accountPasswordDescription": "Change your account password",
"accountPasswordChange": "Change Password",
"accountPasswordChangeSent": "Password reset link sent, please check your email inbox.",
"accountPasswordChangeDescription": "We will send an email to your primary email address to reset your password.",
"accountTwoFactor": "Two-Factor Authentication",
"accountTwoFactorDescription": "Add an extra layer of security to your account",
"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.",
"accountAuthFactor": "Auth factors",
"accountAuthFactorDescription": "Multi-factor authentication to ensure safety and convience",
"accountDeletionDescription": "Permanently delete your account and all your data",
"accountSettingsHelp": "Account Settings Help",
"accountSettingsHelpContent": "This page allows you to manage your account security, privacy, and other settings. If you need assistance, please contact support."
"accountSettingsHelpContent": "This page allows you to manage your account security, privacy, and other settings. If you need assistance, please contact support.",
"unauthorized": "Unauthorized",
"unauthorizedHint": "You're not signed in or session expired, please sign in again.",
"publisherVisitAccountPage": "Visit the profile of {}",
"postVisibility": "Visibility",
"postVisibilityPublic": "Public",
"postVisibilityFriends": "Friends Only",
"postVisibilityUnlisted": "Unlisted",
"postVisibilityPrivate": "Private",
"copyMessage": "Copy Message",
"authFactor": "Authentication Factor",
"authFactorDelete": "Delete the Factor",
"authFactorDeleteHint": "Are you sure you want to delete this authentication factor? This action cannot be undone.",
"authFactorDisable": "Disable the Factor",
"authFactorDisableHint": "Are you sure you want to disable this authentication factor? You can enable it again later.",
"authFactorEnable": "Enable the Factor",
"authFactorEnableHint": "Please enter the code that generated by the factor to enable it.",
"authFactorNew": "Create Auth Factor",
"authFactorSecret": "Secret",
"authFactorSecretHint": "Create an secret for this factor.",
"authFactorQrCodeScan": "Scan this QR code with your authenticator app to set up TOTP authentication",
"authFactorNoQrCode": "No QR code available for this authentication factor",
"cancel": "Cancel",
"confirm": "Confirm",
"authFactorAdditional": "One more step",
"authFactorHint": "Contact method hint",
"authFactorHintHelper": "You need provide a part of your contact method and we will send the verification code to that contact method if it matched our records",
"authSessions": "Active Sessions",
"authSessionsDescription": "See devices you currently logged in.",
"authSessionsCount": {
"one": "{} session",
"other": "{} sessions"
},
"authDeviceCurrent": "Current device",
"lastActiveAt": "Last active at {}",
"authDeviceLogout": "Logout",
"authDeviceLogoutHint": "Are you sure you want to logout this device? This will also disable the push notification to this device.",
"authDeviceEditLabel": "Edit Label",
"authDeviceLabelTitle": "Edit Device Label",
"authDeviceLabelHint": "Enter a name for this device",
"authDeviceSwipeEditHint": "Swipe left to edit label",
"authDeviceSwipeLogoutHint": "Swipe right to logout device",
"typingHint": {
"one": "{} is typing...",
"other": "{} are typing..."
},
"settingsAppearance": "Appearance",
"settingsServer": "Server",
"settingsBehavior": "Behavior",
"settingsDesktop": "Desktop",
"settingsKeyboardShortcuts": "Keyboard Shortcuts",
"settingsEnterToSendDesktopHint": "Press Enter to send messages, use Shift+Enter for new line.",
"settingsHelp": "Settings Help",
"settingsHelpContent": "This page allows you to manage your app settings, appearance, and behavior. If you need assistance, please contact support.",
"settingsKeyboardShortcutSearch": "Search",
"settingsKeyboardShortcutSettings": "Settings",
"settingsKeyboardShortcutNewMessage": "New Message",
"settingsKeyboardShortcutCloseDialog": "Close Dialog",
"close": "Close",
"contactMethod": "Contact Method",
"contactMethodType": "Contact Type",
"contactMethodTypeEmail": "Email",
"contactMethodTypePhone": "Phone",
"contactMethodTypeAddress": "Address",
"contactMethodEmailHint": "Enter your email address",
"contactMethodPhoneHint": "Enter your phone number",
"contactMethodAddressHint": "Enter your physical address",
"contactMethodEmailDescription": "Your email will be used for account recovery and notifications",
"contactMethodPhoneDescription": "Your phone number will be used for account recovery and notifications",
"contactMethodAddressDescription": "Your physical address will be used for shipping and billing purposes.",
"contactMethodVerified": "Verified",
"contactMethodUnverified": "Unverified",
"contactMethodVerify": "Verify Contact",
"contactMethodDelete": "Delete Contact",
"contactMethodNew": "New Contact Method",
"contactMethodContentEmpty": "Contact content cannot be empty",
"contactMethodVerificationSent": "Verification code sent to your contact method",
"contactMethodVerificationNeeded": "The contact method is added, but not verified yet. You can verify it by tapping it and select verify.",
"accountContactMethod": "Contact Methods",
"accountContactMethodDescription": "Manage your contact methods for account recovery and notifications",
"authFactorVerificationNeeded": "The auth factor is added, but it is not enabled yet. You can enable it by tapping it and enter the verification code.",
"contactMethodPrimary": "Primary",
"contactMethodSetPrimary": "Set as Primary",
"contactMethodSetPrimaryHint": "Set this contact method as your primary contact method for account recovery and notifications",
"contactMethodDeleteHint": "Are you sure to delete this contact method? This action cannot be undone."
}

View File

@ -99,6 +99,14 @@
"permissionMember": "成员",
"reply": "回复",
"forward": "转发",
"repliedTo": "回复了",
"forwarded": "转发了",
"hasAttachments": {
"other": "{}个附件"
},
"postHasAttachments": {
"other": "{}个附件"
},
"edited": "已编辑",
"addVideo": "添加视频",
"addPhoto": "添加照片",
@ -278,5 +286,10 @@
"settingsHideBottomNav": "隐藏底部导航",
"settingsSoundEffects": "音效",
"settingsAprilFoolFeatures": "愚人节功能",
"settingsEnterToSend": "按下 Enter 发送"
"settingsEnterToSend": "按下 Enter 发送",
"postVisibility": "可见性",
"postVisibilityPublic": "公开",
"postVisibilityFriends": "仅好友可见",
"postVisibilityUnlisted": "不公开",
"postVisibilityPrivate": "私密"
}

View File

@ -99,6 +99,14 @@
"permissionMember": "成員",
"reply": "回覆",
"forward": "轉發",
"repliedTo": "回覆了",
"forwarded": "轉發了",
"hasAttachments": {
"other": "{}個附件"
},
"postHasAttachments": {
"other": "{}個附件"
},
"edited": "已編輯",
"addVideo": "新增影片",
"addPhoto": "新增照片",
@ -278,5 +286,10 @@
"settingsHideBottomNav": "隱藏底部導航",
"settingsSoundEffects": "音效",
"settingsAprilFoolFeatures": "愚人節功能",
"settingsEnterToSend": "按下 Enter 傳送"
"settingsEnterToSend": "按下 Enter 傳送",
"postVisibility": "可見性",
"postVisibilityPublic": "公開",
"postVisibilityFriends": "僅好友可見",
"postVisibilityUnlisted": "不公開",
"postVisibilityPrivate": "私密"
}

View File

@ -0,0 +1,4 @@
#!/bin/sh
cd "$(dirname "$0")"
exec ./island

View File

@ -0,0 +1,8 @@
[Desktop Entry]
Version=1.0
Type=Application
Terminal=false
Name=Solian
Exec=island %u
Icon=icon-padded
Categories=Network;

Binary file not shown.

View File

@ -29,13 +29,21 @@ flutter_ios_podfile_setup
target 'Runner' do
use_frameworks!
use_modular_headers!
pod 'Kingfisher', '~> 8.0'
pod 'Alamofire'
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
target 'RunnerTests' do
inherit! :search_paths
end
target 'SolianNotificationService' do
inherit! :search_paths
pod 'Kingfisher', '~> 8.0'
pod 'Alamofire'
end
end
post_install do |installer|

View File

@ -1,4 +1,5 @@
PODS:
- Alamofire (5.10.2)
- connectivity_plus (0.0.1):
- Flutter
- croppy (0.0.1):
@ -89,6 +90,8 @@ PODS:
- flutter_webrtc (0.14.0):
- Flutter
- WebRTC-SDK (= 125.6422.07)
- gal (1.0.0):
- Flutter
- GoogleDataTransport (10.1.0):
- nanopb (~> 3.30910.0)
- PromisesObjC (~> 2.4)
@ -137,10 +140,14 @@ PODS:
- OrderedSet (6.0.3)
- package_info_plus (0.4.5):
- Flutter
- pasteboard (0.0.1):
- Flutter
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
- PromisesObjC (2.4.0)
- record_ios (1.0.0):
- Flutter
- SAMKeychain (1.5.3)
- SDWebImage (5.21.0):
- SDWebImage/Core (= 5.21.0)
@ -185,6 +192,7 @@ PODS:
- WebRTC-SDK (125.6422.07)
DEPENDENCIES:
- Alamofire
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
- croppy (from `.symlinks/plugins/croppy/ios`)
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
@ -197,6 +205,7 @@ DEPENDENCIES:
- flutter_platform_alert (from `.symlinks/plugins/flutter_platform_alert/ios`)
- flutter_udid (from `.symlinks/plugins/flutter_udid/ios`)
- flutter_webrtc (from `.symlinks/plugins/flutter_webrtc/ios`)
- gal (from `.symlinks/plugins/gal/ios`)
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
- irondash_engine_context (from `.symlinks/plugins/irondash_engine_context/ios`)
- Kingfisher (~> 8.0)
@ -204,7 +213,9 @@ DEPENDENCIES:
- media_kit_libs_ios_video (from `.symlinks/plugins/media_kit_libs_ios_video/ios`)
- media_kit_video (from `.symlinks/plugins/media_kit_video/ios`)
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
- pasteboard (from `.symlinks/plugins/pasteboard/ios`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- record_ios (from `.symlinks/plugins/record_ios/ios`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
- sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`)
- sqlite3_flutter_libs (from `.symlinks/plugins/sqlite3_flutter_libs/darwin`)
@ -215,6 +226,7 @@ DEPENDENCIES:
SPEC REPOS:
trunk:
- Alamofire
- DKImagePickerController
- DKPhotoGallery
- Firebase
@ -259,6 +271,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/flutter_udid/ios"
flutter_webrtc:
:path: ".symlinks/plugins/flutter_webrtc/ios"
gal:
:path: ".symlinks/plugins/gal/ios"
image_picker_ios:
:path: ".symlinks/plugins/image_picker_ios/ios"
irondash_engine_context:
@ -271,8 +285,12 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/media_kit_video/ios"
package_info_plus:
:path: ".symlinks/plugins/package_info_plus/ios"
pasteboard:
:path: ".symlinks/plugins/pasteboard/ios"
path_provider_foundation:
:path: ".symlinks/plugins/path_provider_foundation/darwin"
record_ios:
:path: ".symlinks/plugins/record_ios/ios"
shared_preferences_foundation:
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
sqflite_darwin:
@ -289,6 +307,7 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/wakelock_plus/ios"
SPEC CHECKSUMS:
Alamofire: 7193b3b92c74a07f85569e1a6c4f4237291e7496
connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd
croppy: 979e8ddc254f4642bffe7d52dc7193354b27ba30
device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe
@ -308,6 +327,7 @@ SPEC CHECKSUMS:
flutter_platform_alert: bf3b5fcd4ac14bd637e20527e9c471633071afd3
flutter_udid: f7c3884e6ec2951efe4f9de082257fc77c4d15e9
flutter_webrtc: fd0d3bdef8766a0736dbbe2e5b7e85f1f3c52117
gal: 29e711cd17bccb47f839d9b30afe9bc9750950b2
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a
@ -319,8 +339,10 @@ SPEC CHECKSUMS:
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
pasteboard: 49088aeb6119d51f976a421db60d8e1ab079b63c
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
record_ios: fee1c924aa4879b882ebca2b4bce6011bcfc3d8b
SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c
SDWebImage: f84b0feeb08d2d11e6a9b843cb06d75ebf5b8868
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
@ -334,6 +356,6 @@ SPEC CHECKSUMS:
wakelock_plus: e29112ab3ef0b318e58cfa5c32326458be66b556
WebRTC-SDK: dff00a3892bc570b6014e046297782084071657e
PODFILE CHECKSUM: 2608312fddeea6d787ca3aa478c756da8f4ca66d
PODFILE CHECKSUM: 0c13198c20d0416ef589aeb2e1dac5c50262254f
COCOAPODS: 1.16.2

View File

@ -10,12 +10,15 @@
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
73CDD6812DEC00480059D95D /* SolianNotificationService.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 73CDD67A2DEC00480059D95D /* SolianNotificationService.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
73D4264B2DEB815D006C0AAE /* NotifyDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73D4264A2DEB815D006C0AAE /* NotifyDelegate.swift */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
755557017FD1B99AFC4F9127 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 29812C17FFBE7DBBC7203981 /* Pods_RunnerTests.framework */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
B87C0E607033790E71B54D73 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F6D834CA86410B09796B312B /* Pods_Runner.framework */; };
D1772CE196985AE8E8C9F2E5 /* Pods_SolianNotificationService.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 39FE4CC6223F0D3C0E1FFD04 /* Pods_SolianNotificationService.framework */; };
E7A0B456EF7AAA71D1397081 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 9AE244813FCDFAA941430393 /* GoogleService-Info.plist */; };
/* End PBXBuildFile section */
@ -27,9 +30,27 @@
remoteGlobalIDString = 97C146ED1CF9000F007C117D;
remoteInfo = Runner;
};
73CDD67F2DEC00480059D95D /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
proxyType = 1;
remoteGlobalIDString = 73CDD6792DEC00480059D95D;
remoteInfo = SolianNotificationService;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
73268D1D2DEAFD670076E970 /* Embed Foundation Extensions */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 13;
files = (
73CDD6812DEC00480059D95D /* SolianNotificationService.appex in Embed Foundation Extensions */,
);
name = "Embed Foundation Extensions";
runOnlyForDeploymentPostprocessing = 0;
};
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
@ -47,16 +68,23 @@
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
14DFD79BE7C26E51B117583C /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
192FDACE67D7CB6AED15C634 /* Pods-NotificationService.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NotificationService.debug.xcconfig"; path = "Target Support Files/Pods-NotificationService/Pods-NotificationService.debug.xcconfig"; sourceTree = "<group>"; };
1C14F71D23E4371602065522 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
252A83CE6862573BB856ED8E /* Pods-NotificationService.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NotificationService.release.xcconfig"; path = "Target Support Files/Pods-NotificationService/Pods-NotificationService.release.xcconfig"; sourceTree = "<group>"; };
29812C17FFBE7DBBC7203981 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
2D2457F8B2E6EF9C0F935035 /* Pods-NotificationService.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NotificationService.profile.xcconfig"; path = "Target Support Files/Pods-NotificationService/Pods-NotificationService.profile.xcconfig"; sourceTree = "<group>"; };
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
39FE4CC6223F0D3C0E1FFD04 /* Pods_SolianNotificationService.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SolianNotificationService.framework; sourceTree = BUILT_PRODUCTS_DIR; };
3A1C47BD29CC6AC2587D4DBE /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
737E920B2DB6A9FF00BE9CDB /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = "<group>"; };
73CDD67A2DEC00480059D95D /* SolianNotificationService.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = SolianNotificationService.appex; sourceTree = BUILT_PRODUCTS_DIR; };
73D4264A2DEB815D006C0AAE /* NotifyDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotifyDelegate.swift; sourceTree = "<group>"; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
8B40620B1EEBB09456406A3C /* Pods-SolianNotificationService.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SolianNotificationService.profile.xcconfig"; path = "Target Support Files/Pods-SolianNotificationService/Pods-SolianNotificationService.profile.xcconfig"; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
@ -66,10 +94,50 @@
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
9AE244813FCDFAA941430393 /* GoogleService-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "Runner/GoogleService-Info.plist"; sourceTree = "<group>"; };
A499FDB2082EB000933AA8C5 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
AA0CA8A3E15DEE023BB27438 /* Pods_NotificationService.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_NotificationService.framework; sourceTree = BUILT_PRODUCTS_DIR; };
B93771F2A63E4148DC6142F7 /* Pods-SolianNotificationService.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SolianNotificationService.release.xcconfig"; path = "Target Support Files/Pods-SolianNotificationService/Pods-SolianNotificationService.release.xcconfig"; sourceTree = "<group>"; };
E6B10A9A85BECA2E576C91FF /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
F6D834CA86410B09796B312B /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
F830F535CB92E3F2E1653A11 /* Pods-SolianNotificationService.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SolianNotificationService.debug.xcconfig"; path = "Target Support Files/Pods-SolianNotificationService/Pods-SolianNotificationService.debug.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
73CDD6822DEC00480059D95D /* Exceptions for "SolianNotificationService" folder in "SolianNotificationService" target */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = (
Info.plist,
);
target = 73CDD6792DEC00480059D95D /* SolianNotificationService */;
};
73CDD68E2DEC00BE0059D95D /* Exceptions for "Services" folder in "SolianNotificationService" target */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = (
CloudFile.swift,
DataExchange.swift,
);
target = 73CDD6792DEC00480059D95D /* SolianNotificationService */;
};
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
/* Begin PBXFileSystemSynchronizedRootGroup section */
73268D272DEB012A0076E970 /* Services */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
73CDD68E2DEC00BE0059D95D /* Exceptions for "Services" folder in "SolianNotificationService" target */,
);
path = Services;
sourceTree = "<group>";
};
73CDD67B2DEC00480059D95D /* SolianNotificationService */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
73CDD6822DEC00480059D95D /* Exceptions for "SolianNotificationService" folder in "SolianNotificationService" target */,
);
path = SolianNotificationService;
sourceTree = "<group>";
};
/* End PBXFileSystemSynchronizedRootGroup section */
/* Begin PBXFrameworksBuildPhase section */
1DFF8FEBBD0CF5A990600776 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
@ -79,6 +147,14 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
73CDD6772DEC00480059D95D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
D1772CE196985AE8E8C9F2E5 /* Pods_SolianNotificationService.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EB1CF9000F007C117D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
@ -103,6 +179,8 @@
children = (
F6D834CA86410B09796B312B /* Pods_Runner.framework */,
29812C17FFBE7DBBC7203981 /* Pods_RunnerTests.framework */,
AA0CA8A3E15DEE023BB27438 /* Pods_NotificationService.framework */,
39FE4CC6223F0D3C0E1FFD04 /* Pods_SolianNotificationService.framework */,
);
name = Frameworks;
sourceTree = "<group>";
@ -116,6 +194,12 @@
14DFD79BE7C26E51B117583C /* Pods-RunnerTests.debug.xcconfig */,
14118AC858B441AB16B7309E /* Pods-RunnerTests.release.xcconfig */,
E6B10A9A85BECA2E576C91FF /* Pods-RunnerTests.profile.xcconfig */,
192FDACE67D7CB6AED15C634 /* Pods-NotificationService.debug.xcconfig */,
252A83CE6862573BB856ED8E /* Pods-NotificationService.release.xcconfig */,
2D2457F8B2E6EF9C0F935035 /* Pods-NotificationService.profile.xcconfig */,
F830F535CB92E3F2E1653A11 /* Pods-SolianNotificationService.debug.xcconfig */,
B93771F2A63E4148DC6142F7 /* Pods-SolianNotificationService.release.xcconfig */,
8B40620B1EEBB09456406A3C /* Pods-SolianNotificationService.profile.xcconfig */,
);
path = Pods;
sourceTree = "<group>";
@ -136,6 +220,7 @@
children = (
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
73CDD67B2DEC00480059D95D /* SolianNotificationService */,
97C146EF1CF9000F007C117D /* Products */,
331C8082294A63A400263BE5 /* RunnerTests */,
91E124CE95BCB4DCD890160D /* Pods */,
@ -149,6 +234,7 @@
children = (
97C146EE1CF9000F007C117D /* Runner.app */,
331C8081294A63A400263BE5 /* RunnerTests.xctest */,
73CDD67A2DEC00480059D95D /* SolianNotificationService.appex */,
);
name = Products;
sourceTree = "<group>";
@ -156,6 +242,7 @@
97C146F01CF9000F007C117D /* Runner */ = {
isa = PBXGroup;
children = (
73268D272DEB012A0076E970 /* Services */,
737E920B2DB6A9FF00BE9CDB /* Runner.entitlements */,
97C146FA1CF9000F007C117D /* Main.storyboard */,
97C146FD1CF9000F007C117D /* Assets.xcassets */,
@ -165,6 +252,7 @@
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
73D4264A2DEB815D006C0AAE /* NotifyDelegate.swift */,
);
path = Runner;
sourceTree = "<group>";
@ -191,6 +279,27 @@
productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
73CDD6792DEC00480059D95D /* SolianNotificationService */ = {
isa = PBXNativeTarget;
buildConfigurationList = 73CDD6832DEC00480059D95D /* Build configuration list for PBXNativeTarget "SolianNotificationService" */;
buildPhases = (
5FC5C1EF9F17B7166B432FF2 /* [CP] Check Pods Manifest.lock */,
73CDD6762DEC00480059D95D /* Sources */,
73CDD6772DEC00480059D95D /* Frameworks */,
73CDD6782DEC00480059D95D /* Resources */,
);
buildRules = (
);
dependencies = (
);
fileSystemSynchronizedGroups = (
73CDD67B2DEC00480059D95D /* SolianNotificationService */,
);
name = SolianNotificationService;
productName = SolianNotificationService;
productReference = 73CDD67A2DEC00480059D95D /* SolianNotificationService.appex */;
productType = "com.apple.product-type.app-extension";
};
97C146ED1CF9000F007C117D /* Runner */ = {
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
@ -199,6 +308,7 @@
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
73268D1D2DEAFD670076E970 /* Embed Foundation Extensions */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
@ -208,6 +318,10 @@
buildRules = (
);
dependencies = (
73CDD6802DEC00480059D95D /* PBXTargetDependency */,
);
fileSystemSynchronizedGroups = (
73268D272DEB012A0076E970 /* Services */,
);
name = Runner;
productName = Runner;
@ -221,6 +335,7 @@
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = YES;
LastSwiftUpdateCheck = 1640;
LastUpgradeCheck = 1510;
ORGANIZATIONNAME = "";
TargetAttributes = {
@ -228,6 +343,9 @@
CreatedOnToolsVersion = 14.0;
TestTargetID = 97C146ED1CF9000F007C117D;
};
73CDD6792DEC00480059D95D = {
CreatedOnToolsVersion = 16.4;
};
97C146ED1CF9000F007C117D = {
CreatedOnToolsVersion = 7.3.1;
LastSwiftMigration = 1100;
@ -235,7 +353,6 @@
};
};
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
@ -243,12 +360,14 @@
Base,
);
mainGroup = 97C146E51CF9000F007C117D;
preferredProjectObjectVersion = 77;
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
97C146ED1CF9000F007C117D /* Runner */,
331C8080294A63A400263BE5 /* RunnerTests */,
73CDD6792DEC00480059D95D /* SolianNotificationService */,
);
};
/* End PBXProject section */
@ -261,6 +380,13 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
73CDD6782DEC00480059D95D /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EC1CF9000F007C117D /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
@ -331,6 +457,28 @@
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
showEnvVarsInLog = 0;
};
5FC5C1EF9F17B7166B432FF2 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-SolianNotificationService-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
8C0351B03869BBF493808288 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
@ -396,12 +544,20 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
73CDD6762DEC00480059D95D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EA1CF9000F007C117D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
73D4264B2DEB815D006C0AAE /* NotifyDelegate.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -413,6 +569,11 @@
target = 97C146ED1CF9000F007C117D /* Runner */;
targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */;
};
73CDD6802DEC00480059D95D /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 73CDD6792DEC00480059D95D /* SolianNotificationService */;
targetProxy = 73CDD67F2DEC00480059D95D /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
@ -562,6 +723,123 @@
};
name = Profile;
};
73CDD6842DEC00480059D95D /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = F830F535CB92E3F2E1653A11 /* Pods-SolianNotificationService.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = W7HPZ53V6B;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = SolianNotificationService/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = SolianNotificationService;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
IPHONEOS_DEPLOYMENT_TARGET = 18.5;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MARKETING_VERSION = 1.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = dev.solsynth.solian.SolianNotificationService;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
73CDD6852DEC00480059D95D /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = B93771F2A63E4148DC6142F7 /* Pods-SolianNotificationService.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = W7HPZ53V6B;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = SolianNotificationService/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = SolianNotificationService;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
IPHONEOS_DEPLOYMENT_TARGET = 18.5;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MARKETING_VERSION = 1.0;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = dev.solsynth.solian.SolianNotificationService;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
73CDD6862DEC00480059D95D /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 8B40620B1EEBB09456406A3C /* Pods-SolianNotificationService.profile.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = W7HPZ53V6B;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = SolianNotificationService/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = SolianNotificationService;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
IPHONEOS_DEPLOYMENT_TARGET = 18.5;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MARKETING_VERSION = 1.0;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = dev.solsynth.solian.SolianNotificationService;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Profile;
};
97C147031CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
@ -737,6 +1015,16 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
73CDD6832DEC00480059D95D /* Build configuration list for PBXNativeTarget "SolianNotificationService" */ = {
isa = XCConfigurationList;
buildConfigurations = (
73CDD6842DEC00480059D95D /* Debug */,
73CDD6852DEC00480059D95D /* Release */,
73CDD6862DEC00480059D95D /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (

View File

@ -3,11 +3,15 @@ import UIKit
@main
@objc class AppDelegate: FlutterAppDelegate {
let notifyDelegate = NotifyDelegate()
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
UNUserNotificationCenter.current().delegate = notifyDelegate
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}

View File

@ -2,6 +2,8 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>CFBundleDevelopmentRegion</key>
@ -64,5 +66,10 @@
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>NSUserActivityTypes</key>
<array>
<string>INStartCallIntent</string>
<string>INSendMessageIntent</string>
</array>
</dict>
</plist>

View File

@ -1,153 +0,0 @@
//
// BlurHashDecoder.swift
// Runner
//
// Created by LittleSheep on 2025/4/21.
//
import UIKit
extension UIImage {
public convenience init?(blurHash: String, size: CGSize, punch: Float = 1) {
guard blurHash.count >= 6 else { return nil }
let sizeFlag = String(blurHash[0]).decode83()
let numY = (sizeFlag / 9) + 1
let numX = (sizeFlag % 9) + 1
let quantisedMaximumValue = String(blurHash[1]).decode83()
let maximumValue = Float(quantisedMaximumValue + 1) / 166
guard blurHash.count == 4 + 2 * numX * numY else { return nil }
let colours: [(Float, Float, Float)] = (0 ..< numX * numY).map { i in
if i == 0 {
let value = String(blurHash[2 ..< 6]).decode83()
return decodeDC(value)
} else {
let value = String(blurHash[4 + i * 2 ..< 4 + i * 2 + 2]).decode83()
return decodeAC(value, maximumValue: maximumValue * punch)
}
}
let width = Int(size.width)
let height = Int(size.height)
let bytesPerRow = width * 3
guard let data = CFDataCreateMutable(kCFAllocatorDefault, bytesPerRow * height) else { return nil }
CFDataSetLength(data, bytesPerRow * height)
guard let pixels = CFDataGetMutableBytePtr(data) else { return nil }
for y in 0 ..< height {
for x in 0 ..< width {
var r: Float = 0
var g: Float = 0
var b: Float = 0
for j in 0 ..< numY {
for i in 0 ..< numX {
let basis = cos(Float.pi * Float(x) * Float(i) / Float(width)) * cos(Float.pi * Float(y) * Float(j) / Float(height))
let colour = colours[i + j * numX]
r += colour.0 * basis
g += colour.1 * basis
b += colour.2 * basis
}
}
let intR = UInt8(linearTosRGB(r))
let intG = UInt8(linearTosRGB(g))
let intB = UInt8(linearTosRGB(b))
pixels[3 * x + 0 + y * bytesPerRow] = intR
pixels[3 * x + 1 + y * bytesPerRow] = intG
pixels[3 * x + 2 + y * bytesPerRow] = intB
}
}
let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.none.rawValue)
guard let provider = CGDataProvider(data: data) else { return nil }
guard let cgImage = CGImage(width: width, height: height, bitsPerComponent: 8, bitsPerPixel: 24, bytesPerRow: bytesPerRow,
space: CGColorSpaceCreateDeviceRGB(), bitmapInfo: bitmapInfo, provider: provider, decode: nil, shouldInterpolate: true, intent: .defaultIntent) else { return nil }
self.init(cgImage: cgImage)
}
}
private func decodeDC(_ value: Int) -> (Float, Float, Float) {
let intR = value >> 16
let intG = (value >> 8) & 255
let intB = value & 255
return (sRGBToLinear(intR), sRGBToLinear(intG), sRGBToLinear(intB))
}
private func decodeAC(_ value: Int, maximumValue: Float) -> (Float, Float, Float) {
let quantR = value / (19 * 19)
let quantG = (value / 19) % 19
let quantB = value % 19
let rgb = (
signPow((Float(quantR) - 9) / 9, 2) * maximumValue,
signPow((Float(quantG) - 9) / 9, 2) * maximumValue,
signPow((Float(quantB) - 9) / 9, 2) * maximumValue
)
return rgb
}
private func signPow(_ value: Float, _ exp: Float) -> Float {
return copysign(pow(abs(value), exp), value)
}
private func linearTosRGB(_ value: Float) -> Int {
let v = max(0, min(1, value))
if v <= 0.0031308 { return Int(v * 12.92 * 255 + 0.5) }
else { return Int((1.055 * pow(v, 1 / 2.4) - 0.055) * 255 + 0.5) }
}
private func sRGBToLinear<Type: BinaryInteger>(_ value: Type) -> Float {
let v = Float(Int64(value)) / 255
if v <= 0.04045 { return v / 12.92 }
else { return pow((v + 0.055) / 1.055, 2.4) }
}
private let encodeCharacters: [String] = {
return "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#$%*+,-.:;=?@[]^_{|}~".map { String($0) }
}()
private let decodeCharacters: [String: Int] = {
var dict: [String: Int] = [:]
for (index, character) in encodeCharacters.enumerated() {
dict[character] = index
}
return dict
}()
extension String {
func decode83() -> Int {
var value: Int = 0
for character in self {
if let digit = decodeCharacters[String(character)] {
value = value * 83 + digit
}
}
return value
}
}
private extension String {
subscript (offset: Int) -> Character {
return self[index(startIndex, offsetBy: offset)]
}
subscript (bounds: CountableClosedRange<Int>) -> Substring {
let start = index(startIndex, offsetBy: bounds.lowerBound)
let end = index(startIndex, offsetBy: bounds.upperBound)
return self[start...end]
}
subscript (bounds: CountableRange<Int>) -> Substring {
let start = index(startIndex, offsetBy: bounds.lowerBound)
let end = index(startIndex, offsetBy: bounds.upperBound)
return self[start..<end]
}
}

View File

@ -1,42 +0,0 @@
//
// ImageView.swift
// Runner
//
// Created by LittleSheep on 2025/4/21.
//
import UIKit
import Kingfisher
class ImageView: UIImageView {
private var _size: CGSize = CGSize(width: 0, height: 0)
// Initialize the image view
override init(frame: CGRect) {
super.init(frame: frame)
contentMode = .scaleAspectFill
clipsToBounds = true
}
required init?(coder: NSCoder) {
super.init(coder: coder)
contentMode = .scaleAspectFill
clipsToBounds = true
}
// Method to set the image from a URL using Kingfisher
func setImage(from url: URL, blurHash: String?) {
let placeholderImage = blurHash != nil ? UIImage.init(blurHash: blurHash!, size: _size) : nil
let processor = DownsamplingImageProcessor(size: _size) |> RoundCornerImageProcessor(cornerRadius: 20)
self.kf.indicatorType = .activity
self.kf.setImage(
with: url,
placeholder: placeholderImage,
options: [
.processor(processor),
.transition(.fade(0.3))
]
)
}
}

View File

@ -1,62 +0,0 @@
//
// NativeImage.swift
// Runner
//
// Created by LittleSheep on 2025/4/21.
//
import Flutter
import UIKit
import Kingfisher
class FLNativeImageFactory : NSObject, FlutterPlatformViewFactory {
private var messenger: FlutterBinaryMessenger
init(messenger: FlutterBinaryMessenger) {
self.messenger = messenger
super.init()
}
func create(
withFrame frame: CGRect,
viewIdentifier viewId: Int64,
arguments args: Any?
) -> FlutterPlatformView {
return FLNativeImage(
frame: frame,
viewIdentifier: viewId,
arguments: args,
binaryMessenger: messenger)
}
/// Implementing this method is only necessary when the `arguments` in `createWithFrame` is not `nil`.
public func createArgsCodec() -> FlutterMessageCodec & NSObjectProtocol {
return FlutterStandardMessageCodec.sharedInstance()
}
}
class FLNativeImage : NSObject, FlutterPlatformView {
private var _view: ImageView
init(
frame: CGRect,
viewIdentifier viewId: Int64,
arguments args: Any?,
binaryMessenger messenger: FlutterBinaryMessenger?
) {
_view = ImageView(frame: frame)
super.init()
let argsMap = args as! [AnyHashable: Any]
let source = argsMap["src"] as! String
let blurHash = argsMap["blur"] as? String
if let url = URL(string: source) {
_view.setImage(from: url, blurHash: blurHash)
}
}
func view() -> UIView {
return _view
}
}

View File

@ -0,0 +1,49 @@
//
// NotifyDelegate.swift
// Runner
//
// Created by LittleSheep on 2025/6/1.
//
import Foundation
import Alamofire
class NotifyDelegate: UIResponder, UNUserNotificationCenterDelegate {
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
if let textResponse = response as? UNTextInputNotificationResponse {
let content = response.notification.request.content
guard let metadata = content.userInfo["meta"] as? [AnyHashable: Any] else {
return
}
var token: String? = UserDefaults.standard.getFlutterToken()
if token == nil {
return
}
let serverUrl = UserDefaults.standard.getServerUrl()
let url = "\(serverUrl)/chat/\(metadata["room_id"] ?? "")/messages"
let parameters: [String: Any?] = [
"content": textResponse.userText,
"replied_message_id": metadata["message_id"]
]
AF.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default, headers: HTTPHeaders(
[HTTPHeader(name: "Authorization", value: "AtField \(token!)")]
))
.validate()
.responseString { response in
switch response.result {
case .success(_):
break
case .failure(let error):
print("Failed to send chat reply message: \(error)")
break
}
}
}
completionHandler()
}
}

View File

@ -4,5 +4,7 @@
<dict>
<key>aps-environment</key>
<string>development</string>
<key>com.apple.developer.usernotifications.communication</key>
<true/>
</dict>
</plist>

View File

@ -0,0 +1,14 @@
//
// CloudFile.swift
// Runner
//
// Created by LittleSheep on 2025/5/31.
//
import Foundation
func getAttachmentUrl(for identifier: String) -> String {
let serverBaseUrl = "https://nt.solian.app"
return identifier.starts(with: "http") ? identifier : "\(serverBaseUrl)/files/\(identifier)"
}

View File

@ -0,0 +1,31 @@
//
// DataExchange.swift
// Runner
//
// Created by LittleSheep on 2025/6/2.
//
import Foundation
extension UserDefaults {
func getFlutterValue<T>(forKey key: String) -> T? {
let prefixedKey = "flutter.\(key)"
return self.object(forKey: prefixedKey) as? T
}
func getFlutterToken(forKey key: String = "dyn_user_tk") -> String? {
let prefixedKey = "flutter.\(key)"
guard let jsonString = self.string(forKey: prefixedKey),
let data = jsonString.data(using: .utf8),
let jsonObject = try? JSONSerialization.jsonObject(with: data),
let jsonDict = jsonObject as? [String: Any],
let token = jsonDict["token"] as? String else {
return nil
}
return token
}
func getServerUrl(forKey key: String = "app_server_url") -> String {
return self.getFlutterValue(forKey: key) ?? "https://nt.solian.app"
}
}

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSUserActivityTypes</key>
<array>
<string>INStartCallIntent</string>
<string>INSendMessageIntent</string>
</array>
<key>NSExtension</key>
<dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.usernotifications.service</string>
<key>NSExtensionPrincipalClass</key>
<string>$(PRODUCT_MODULE_NAME).NotificationService</string>
</dict>
</dict>
</plist>

View File

@ -0,0 +1,221 @@
//
// NotificationService.swift
// NotificationService
//
// Created by LittleSheep on 2025/5/31.
//
import UserNotifications
import Intents
import Kingfisher
import UniformTypeIdentifiers
enum ParseNotificationPayloadError: Error {
case missingMetadata(String)
case missingAvatarUrl(String)
}
class NotificationService: UNNotificationServiceExtension {
private var contentHandler: ((UNNotificationContent) -> Void)?
private var bestAttemptContent: UNMutableNotificationContent?
override func didReceive(
_ request: UNNotificationRequest,
withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void
) {
self.contentHandler = contentHandler
guard let bestAttemptContent = request.content.mutableCopy() as? UNMutableNotificationContent else {
contentHandler(request.content)
return
}
self.bestAttemptContent = bestAttemptContent
do {
try processNotification(request: request, content: bestAttemptContent)
} catch {
contentHandler(bestAttemptContent)
}
}
override func serviceExtensionTimeWillExpire() {
if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent {
contentHandler(bestAttemptContent)
}
}
private func processNotification(request: UNNotificationRequest, content: UNMutableNotificationContent) throws {
switch content.userInfo["type"] as? String {
case "messages.new":
try handleMessagingNotification(request: request, content: content)
default:
try handleDefaultNotification(content: content)
}
}
private func handleMessagingNotification(request: UNNotificationRequest, content: UNMutableNotificationContent) throws {
guard let meta = content.userInfo["meta"] as? [AnyHashable: Any] else {
throw ParseNotificationPayloadError.missingMetadata("The notification has no meta.")
}
let pfpIdentifier = meta["pfp"] as? String
let replyableMessageCategory = UNNotificationCategory(
identifier: content.categoryIdentifier,
actions: [
UNTextInputNotificationAction(
identifier: "reply_action",
title: "Reply",
options: []
),
],
intentIdentifiers: [],
options: []
)
UNUserNotificationCenter.current().setNotificationCategories([replyableMessageCategory])
content.categoryIdentifier = replyableMessageCategory.identifier
let metaCopy = meta as? [String: Any] ?? [:]
let pfpUrl = pfpIdentifier != nil ? getAttachmentUrl(for: pfpIdentifier!) : nil
let targetSize = 512
let scaleProcessor = ResizingImageProcessor(referenceSize: CGSize(width: targetSize, height: targetSize), mode: .aspectFit)
KingfisherManager.shared.retrieveImage(with: URL(string: pfpUrl!)!, options: [.processor(scaleProcessor)], completionHandler: { result in
var image: Data?
switch result {
case .success(let value):
image = value.image.pngData()
case .failure(let error):
print("Unable to get pfp url: \(error)")
}
let handle = INPersonHandle(value: "\(metaCopy["user_id"] ?? "")", type: .unknown)
let sender = INPerson(
personHandle: handle,
nameComponents: PersonNameComponents(nickname: "\(metaCopy["sender_name"] ?? "")"),
displayName: content.title,
image: image == nil ? nil : INImage(imageData: image!),
contactIdentifier: nil,
customIdentifier: nil
)
let intent = self.createMessageIntent(with: sender, meta: metaCopy, body: content.body)
self.donateInteraction(for: intent)
let updatedContent = try? request.content.updating(from: intent)
self.contentHandler?(updatedContent ?? content)
})
}
private func handleDefaultNotification(content: UNMutableNotificationContent) throws {
guard let meta = content.userInfo["meta"] as? [AnyHashable: Any] else {
throw ParseNotificationPayloadError.missingMetadata("The notification has no meta.")
}
if let imageIdentifier = meta["image"] as? String {
attachMedia(to: content, withIdentifier: [imageIdentifier], fileType: UTType.webP, doScaleDown: true)
} else if let pfpIdentifier = meta["pfp"] as? String {
attachMedia(to: content, withIdentifier: [pfpIdentifier], fileType: UTType.webP, doScaleDown: true)
} else if let imagesIdentifier = meta["images"] as? Array<String> {
attachMedia(to: content, withIdentifier: imagesIdentifier, fileType: UTType.webP, doScaleDown: true)
} else {
contentHandler?(content)
}
}
private func attachMedia(to content: UNMutableNotificationContent, withIdentifier identifier: Array<String>, fileType type: UTType?, doScaleDown scaleDown: Bool = false) {
let attachmentUrls = identifier.compactMap { element in
return getAttachmentUrl(for: element)
}
guard !attachmentUrls.isEmpty else {
print("Invalid URLs for attachments: \(attachmentUrls)")
return
}
let targetSize = 512
let scaleProcessor = ResizingImageProcessor(referenceSize: CGSize(width: targetSize, height: targetSize), mode: .aspectFit)
for attachmentUrl in attachmentUrls {
guard let remoteUrl = URL(string: attachmentUrl) else {
print("Invalid URL for attachment: \(attachmentUrl)")
continue // Skip this URL and move to the next one
}
KingfisherManager.shared.retrieveImage(with: remoteUrl, options: scaleDown ? [
.processor(scaleProcessor)
] : nil) { [weak self] result in
guard let self = self else { return }
switch result {
case .success(let retrievalResult):
// The image is either retrieved from cache or downloaded
let tempDirectory = FileManager.default.temporaryDirectory
let cachedFileUrl = tempDirectory.appendingPathComponent(UUID().uuidString) // Unique identifier for each file
do {
// Write the image data to a temporary file for UNNotificationAttachment
try retrievalResult.image.pngData()?.write(to: cachedFileUrl)
self.attachLocalMedia(to: content, fileType: type?.identifier, from: cachedFileUrl, withIdentifier: attachmentUrl)
} catch {
print("Failed to write media to temporary file: \(error.localizedDescription)")
self.contentHandler?(content)
}
case .failure(let error):
print("Failed to retrieve image: \(error.localizedDescription)")
self.contentHandler?(content)
}
}
}
}
private func attachLocalMedia(to content: UNMutableNotificationContent, fileType type: String?, from localUrl: URL, withIdentifier identifier: String) {
do {
let attachment = try UNNotificationAttachment(identifier: identifier, url: localUrl, options: [
UNNotificationAttachmentOptionsTypeHintKey: type as Any,
UNNotificationAttachmentOptionsThumbnailHiddenKey: 0,
])
content.attachments = [attachment]
} catch let error as NSError {
// Log detailed error information
print("Failed to create attachment from file at \(localUrl.path)")
print("Error: \(error.localizedDescription)")
// Check specific error codes if needed
if error.domain == NSCocoaErrorDomain {
switch error.code {
case NSFileReadNoSuchFileError:
print("File does not exist at \(localUrl.path)")
case NSFileReadNoPermissionError:
print("No permission to read file at \(localUrl.path)")
default:
print("Unhandled file error: \(error.code)")
}
}
}
// Call content handler regardless of success or failure
self.contentHandler?(content)
}
private func createMessageIntent(with sender: INPerson, meta: [AnyHashable: Any], body: String) -> INSendMessageIntent {
INSendMessageIntent(
recipients: nil,
outgoingMessageType: .outgoingMessageText,
content: body,
speakableGroupName: meta["room_name"] != nil ? INSpeakableString(spokenPhrase: meta["room_name"] as! String) : nil,
conversationIdentifier: "\(meta["room_id"] ?? "")",
serviceName: nil,
sender: sender,
attachments: nil
)
}
private func donateInteraction(for intent: INIntent) {
let interaction = INInteraction(intent: intent, response: nil)
interaction.direction = .incoming
interaction.donate(completion: nil)
}
}

View File

@ -1,3 +1,6 @@
import 'dart:convert';
import 'dart:developer';
import 'package:dio/dio.dart';
import 'package:island/database/drift_db.dart';
import 'package:island/database/message.dart';
@ -45,6 +48,13 @@ class MessageRepository {
},
);
for (final item in resp.data['changes']) {
if (item['message']['sender']['account'] == null)
log(jsonEncode(item['message']['sender']['account']));
// if (item['message'] != null &&
// item['message']['sender']['account'] == null) {
// }
}
final response = MessageSyncResponse.fromJson(resp.data);
for (final change in response.changes) {
switch (change.action) {
@ -186,7 +196,7 @@ class MessageRepository {
}
Future<LocalChatMessage> sendMessage(
String atk,
String token,
String baseUrl,
String roomId,
String content,
@ -231,8 +241,8 @@ class MessageRepository {
for (var idx = 0; idx < attachments.length; idx++) {
final cloudFile =
await putMediaToCloud(
fileData: attachments[idx].data,
atk: atk,
fileData: attachments[idx],
atk: token,
baseUrl: baseUrl,
filename: attachments[idx].data.name ?? 'Post media',
mimetype:
@ -299,7 +309,7 @@ class MessageRepository {
}
Future<LocalChatMessage> retryMessage(String pendingMessageId) async {
final message = pendingMessages[pendingMessageId];
final message = await getMessageById(pendingMessageId);
if (message == null) {
throw Exception('Message not found');
}

View File

@ -10,13 +10,10 @@ sealed class SnActivity with _$SnActivity {
required String id,
required String type,
required String resourceIdentifier,
required int visibility,
required String accountId,
required SnAccount account,
required dynamic data,
required DateTime createdAt,
required DateTime updatedAt,
required dynamic deletedAt,
required DateTime? deletedAt,
}) = _SnActivity;
factory SnActivity.fromJson(Map<String, dynamic> json) =>

View File

@ -16,7 +16,7 @@ T _$identity<T>(T value) => value;
/// @nodoc
mixin _$SnActivity {
String get id; String get type; String get resourceIdentifier; int get visibility; String get accountId; SnAccount get account; dynamic get data; DateTime get createdAt; DateTime get updatedAt; dynamic get deletedAt;
String get id; String get type; String get resourceIdentifier; dynamic get data; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt;
/// Create a copy of SnActivity
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@ -29,16 +29,16 @@ $SnActivityCopyWith<SnActivity> get copyWith => _$SnActivityCopyWithImpl<SnActiv
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnActivity&&(identical(other.id, id) || other.id == id)&&(identical(other.type, type) || other.type == type)&&(identical(other.resourceIdentifier, resourceIdentifier) || other.resourceIdentifier == resourceIdentifier)&&(identical(other.visibility, visibility) || other.visibility == visibility)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.account, account) || other.account == account)&&const DeepCollectionEquality().equals(other.data, data)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&const DeepCollectionEquality().equals(other.deletedAt, deletedAt));
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnActivity&&(identical(other.id, id) || other.id == id)&&(identical(other.type, type) || other.type == type)&&(identical(other.resourceIdentifier, resourceIdentifier) || other.resourceIdentifier == resourceIdentifier)&&const DeepCollectionEquality().equals(other.data, data)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,id,type,resourceIdentifier,visibility,accountId,account,const DeepCollectionEquality().hash(data),createdAt,updatedAt,const DeepCollectionEquality().hash(deletedAt));
int get hashCode => Object.hash(runtimeType,id,type,resourceIdentifier,const DeepCollectionEquality().hash(data),createdAt,updatedAt,deletedAt);
@override
String toString() {
return 'SnActivity(id: $id, type: $type, resourceIdentifier: $resourceIdentifier, visibility: $visibility, accountId: $accountId, account: $account, data: $data, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
return 'SnActivity(id: $id, type: $type, resourceIdentifier: $resourceIdentifier, data: $data, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
}
@ -49,11 +49,11 @@ abstract mixin class $SnActivityCopyWith<$Res> {
factory $SnActivityCopyWith(SnActivity value, $Res Function(SnActivity) _then) = _$SnActivityCopyWithImpl;
@useResult
$Res call({
String id, String type, String resourceIdentifier, int visibility, String accountId, SnAccount account, dynamic data, DateTime createdAt, DateTime updatedAt, dynamic deletedAt
String id, String type, String resourceIdentifier, dynamic data, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
});
$SnAccountCopyWith<$Res> get account;
}
/// @nodoc
@ -66,31 +66,19 @@ class _$SnActivityCopyWithImpl<$Res>
/// Create a copy of SnActivity
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? type = null,Object? resourceIdentifier = null,Object? visibility = null,Object? accountId = null,Object? account = null,Object? data = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? type = null,Object? resourceIdentifier = null,Object? data = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
return _then(_self.copyWith(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
as String,resourceIdentifier: null == resourceIdentifier ? _self.resourceIdentifier : resourceIdentifier // ignore: cast_nullable_to_non_nullable
as String,visibility: null == visibility ? _self.visibility : visibility // ignore: cast_nullable_to_non_nullable
as int,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable
as String,account: null == account ? _self.account : account // ignore: cast_nullable_to_non_nullable
as SnAccount,data: freezed == data ? _self.data : data // ignore: cast_nullable_to_non_nullable
as String,data: freezed == data ? _self.data : data // ignore: cast_nullable_to_non_nullable
as dynamic,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
as dynamic,
as DateTime?,
));
}
/// Create a copy of SnActivity
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$SnAccountCopyWith<$Res> get account {
return $SnAccountCopyWith<$Res>(_self.account, (value) {
return _then(_self.copyWith(account: value));
});
}
}
@ -98,19 +86,16 @@ $SnAccountCopyWith<$Res> get account {
@JsonSerializable()
class _SnActivity implements SnActivity {
const _SnActivity({required this.id, required this.type, required this.resourceIdentifier, required this.visibility, required this.accountId, required this.account, required this.data, required this.createdAt, required this.updatedAt, required this.deletedAt});
const _SnActivity({required this.id, required this.type, required this.resourceIdentifier, required this.data, required this.createdAt, required this.updatedAt, required this.deletedAt});
factory _SnActivity.fromJson(Map<String, dynamic> json) => _$SnActivityFromJson(json);
@override final String id;
@override final String type;
@override final String resourceIdentifier;
@override final int visibility;
@override final String accountId;
@override final SnAccount account;
@override final dynamic data;
@override final DateTime createdAt;
@override final DateTime updatedAt;
@override final dynamic deletedAt;
@override final DateTime? deletedAt;
/// Create a copy of SnActivity
/// with the given fields replaced by the non-null parameter values.
@ -125,16 +110,16 @@ Map<String, dynamic> toJson() {
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnActivity&&(identical(other.id, id) || other.id == id)&&(identical(other.type, type) || other.type == type)&&(identical(other.resourceIdentifier, resourceIdentifier) || other.resourceIdentifier == resourceIdentifier)&&(identical(other.visibility, visibility) || other.visibility == visibility)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.account, account) || other.account == account)&&const DeepCollectionEquality().equals(other.data, data)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&const DeepCollectionEquality().equals(other.deletedAt, deletedAt));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnActivity&&(identical(other.id, id) || other.id == id)&&(identical(other.type, type) || other.type == type)&&(identical(other.resourceIdentifier, resourceIdentifier) || other.resourceIdentifier == resourceIdentifier)&&const DeepCollectionEquality().equals(other.data, data)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,id,type,resourceIdentifier,visibility,accountId,account,const DeepCollectionEquality().hash(data),createdAt,updatedAt,const DeepCollectionEquality().hash(deletedAt));
int get hashCode => Object.hash(runtimeType,id,type,resourceIdentifier,const DeepCollectionEquality().hash(data),createdAt,updatedAt,deletedAt);
@override
String toString() {
return 'SnActivity(id: $id, type: $type, resourceIdentifier: $resourceIdentifier, visibility: $visibility, accountId: $accountId, account: $account, data: $data, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
return 'SnActivity(id: $id, type: $type, resourceIdentifier: $resourceIdentifier, data: $data, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
}
@ -145,11 +130,11 @@ abstract mixin class _$SnActivityCopyWith<$Res> implements $SnActivityCopyWith<$
factory _$SnActivityCopyWith(_SnActivity value, $Res Function(_SnActivity) _then) = __$SnActivityCopyWithImpl;
@override @useResult
$Res call({
String id, String type, String resourceIdentifier, int visibility, String accountId, SnAccount account, dynamic data, DateTime createdAt, DateTime updatedAt, dynamic deletedAt
String id, String type, String resourceIdentifier, dynamic data, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
});
@override $SnAccountCopyWith<$Res> get account;
}
/// @nodoc
@ -162,32 +147,20 @@ class __$SnActivityCopyWithImpl<$Res>
/// Create a copy of SnActivity
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? type = null,Object? resourceIdentifier = null,Object? visibility = null,Object? accountId = null,Object? account = null,Object? data = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? type = null,Object? resourceIdentifier = null,Object? data = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
return _then(_SnActivity(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
as String,resourceIdentifier: null == resourceIdentifier ? _self.resourceIdentifier : resourceIdentifier // ignore: cast_nullable_to_non_nullable
as String,visibility: null == visibility ? _self.visibility : visibility // ignore: cast_nullable_to_non_nullable
as int,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable
as String,account: null == account ? _self.account : account // ignore: cast_nullable_to_non_nullable
as SnAccount,data: freezed == data ? _self.data : data // ignore: cast_nullable_to_non_nullable
as String,data: freezed == data ? _self.data : data // ignore: cast_nullable_to_non_nullable
as dynamic,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
as dynamic,
as DateTime?,
));
}
/// Create a copy of SnActivity
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$SnAccountCopyWith<$Res> get account {
return $SnAccountCopyWith<$Res>(_self.account, (value) {
return _then(_self.copyWith(account: value));
});
}
}

View File

@ -10,13 +10,13 @@ _SnActivity _$SnActivityFromJson(Map<String, dynamic> json) => _SnActivity(
id: json['id'] as String,
type: json['type'] as String,
resourceIdentifier: json['resource_identifier'] as String,
visibility: (json['visibility'] as num).toInt(),
accountId: json['account_id'] as String,
account: SnAccount.fromJson(json['account'] as Map<String, dynamic>),
data: json['data'],
createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String),
deletedAt: json['deleted_at'],
deletedAt:
json['deleted_at'] == null
? null
: DateTime.parse(json['deleted_at'] as String),
);
Map<String, dynamic> _$SnActivityToJson(_SnActivity instance) =>
@ -24,13 +24,10 @@ Map<String, dynamic> _$SnActivityToJson(_SnActivity instance) =>
'id': instance.id,
'type': instance.type,
'resource_identifier': instance.resourceIdentifier,
'visibility': instance.visibility,
'account_id': instance.accountId,
'account': instance.account.toJson(),
'data': instance.data,
'created_at': instance.createdAt.toIso8601String(),
'updated_at': instance.updatedAt.toIso8601String(),
'deleted_at': instance.deletedAt,
'deleted_at': instance.deletedAt?.toIso8601String(),
};
_SnCheckInResult _$SnCheckInResultFromJson(Map<String, dynamic> json) =>

View File

@ -4,14 +4,11 @@ part 'auth.freezed.dart';
part 'auth.g.dart';
@freezed
sealed class AppTokenPair with _$AppTokenPair {
const factory AppTokenPair({
required String accessToken,
required String refreshToken,
}) = _AppTokenPair;
sealed class AppToken with _$AppToken {
const factory AppToken({required String token}) = _AppToken;
factory AppTokenPair.fromJson(Map<String, dynamic> json) =>
_$AppTokenPairFromJson(json);
factory AppToken.fromJson(Map<String, dynamic> json) =>
_$AppTokenFromJson(json);
}
@freezed
@ -21,13 +18,18 @@ sealed class SnAuthChallenge with _$SnAuthChallenge {
required DateTime expiredAt,
required int stepRemain,
required int stepTotal,
required int failedAttempts,
required int platform,
required int type,
required List<String> blacklistFactors,
required List<String> audiences,
required List<String> scopes,
required List<dynamic> audiences,
required List<dynamic> scopes,
required String ipAddress,
required String userAgent,
required String? deviceId,
required String deviceId,
required String? nonce,
required String? location,
required String accountId,
required DateTime createdAt,
required DateTime updatedAt,
required DateTime? deletedAt,
@ -37,6 +39,25 @@ sealed class SnAuthChallenge with _$SnAuthChallenge {
_$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
sealed class SnAuthFactor with _$SnAuthFactor {
const factory SnAuthFactor({
@ -45,8 +66,28 @@ sealed class SnAuthFactor with _$SnAuthFactor {
required DateTime createdAt,
required DateTime updatedAt,
required DateTime? deletedAt,
required DateTime? expiredAt,
required DateTime? enabledAt,
required int trustworthy,
required Map<String, dynamic>? createdResponse,
}) = _SnAuthFactor;
factory SnAuthFactor.fromJson(Map<String, dynamic> json) =>
_$SnAuthFactorFromJson(json);
}
@freezed
sealed class SnAuthDevice with _$SnAuthDevice {
const factory SnAuthDevice({
required dynamic label,
required String userAgent,
required String deviceId,
required int platform,
required List<SnAuthSession> sessions,
// Not from backend, used for UI
@Default(false) bool isCurrent,
}) = _SnAuthDevice;
factory SnAuthDevice.fromJson(Map<String, dynamic> json) =>
_$SnAuthDeviceFromJson(json);
}

View File

@ -14,42 +14,42 @@ part of 'auth.dart';
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$AppTokenPair {
mixin _$AppToken {
String get accessToken; String get refreshToken;
/// Create a copy of AppTokenPair
String get token;
/// Create a copy of AppToken
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$AppTokenPairCopyWith<AppTokenPair> get copyWith => _$AppTokenPairCopyWithImpl<AppTokenPair>(this as AppTokenPair, _$identity);
$AppTokenCopyWith<AppToken> get copyWith => _$AppTokenCopyWithImpl<AppToken>(this as AppToken, _$identity);
/// Serializes this AppTokenPair to a JSON map.
/// Serializes this AppToken to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is AppTokenPair&&(identical(other.accessToken, accessToken) || other.accessToken == accessToken)&&(identical(other.refreshToken, refreshToken) || other.refreshToken == refreshToken));
return identical(this, other) || (other.runtimeType == runtimeType&&other is AppToken&&(identical(other.token, token) || other.token == token));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,accessToken,refreshToken);
int get hashCode => Object.hash(runtimeType,token);
@override
String toString() {
return 'AppTokenPair(accessToken: $accessToken, refreshToken: $refreshToken)';
return 'AppToken(token: $token)';
}
}
/// @nodoc
abstract mixin class $AppTokenPairCopyWith<$Res> {
factory $AppTokenPairCopyWith(AppTokenPair value, $Res Function(AppTokenPair) _then) = _$AppTokenPairCopyWithImpl;
abstract mixin class $AppTokenCopyWith<$Res> {
factory $AppTokenCopyWith(AppToken value, $Res Function(AppToken) _then) = _$AppTokenCopyWithImpl;
@useResult
$Res call({
String accessToken, String refreshToken
String token
});
@ -57,19 +57,18 @@ $Res call({
}
/// @nodoc
class _$AppTokenPairCopyWithImpl<$Res>
implements $AppTokenPairCopyWith<$Res> {
_$AppTokenPairCopyWithImpl(this._self, this._then);
class _$AppTokenCopyWithImpl<$Res>
implements $AppTokenCopyWith<$Res> {
_$AppTokenCopyWithImpl(this._self, this._then);
final AppTokenPair _self;
final $Res Function(AppTokenPair) _then;
final AppToken _self;
final $Res Function(AppToken) _then;
/// Create a copy of AppTokenPair
/// Create a copy of AppToken
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? accessToken = null,Object? refreshToken = null,}) {
@pragma('vm:prefer-inline') @override $Res call({Object? token = null,}) {
return _then(_self.copyWith(
accessToken: null == accessToken ? _self.accessToken : accessToken // ignore: cast_nullable_to_non_nullable
as String,refreshToken: null == refreshToken ? _self.refreshToken : refreshToken // ignore: cast_nullable_to_non_nullable
token: null == token ? _self.token : token // ignore: cast_nullable_to_non_nullable
as String,
));
}
@ -80,47 +79,46 @@ as String,
/// @nodoc
@JsonSerializable()
class _AppTokenPair implements AppTokenPair {
const _AppTokenPair({required this.accessToken, required this.refreshToken});
factory _AppTokenPair.fromJson(Map<String, dynamic> json) => _$AppTokenPairFromJson(json);
class _AppToken implements AppToken {
const _AppToken({required this.token});
factory _AppToken.fromJson(Map<String, dynamic> json) => _$AppTokenFromJson(json);
@override final String accessToken;
@override final String refreshToken;
@override final String token;
/// Create a copy of AppTokenPair
/// Create a copy of AppToken
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$AppTokenPairCopyWith<_AppTokenPair> get copyWith => __$AppTokenPairCopyWithImpl<_AppTokenPair>(this, _$identity);
_$AppTokenCopyWith<_AppToken> get copyWith => __$AppTokenCopyWithImpl<_AppToken>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$AppTokenPairToJson(this, );
return _$AppTokenToJson(this, );
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _AppTokenPair&&(identical(other.accessToken, accessToken) || other.accessToken == accessToken)&&(identical(other.refreshToken, refreshToken) || other.refreshToken == refreshToken));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _AppToken&&(identical(other.token, token) || other.token == token));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,accessToken,refreshToken);
int get hashCode => Object.hash(runtimeType,token);
@override
String toString() {
return 'AppTokenPair(accessToken: $accessToken, refreshToken: $refreshToken)';
return 'AppToken(token: $token)';
}
}
/// @nodoc
abstract mixin class _$AppTokenPairCopyWith<$Res> implements $AppTokenPairCopyWith<$Res> {
factory _$AppTokenPairCopyWith(_AppTokenPair value, $Res Function(_AppTokenPair) _then) = __$AppTokenPairCopyWithImpl;
abstract mixin class _$AppTokenCopyWith<$Res> implements $AppTokenCopyWith<$Res> {
factory _$AppTokenCopyWith(_AppToken value, $Res Function(_AppToken) _then) = __$AppTokenCopyWithImpl;
@override @useResult
$Res call({
String accessToken, String refreshToken
String token
});
@ -128,19 +126,18 @@ $Res call({
}
/// @nodoc
class __$AppTokenPairCopyWithImpl<$Res>
implements _$AppTokenPairCopyWith<$Res> {
__$AppTokenPairCopyWithImpl(this._self, this._then);
class __$AppTokenCopyWithImpl<$Res>
implements _$AppTokenCopyWith<$Res> {
__$AppTokenCopyWithImpl(this._self, this._then);
final _AppTokenPair _self;
final $Res Function(_AppTokenPair) _then;
final _AppToken _self;
final $Res Function(_AppToken) _then;
/// Create a copy of AppTokenPair
/// Create a copy of AppToken
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? accessToken = null,Object? refreshToken = null,}) {
return _then(_AppTokenPair(
accessToken: null == accessToken ? _self.accessToken : accessToken // ignore: cast_nullable_to_non_nullable
as String,refreshToken: null == refreshToken ? _self.refreshToken : refreshToken // ignore: cast_nullable_to_non_nullable
@override @pragma('vm:prefer-inline') $Res call({Object? token = null,}) {
return _then(_AppToken(
token: null == token ? _self.token : token // ignore: cast_nullable_to_non_nullable
as String,
));
}
@ -152,7 +149,7 @@ as String,
/// @nodoc
mixin _$SnAuthChallenge {
String get id; DateTime get expiredAt; int get stepRemain; int get stepTotal; List<String> get blacklistFactors; List<String> get audiences; List<String> get scopes; String get ipAddress; String get userAgent; String? get deviceId; String? get nonce; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt;
String get id; DateTime get expiredAt; int get stepRemain; int get stepTotal; int get failedAttempts; int get platform; int get type; List<String> get blacklistFactors; List<dynamic> get audiences; List<dynamic> get scopes; String get ipAddress; String get userAgent; String get deviceId; String? get nonce; String? get location; String get accountId; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt;
/// Create a copy of SnAuthChallenge
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@ -165,16 +162,16 @@ $SnAuthChallengeCopyWith<SnAuthChallenge> get copyWith => _$SnAuthChallengeCopyW
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnAuthChallenge&&(identical(other.id, id) || other.id == id)&&(identical(other.expiredAt, expiredAt) || other.expiredAt == expiredAt)&&(identical(other.stepRemain, stepRemain) || other.stepRemain == stepRemain)&&(identical(other.stepTotal, stepTotal) || other.stepTotal == stepTotal)&&const DeepCollectionEquality().equals(other.blacklistFactors, blacklistFactors)&&const DeepCollectionEquality().equals(other.audiences, audiences)&&const DeepCollectionEquality().equals(other.scopes, scopes)&&(identical(other.ipAddress, ipAddress) || other.ipAddress == ipAddress)&&(identical(other.userAgent, userAgent) || other.userAgent == userAgent)&&(identical(other.deviceId, deviceId) || other.deviceId == deviceId)&&(identical(other.nonce, nonce) || other.nonce == nonce)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnAuthChallenge&&(identical(other.id, id) || other.id == id)&&(identical(other.expiredAt, expiredAt) || other.expiredAt == expiredAt)&&(identical(other.stepRemain, stepRemain) || other.stepRemain == stepRemain)&&(identical(other.stepTotal, stepTotal) || other.stepTotal == stepTotal)&&(identical(other.failedAttempts, failedAttempts) || other.failedAttempts == failedAttempts)&&(identical(other.platform, platform) || other.platform == platform)&&(identical(other.type, type) || other.type == type)&&const DeepCollectionEquality().equals(other.blacklistFactors, blacklistFactors)&&const DeepCollectionEquality().equals(other.audiences, audiences)&&const DeepCollectionEquality().equals(other.scopes, scopes)&&(identical(other.ipAddress, ipAddress) || other.ipAddress == ipAddress)&&(identical(other.userAgent, userAgent) || other.userAgent == userAgent)&&(identical(other.deviceId, deviceId) || other.deviceId == deviceId)&&(identical(other.nonce, nonce) || other.nonce == nonce)&&(identical(other.location, location) || other.location == location)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,id,expiredAt,stepRemain,stepTotal,const DeepCollectionEquality().hash(blacklistFactors),const DeepCollectionEquality().hash(audiences),const DeepCollectionEquality().hash(scopes),ipAddress,userAgent,deviceId,nonce,createdAt,updatedAt,deletedAt);
int get hashCode => Object.hashAll([runtimeType,id,expiredAt,stepRemain,stepTotal,failedAttempts,platform,type,const DeepCollectionEquality().hash(blacklistFactors),const DeepCollectionEquality().hash(audiences),const DeepCollectionEquality().hash(scopes),ipAddress,userAgent,deviceId,nonce,location,accountId,createdAt,updatedAt,deletedAt]);
@override
String toString() {
return 'SnAuthChallenge(id: $id, expiredAt: $expiredAt, stepRemain: $stepRemain, stepTotal: $stepTotal, blacklistFactors: $blacklistFactors, audiences: $audiences, scopes: $scopes, ipAddress: $ipAddress, userAgent: $userAgent, deviceId: $deviceId, nonce: $nonce, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
return 'SnAuthChallenge(id: $id, expiredAt: $expiredAt, stepRemain: $stepRemain, stepTotal: $stepTotal, failedAttempts: $failedAttempts, platform: $platform, type: $type, blacklistFactors: $blacklistFactors, audiences: $audiences, scopes: $scopes, ipAddress: $ipAddress, userAgent: $userAgent, deviceId: $deviceId, nonce: $nonce, location: $location, accountId: $accountId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
}
@ -185,7 +182,7 @@ abstract mixin class $SnAuthChallengeCopyWith<$Res> {
factory $SnAuthChallengeCopyWith(SnAuthChallenge value, $Res Function(SnAuthChallenge) _then) = _$SnAuthChallengeCopyWithImpl;
@useResult
$Res call({
String id, DateTime expiredAt, int stepRemain, int stepTotal, List<String> blacklistFactors, List<String> audiences, List<String> scopes, String ipAddress, String userAgent, String? deviceId, String? nonce, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
String id, DateTime expiredAt, int stepRemain, int stepTotal, int failedAttempts, int platform, int type, List<String> blacklistFactors, List<dynamic> audiences, List<dynamic> scopes, String ipAddress, String userAgent, String deviceId, String? nonce, String? location, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
});
@ -202,20 +199,25 @@ class _$SnAuthChallengeCopyWithImpl<$Res>
/// Create a copy of SnAuthChallenge
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? expiredAt = null,Object? stepRemain = null,Object? stepTotal = null,Object? blacklistFactors = null,Object? audiences = null,Object? scopes = null,Object? ipAddress = null,Object? userAgent = null,Object? deviceId = freezed,Object? nonce = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? expiredAt = null,Object? stepRemain = null,Object? stepTotal = null,Object? failedAttempts = null,Object? platform = null,Object? type = null,Object? blacklistFactors = null,Object? audiences = null,Object? scopes = null,Object? ipAddress = null,Object? userAgent = null,Object? deviceId = null,Object? nonce = freezed,Object? location = freezed,Object? accountId = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
return _then(_self.copyWith(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,expiredAt: null == expiredAt ? _self.expiredAt : expiredAt // ignore: cast_nullable_to_non_nullable
as DateTime,stepRemain: null == stepRemain ? _self.stepRemain : stepRemain // ignore: cast_nullable_to_non_nullable
as int,stepTotal: null == stepTotal ? _self.stepTotal : stepTotal // ignore: cast_nullable_to_non_nullable
as int,failedAttempts: null == failedAttempts ? _self.failedAttempts : failedAttempts // ignore: cast_nullable_to_non_nullable
as int,platform: null == platform ? _self.platform : platform // ignore: cast_nullable_to_non_nullable
as int,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
as int,blacklistFactors: null == blacklistFactors ? _self.blacklistFactors : blacklistFactors // ignore: cast_nullable_to_non_nullable
as List<String>,audiences: null == audiences ? _self.audiences : audiences // ignore: cast_nullable_to_non_nullable
as List<String>,scopes: null == scopes ? _self.scopes : scopes // ignore: cast_nullable_to_non_nullable
as List<String>,ipAddress: null == ipAddress ? _self.ipAddress : ipAddress // ignore: cast_nullable_to_non_nullable
as List<dynamic>,scopes: null == scopes ? _self.scopes : scopes // ignore: cast_nullable_to_non_nullable
as List<dynamic>,ipAddress: null == ipAddress ? _self.ipAddress : ipAddress // ignore: cast_nullable_to_non_nullable
as String,userAgent: null == userAgent ? _self.userAgent : userAgent // ignore: cast_nullable_to_non_nullable
as String,deviceId: freezed == deviceId ? _self.deviceId : deviceId // ignore: cast_nullable_to_non_nullable
as String?,nonce: freezed == nonce ? _self.nonce : nonce // ignore: cast_nullable_to_non_nullable
as String?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
as String,deviceId: null == deviceId ? _self.deviceId : deviceId // ignore: cast_nullable_to_non_nullable
as String,nonce: freezed == nonce ? _self.nonce : nonce // ignore: cast_nullable_to_non_nullable
as String?,location: freezed == location ? _self.location : location // ignore: cast_nullable_to_non_nullable
as String?,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable
as String,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
as DateTime?,
@ -229,13 +231,16 @@ as DateTime?,
@JsonSerializable()
class _SnAuthChallenge implements SnAuthChallenge {
const _SnAuthChallenge({required this.id, required this.expiredAt, required this.stepRemain, required this.stepTotal, required final List<String> blacklistFactors, required final List<String> audiences, required final List<String> scopes, required this.ipAddress, required this.userAgent, required this.deviceId, required this.nonce, required this.createdAt, required this.updatedAt, required this.deletedAt}): _blacklistFactors = blacklistFactors,_audiences = audiences,_scopes = scopes;
const _SnAuthChallenge({required this.id, required this.expiredAt, required this.stepRemain, required this.stepTotal, required this.failedAttempts, required this.platform, required this.type, required final List<String> blacklistFactors, required final List<dynamic> audiences, required final List<dynamic> scopes, required this.ipAddress, required this.userAgent, required this.deviceId, required this.nonce, required this.location, required this.accountId, required this.createdAt, required this.updatedAt, required this.deletedAt}): _blacklistFactors = blacklistFactors,_audiences = audiences,_scopes = scopes;
factory _SnAuthChallenge.fromJson(Map<String, dynamic> json) => _$SnAuthChallengeFromJson(json);
@override final String id;
@override final DateTime expiredAt;
@override final int stepRemain;
@override final int stepTotal;
@override final int failedAttempts;
@override final int platform;
@override final int type;
final List<String> _blacklistFactors;
@override List<String> get blacklistFactors {
if (_blacklistFactors is EqualUnmodifiableListView) return _blacklistFactors;
@ -243,15 +248,15 @@ class _SnAuthChallenge implements SnAuthChallenge {
return EqualUnmodifiableListView(_blacklistFactors);
}
final List<String> _audiences;
@override List<String> get audiences {
final List<dynamic> _audiences;
@override List<dynamic> get audiences {
if (_audiences is EqualUnmodifiableListView) return _audiences;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_audiences);
}
final List<String> _scopes;
@override List<String> get scopes {
final List<dynamic> _scopes;
@override List<dynamic> get scopes {
if (_scopes is EqualUnmodifiableListView) return _scopes;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_scopes);
@ -259,8 +264,10 @@ class _SnAuthChallenge implements SnAuthChallenge {
@override final String ipAddress;
@override final String userAgent;
@override final String? deviceId;
@override final String deviceId;
@override final String? nonce;
@override final String? location;
@override final String accountId;
@override final DateTime createdAt;
@override final DateTime updatedAt;
@override final DateTime? deletedAt;
@ -278,16 +285,16 @@ Map<String, dynamic> toJson() {
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnAuthChallenge&&(identical(other.id, id) || other.id == id)&&(identical(other.expiredAt, expiredAt) || other.expiredAt == expiredAt)&&(identical(other.stepRemain, stepRemain) || other.stepRemain == stepRemain)&&(identical(other.stepTotal, stepTotal) || other.stepTotal == stepTotal)&&const DeepCollectionEquality().equals(other._blacklistFactors, _blacklistFactors)&&const DeepCollectionEquality().equals(other._audiences, _audiences)&&const DeepCollectionEquality().equals(other._scopes, _scopes)&&(identical(other.ipAddress, ipAddress) || other.ipAddress == ipAddress)&&(identical(other.userAgent, userAgent) || other.userAgent == userAgent)&&(identical(other.deviceId, deviceId) || other.deviceId == deviceId)&&(identical(other.nonce, nonce) || other.nonce == nonce)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnAuthChallenge&&(identical(other.id, id) || other.id == id)&&(identical(other.expiredAt, expiredAt) || other.expiredAt == expiredAt)&&(identical(other.stepRemain, stepRemain) || other.stepRemain == stepRemain)&&(identical(other.stepTotal, stepTotal) || other.stepTotal == stepTotal)&&(identical(other.failedAttempts, failedAttempts) || other.failedAttempts == failedAttempts)&&(identical(other.platform, platform) || other.platform == platform)&&(identical(other.type, type) || other.type == type)&&const DeepCollectionEquality().equals(other._blacklistFactors, _blacklistFactors)&&const DeepCollectionEquality().equals(other._audiences, _audiences)&&const DeepCollectionEquality().equals(other._scopes, _scopes)&&(identical(other.ipAddress, ipAddress) || other.ipAddress == ipAddress)&&(identical(other.userAgent, userAgent) || other.userAgent == userAgent)&&(identical(other.deviceId, deviceId) || other.deviceId == deviceId)&&(identical(other.nonce, nonce) || other.nonce == nonce)&&(identical(other.location, location) || other.location == location)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,id,expiredAt,stepRemain,stepTotal,const DeepCollectionEquality().hash(_blacklistFactors),const DeepCollectionEquality().hash(_audiences),const DeepCollectionEquality().hash(_scopes),ipAddress,userAgent,deviceId,nonce,createdAt,updatedAt,deletedAt);
int get hashCode => Object.hashAll([runtimeType,id,expiredAt,stepRemain,stepTotal,failedAttempts,platform,type,const DeepCollectionEquality().hash(_blacklistFactors),const DeepCollectionEquality().hash(_audiences),const DeepCollectionEquality().hash(_scopes),ipAddress,userAgent,deviceId,nonce,location,accountId,createdAt,updatedAt,deletedAt]);
@override
String toString() {
return 'SnAuthChallenge(id: $id, expiredAt: $expiredAt, stepRemain: $stepRemain, stepTotal: $stepTotal, blacklistFactors: $blacklistFactors, audiences: $audiences, scopes: $scopes, ipAddress: $ipAddress, userAgent: $userAgent, deviceId: $deviceId, nonce: $nonce, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
return 'SnAuthChallenge(id: $id, expiredAt: $expiredAt, stepRemain: $stepRemain, stepTotal: $stepTotal, failedAttempts: $failedAttempts, platform: $platform, type: $type, blacklistFactors: $blacklistFactors, audiences: $audiences, scopes: $scopes, ipAddress: $ipAddress, userAgent: $userAgent, deviceId: $deviceId, nonce: $nonce, location: $location, accountId: $accountId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
}
@ -298,7 +305,7 @@ abstract mixin class _$SnAuthChallengeCopyWith<$Res> implements $SnAuthChallenge
factory _$SnAuthChallengeCopyWith(_SnAuthChallenge value, $Res Function(_SnAuthChallenge) _then) = __$SnAuthChallengeCopyWithImpl;
@override @useResult
$Res call({
String id, DateTime expiredAt, int stepRemain, int stepTotal, List<String> blacklistFactors, List<String> audiences, List<String> scopes, String ipAddress, String userAgent, String? deviceId, String? nonce, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
String id, DateTime expiredAt, int stepRemain, int stepTotal, int failedAttempts, int platform, int type, List<String> blacklistFactors, List<dynamic> audiences, List<dynamic> scopes, String ipAddress, String userAgent, String deviceId, String? nonce, String? location, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
});
@ -315,20 +322,25 @@ class __$SnAuthChallengeCopyWithImpl<$Res>
/// Create a copy of SnAuthChallenge
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? expiredAt = null,Object? stepRemain = null,Object? stepTotal = null,Object? blacklistFactors = null,Object? audiences = null,Object? scopes = null,Object? ipAddress = null,Object? userAgent = null,Object? deviceId = freezed,Object? nonce = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? expiredAt = null,Object? stepRemain = null,Object? stepTotal = null,Object? failedAttempts = null,Object? platform = null,Object? type = null,Object? blacklistFactors = null,Object? audiences = null,Object? scopes = null,Object? ipAddress = null,Object? userAgent = null,Object? deviceId = null,Object? nonce = freezed,Object? location = freezed,Object? accountId = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
return _then(_SnAuthChallenge(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,expiredAt: null == expiredAt ? _self.expiredAt : expiredAt // ignore: cast_nullable_to_non_nullable
as DateTime,stepRemain: null == stepRemain ? _self.stepRemain : stepRemain // ignore: cast_nullable_to_non_nullable
as int,stepTotal: null == stepTotal ? _self.stepTotal : stepTotal // ignore: cast_nullable_to_non_nullable
as int,failedAttempts: null == failedAttempts ? _self.failedAttempts : failedAttempts // ignore: cast_nullable_to_non_nullable
as int,platform: null == platform ? _self.platform : platform // ignore: cast_nullable_to_non_nullable
as int,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
as int,blacklistFactors: null == blacklistFactors ? _self._blacklistFactors : blacklistFactors // ignore: cast_nullable_to_non_nullable
as List<String>,audiences: null == audiences ? _self._audiences : audiences // ignore: cast_nullable_to_non_nullable
as List<String>,scopes: null == scopes ? _self._scopes : scopes // ignore: cast_nullable_to_non_nullable
as List<String>,ipAddress: null == ipAddress ? _self.ipAddress : ipAddress // ignore: cast_nullable_to_non_nullable
as List<dynamic>,scopes: null == scopes ? _self._scopes : scopes // ignore: cast_nullable_to_non_nullable
as List<dynamic>,ipAddress: null == ipAddress ? _self.ipAddress : ipAddress // ignore: cast_nullable_to_non_nullable
as String,userAgent: null == userAgent ? _self.userAgent : userAgent // ignore: cast_nullable_to_non_nullable
as String,deviceId: freezed == deviceId ? _self.deviceId : deviceId // ignore: cast_nullable_to_non_nullable
as String?,nonce: freezed == nonce ? _self.nonce : nonce // ignore: cast_nullable_to_non_nullable
as String?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
as String,deviceId: null == deviceId ? _self.deviceId : deviceId // ignore: cast_nullable_to_non_nullable
as String,nonce: freezed == nonce ? _self.nonce : nonce // ignore: cast_nullable_to_non_nullable
as String?,location: freezed == location ? _self.location : location // ignore: cast_nullable_to_non_nullable
as String?,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable
as String,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
as DateTime?,
@ -339,10 +351,188 @@ as DateTime?,
}
/// @nodoc
mixin _$SnAuthSession {
String get id; String? get label; DateTime get lastGrantedAt; DateTime get expiredAt; String get accountId; String get challengeId; SnAuthChallenge get challenge; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt;
/// Create a copy of SnAuthSession
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$SnAuthSessionCopyWith<SnAuthSession> get copyWith => _$SnAuthSessionCopyWithImpl<SnAuthSession>(this as SnAuthSession, _$identity);
/// Serializes this SnAuthSession to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnAuthSession&&(identical(other.id, id) || other.id == id)&&(identical(other.label, label) || other.label == label)&&(identical(other.lastGrantedAt, lastGrantedAt) || other.lastGrantedAt == lastGrantedAt)&&(identical(other.expiredAt, expiredAt) || other.expiredAt == expiredAt)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.challengeId, challengeId) || other.challengeId == challengeId)&&(identical(other.challenge, challenge) || other.challenge == challenge)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,id,label,lastGrantedAt,expiredAt,accountId,challengeId,challenge,createdAt,updatedAt,deletedAt);
@override
String toString() {
return 'SnAuthSession(id: $id, label: $label, lastGrantedAt: $lastGrantedAt, expiredAt: $expiredAt, accountId: $accountId, challengeId: $challengeId, challenge: $challenge, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
}
}
/// @nodoc
abstract mixin class $SnAuthSessionCopyWith<$Res> {
factory $SnAuthSessionCopyWith(SnAuthSession value, $Res Function(SnAuthSession) _then) = _$SnAuthSessionCopyWithImpl;
@useResult
$Res call({
String id, String? label, DateTime lastGrantedAt, DateTime expiredAt, String accountId, String challengeId, SnAuthChallenge challenge, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
});
$SnAuthChallengeCopyWith<$Res> get challenge;
}
/// @nodoc
class _$SnAuthSessionCopyWithImpl<$Res>
implements $SnAuthSessionCopyWith<$Res> {
_$SnAuthSessionCopyWithImpl(this._self, this._then);
final SnAuthSession _self;
final $Res Function(SnAuthSession) _then;
/// Create a copy of SnAuthSession
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? label = freezed,Object? lastGrantedAt = null,Object? expiredAt = null,Object? accountId = null,Object? challengeId = null,Object? challenge = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
return _then(_self.copyWith(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,label: freezed == label ? _self.label : label // ignore: cast_nullable_to_non_nullable
as String?,lastGrantedAt: null == lastGrantedAt ? _self.lastGrantedAt : lastGrantedAt // ignore: cast_nullable_to_non_nullable
as DateTime,expiredAt: null == expiredAt ? _self.expiredAt : expiredAt // ignore: cast_nullable_to_non_nullable
as DateTime,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable
as String,challengeId: null == challengeId ? _self.challengeId : challengeId // ignore: cast_nullable_to_non_nullable
as String,challenge: null == challenge ? _self.challenge : challenge // ignore: cast_nullable_to_non_nullable
as SnAuthChallenge,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
as DateTime?,
));
}
/// Create a copy of SnAuthSession
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$SnAuthChallengeCopyWith<$Res> get challenge {
return $SnAuthChallengeCopyWith<$Res>(_self.challenge, (value) {
return _then(_self.copyWith(challenge: value));
});
}
}
/// @nodoc
@JsonSerializable()
class _SnAuthSession implements SnAuthSession {
const _SnAuthSession({required this.id, required this.label, required this.lastGrantedAt, required this.expiredAt, required this.accountId, required this.challengeId, required this.challenge, required this.createdAt, required this.updatedAt, required this.deletedAt});
factory _SnAuthSession.fromJson(Map<String, dynamic> json) => _$SnAuthSessionFromJson(json);
@override final String id;
@override final String? label;
@override final DateTime lastGrantedAt;
@override final DateTime expiredAt;
@override final String accountId;
@override final String challengeId;
@override final SnAuthChallenge challenge;
@override final DateTime createdAt;
@override final DateTime updatedAt;
@override final DateTime? deletedAt;
/// Create a copy of SnAuthSession
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$SnAuthSessionCopyWith<_SnAuthSession> get copyWith => __$SnAuthSessionCopyWithImpl<_SnAuthSession>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$SnAuthSessionToJson(this, );
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnAuthSession&&(identical(other.id, id) || other.id == id)&&(identical(other.label, label) || other.label == label)&&(identical(other.lastGrantedAt, lastGrantedAt) || other.lastGrantedAt == lastGrantedAt)&&(identical(other.expiredAt, expiredAt) || other.expiredAt == expiredAt)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.challengeId, challengeId) || other.challengeId == challengeId)&&(identical(other.challenge, challenge) || other.challenge == challenge)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,id,label,lastGrantedAt,expiredAt,accountId,challengeId,challenge,createdAt,updatedAt,deletedAt);
@override
String toString() {
return 'SnAuthSession(id: $id, label: $label, lastGrantedAt: $lastGrantedAt, expiredAt: $expiredAt, accountId: $accountId, challengeId: $challengeId, challenge: $challenge, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
}
}
/// @nodoc
abstract mixin class _$SnAuthSessionCopyWith<$Res> implements $SnAuthSessionCopyWith<$Res> {
factory _$SnAuthSessionCopyWith(_SnAuthSession value, $Res Function(_SnAuthSession) _then) = __$SnAuthSessionCopyWithImpl;
@override @useResult
$Res call({
String id, String? label, DateTime lastGrantedAt, DateTime expiredAt, String accountId, String challengeId, SnAuthChallenge challenge, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
});
@override $SnAuthChallengeCopyWith<$Res> get challenge;
}
/// @nodoc
class __$SnAuthSessionCopyWithImpl<$Res>
implements _$SnAuthSessionCopyWith<$Res> {
__$SnAuthSessionCopyWithImpl(this._self, this._then);
final _SnAuthSession _self;
final $Res Function(_SnAuthSession) _then;
/// Create a copy of SnAuthSession
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? label = freezed,Object? lastGrantedAt = null,Object? expiredAt = null,Object? accountId = null,Object? challengeId = null,Object? challenge = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
return _then(_SnAuthSession(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,label: freezed == label ? _self.label : label // ignore: cast_nullable_to_non_nullable
as String?,lastGrantedAt: null == lastGrantedAt ? _self.lastGrantedAt : lastGrantedAt // ignore: cast_nullable_to_non_nullable
as DateTime,expiredAt: null == expiredAt ? _self.expiredAt : expiredAt // ignore: cast_nullable_to_non_nullable
as DateTime,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable
as String,challengeId: null == challengeId ? _self.challengeId : challengeId // ignore: cast_nullable_to_non_nullable
as String,challenge: null == challenge ? _self.challenge : challenge // ignore: cast_nullable_to_non_nullable
as SnAuthChallenge,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
as DateTime?,
));
}
/// Create a copy of SnAuthSession
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$SnAuthChallengeCopyWith<$Res> get challenge {
return $SnAuthChallengeCopyWith<$Res>(_self.challenge, (value) {
return _then(_self.copyWith(challenge: value));
});
}
}
/// @nodoc
mixin _$SnAuthFactor {
String get id; int get type; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt;
String get id; int get type; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; DateTime? get expiredAt; DateTime? get enabledAt; int get trustworthy; Map<String, dynamic>? get createdResponse;
/// Create a copy of SnAuthFactor
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@ -355,16 +545,16 @@ $SnAuthFactorCopyWith<SnAuthFactor> get copyWith => _$SnAuthFactorCopyWithImpl<S
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnAuthFactor&&(identical(other.id, id) || other.id == id)&&(identical(other.type, type) || other.type == type)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnAuthFactor&&(identical(other.id, id) || other.id == id)&&(identical(other.type, type) || other.type == type)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.expiredAt, expiredAt) || other.expiredAt == expiredAt)&&(identical(other.enabledAt, enabledAt) || other.enabledAt == enabledAt)&&(identical(other.trustworthy, trustworthy) || other.trustworthy == trustworthy)&&const DeepCollectionEquality().equals(other.createdResponse, createdResponse));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,id,type,createdAt,updatedAt,deletedAt);
int get hashCode => Object.hash(runtimeType,id,type,createdAt,updatedAt,deletedAt,expiredAt,enabledAt,trustworthy,const DeepCollectionEquality().hash(createdResponse));
@override
String toString() {
return 'SnAuthFactor(id: $id, type: $type, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
return 'SnAuthFactor(id: $id, type: $type, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, expiredAt: $expiredAt, enabledAt: $enabledAt, trustworthy: $trustworthy, createdResponse: $createdResponse)';
}
@ -375,7 +565,7 @@ abstract mixin class $SnAuthFactorCopyWith<$Res> {
factory $SnAuthFactorCopyWith(SnAuthFactor value, $Res Function(SnAuthFactor) _then) = _$SnAuthFactorCopyWithImpl;
@useResult
$Res call({
String id, int type, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
String id, int type, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, DateTime? expiredAt, DateTime? enabledAt, int trustworthy, Map<String, dynamic>? createdResponse
});
@ -392,14 +582,18 @@ class _$SnAuthFactorCopyWithImpl<$Res>
/// Create a copy of SnAuthFactor
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? type = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? type = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? expiredAt = freezed,Object? enabledAt = freezed,Object? trustworthy = null,Object? createdResponse = freezed,}) {
return _then(_self.copyWith(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
as int,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
as DateTime?,
as DateTime?,expiredAt: freezed == expiredAt ? _self.expiredAt : expiredAt // ignore: cast_nullable_to_non_nullable
as DateTime?,enabledAt: freezed == enabledAt ? _self.enabledAt : enabledAt // ignore: cast_nullable_to_non_nullable
as DateTime?,trustworthy: null == trustworthy ? _self.trustworthy : trustworthy // ignore: cast_nullable_to_non_nullable
as int,createdResponse: freezed == createdResponse ? _self.createdResponse : createdResponse // ignore: cast_nullable_to_non_nullable
as Map<String, dynamic>?,
));
}
@ -410,7 +604,7 @@ as DateTime?,
@JsonSerializable()
class _SnAuthFactor implements SnAuthFactor {
const _SnAuthFactor({required this.id, required this.type, required this.createdAt, required this.updatedAt, required this.deletedAt});
const _SnAuthFactor({required this.id, required this.type, required this.createdAt, required this.updatedAt, required this.deletedAt, required this.expiredAt, required this.enabledAt, required this.trustworthy, required final Map<String, dynamic>? createdResponse}): _createdResponse = createdResponse;
factory _SnAuthFactor.fromJson(Map<String, dynamic> json) => _$SnAuthFactorFromJson(json);
@override final String id;
@ -418,6 +612,18 @@ class _SnAuthFactor implements SnAuthFactor {
@override final DateTime createdAt;
@override final DateTime updatedAt;
@override final DateTime? deletedAt;
@override final DateTime? expiredAt;
@override final DateTime? enabledAt;
@override final int trustworthy;
final Map<String, dynamic>? _createdResponse;
@override Map<String, dynamic>? get createdResponse {
final value = _createdResponse;
if (value == null) return null;
if (_createdResponse is EqualUnmodifiableMapView) return _createdResponse;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(value);
}
/// Create a copy of SnAuthFactor
/// with the given fields replaced by the non-null parameter values.
@ -432,16 +638,16 @@ Map<String, dynamic> toJson() {
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnAuthFactor&&(identical(other.id, id) || other.id == id)&&(identical(other.type, type) || other.type == type)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnAuthFactor&&(identical(other.id, id) || other.id == id)&&(identical(other.type, type) || other.type == type)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.expiredAt, expiredAt) || other.expiredAt == expiredAt)&&(identical(other.enabledAt, enabledAt) || other.enabledAt == enabledAt)&&(identical(other.trustworthy, trustworthy) || other.trustworthy == trustworthy)&&const DeepCollectionEquality().equals(other._createdResponse, _createdResponse));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,id,type,createdAt,updatedAt,deletedAt);
int get hashCode => Object.hash(runtimeType,id,type,createdAt,updatedAt,deletedAt,expiredAt,enabledAt,trustworthy,const DeepCollectionEquality().hash(_createdResponse));
@override
String toString() {
return 'SnAuthFactor(id: $id, type: $type, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
return 'SnAuthFactor(id: $id, type: $type, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, expiredAt: $expiredAt, enabledAt: $enabledAt, trustworthy: $trustworthy, createdResponse: $createdResponse)';
}
@ -452,7 +658,7 @@ abstract mixin class _$SnAuthFactorCopyWith<$Res> implements $SnAuthFactorCopyWi
factory _$SnAuthFactorCopyWith(_SnAuthFactor value, $Res Function(_SnAuthFactor) _then) = __$SnAuthFactorCopyWithImpl;
@override @useResult
$Res call({
String id, int type, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
String id, int type, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, DateTime? expiredAt, DateTime? enabledAt, int trustworthy, Map<String, dynamic>? createdResponse
});
@ -469,14 +675,174 @@ class __$SnAuthFactorCopyWithImpl<$Res>
/// Create a copy of SnAuthFactor
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? type = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? type = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? expiredAt = freezed,Object? enabledAt = freezed,Object? trustworthy = null,Object? createdResponse = freezed,}) {
return _then(_SnAuthFactor(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
as int,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
as DateTime?,
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,
));
}

View File

@ -6,17 +6,12 @@ part of 'auth.dart';
// JsonSerializableGenerator
// **************************************************************************
_AppTokenPair _$AppTokenPairFromJson(Map<String, dynamic> json) =>
_AppTokenPair(
accessToken: json['access_token'] as String,
refreshToken: json['refresh_token'] as String,
);
_AppToken _$AppTokenFromJson(Map<String, dynamic> json) =>
_AppToken(token: json['token'] as String);
Map<String, dynamic> _$AppTokenPairToJson(_AppTokenPair instance) =>
<String, dynamic>{
'access_token': instance.accessToken,
'refresh_token': instance.refreshToken,
};
Map<String, dynamic> _$AppTokenToJson(_AppToken instance) => <String, dynamic>{
'token': instance.token,
};
_SnAuthChallenge _$SnAuthChallengeFromJson(Map<String, dynamic> json) =>
_SnAuthChallenge(
@ -24,18 +19,21 @@ _SnAuthChallenge _$SnAuthChallengeFromJson(Map<String, dynamic> json) =>
expiredAt: DateTime.parse(json['expired_at'] as String),
stepRemain: (json['step_remain'] as num).toInt(),
stepTotal: (json['step_total'] as num).toInt(),
failedAttempts: (json['failed_attempts'] as num).toInt(),
platform: (json['platform'] as num).toInt(),
type: (json['type'] as num).toInt(),
blacklistFactors:
(json['blacklist_factors'] as List<dynamic>)
.map((e) => e as String)
.toList(),
audiences:
(json['audiences'] as List<dynamic>).map((e) => e as String).toList(),
scopes:
(json['scopes'] as List<dynamic>).map((e) => e as String).toList(),
audiences: json['audiences'] as List<dynamic>,
scopes: json['scopes'] as List<dynamic>,
ipAddress: json['ip_address'] as String,
userAgent: json['user_agent'] as String,
deviceId: json['device_id'] as String?,
deviceId: json['device_id'] as String,
nonce: json['nonce'] as String?,
location: json['location'] as String?,
accountId: json['account_id'] as String,
createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String),
deletedAt:
@ -50,6 +48,9 @@ Map<String, dynamic> _$SnAuthChallengeToJson(_SnAuthChallenge instance) =>
'expired_at': instance.expiredAt.toIso8601String(),
'step_remain': instance.stepRemain,
'step_total': instance.stepTotal,
'failed_attempts': instance.failedAttempts,
'platform': instance.platform,
'type': instance.type,
'blacklist_factors': instance.blacklistFactors,
'audiences': instance.audiences,
'scopes': instance.scopes,
@ -57,6 +58,41 @@ Map<String, dynamic> _$SnAuthChallengeToJson(_SnAuthChallenge instance) =>
'user_agent': instance.userAgent,
'device_id': instance.deviceId,
'nonce': instance.nonce,
'location': instance.location,
'account_id': instance.accountId,
'created_at': instance.createdAt.toIso8601String(),
'updated_at': instance.updatedAt.toIso8601String(),
'deleted_at': instance.deletedAt?.toIso8601String(),
};
_SnAuthSession _$SnAuthSessionFromJson(Map<String, dynamic> json) =>
_SnAuthSession(
id: json['id'] as String,
label: json['label'] as String?,
lastGrantedAt: DateTime.parse(json['last_granted_at'] as String),
expiredAt: DateTime.parse(json['expired_at'] as String),
accountId: json['account_id'] as String,
challengeId: json['challenge_id'] as String,
challenge: SnAuthChallenge.fromJson(
json['challenge'] as Map<String, dynamic>,
),
createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String),
deletedAt:
json['deleted_at'] == null
? null
: DateTime.parse(json['deleted_at'] as String),
);
Map<String, dynamic> _$SnAuthSessionToJson(_SnAuthSession instance) =>
<String, dynamic>{
'id': instance.id,
'label': instance.label,
'last_granted_at': instance.lastGrantedAt.toIso8601String(),
'expired_at': instance.expiredAt.toIso8601String(),
'account_id': instance.accountId,
'challenge_id': instance.challengeId,
'challenge': instance.challenge.toJson(),
'created_at': instance.createdAt.toIso8601String(),
'updated_at': instance.updatedAt.toIso8601String(),
'deleted_at': instance.deletedAt?.toIso8601String(),
@ -72,6 +108,16 @@ _SnAuthFactor _$SnAuthFactorFromJson(Map<String, dynamic> json) =>
json['deleted_at'] == null
? null
: DateTime.parse(json['deleted_at'] as String),
expiredAt:
json['expired_at'] == null
? null
: DateTime.parse(json['expired_at'] as String),
enabledAt:
json['enabled_at'] == null
? null
: DateTime.parse(json['enabled_at'] as String),
trustworthy: (json['trustworthy'] as num).toInt(),
createdResponse: json['created_response'] as Map<String, dynamic>?,
);
Map<String, dynamic> _$SnAuthFactorToJson(_SnAuthFactor instance) =>
@ -81,4 +127,31 @@ Map<String, dynamic> _$SnAuthFactorToJson(_SnAuthFactor instance) =>
'created_at': instance.createdAt.toIso8601String(),
'updated_at': instance.updatedAt.toIso8601String(),
'deleted_at': instance.deletedAt?.toIso8601String(),
'expired_at': instance.expiredAt?.toIso8601String(),
'enabled_at': instance.enabledAt?.toIso8601String(),
'trustworthy': instance.trustworthy,
'created_response': instance.createdResponse,
};
_SnAuthDevice _$SnAuthDeviceFromJson(Map<String, dynamic> json) =>
_SnAuthDevice(
label: json['label'],
userAgent: json['user_agent'] as String,
deviceId: json['device_id'] as String,
platform: (json['platform'] as num).toInt(),
sessions:
(json['sessions'] as List<dynamic>)
.map((e) => SnAuthSession.fromJson(e as Map<String, dynamic>))
.toList(),
isCurrent: json['is_current'] as bool? ?? false,
);
Map<String, dynamic> _$SnAuthDeviceToJson(_SnAuthDevice instance) =>
<String, dynamic>{
'label': instance.label,
'user_agent': instance.userAgent,
'device_id': instance.deviceId,
'platform': instance.platform,
'sessions': instance.sessions.map((e) => e.toJson()).toList(),
'is_current': instance.isCurrent,
};

View File

@ -1,3 +1,6 @@
import 'dart:convert';
import 'dart:developer';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:island/models/file.dart';
import 'package:island/models/realm.dart';
@ -14,9 +17,7 @@ sealed class SnChatRoom with _$SnChatRoom {
required String? description,
required int type,
required bool isPublic,
required String? pictureId,
required SnCloudFile? picture,
required String? backgroundId,
required SnCloudFile? background,
required String? realmId,
required SnRealm? realm,
@ -90,6 +91,7 @@ sealed class SnChatMember with _$SnChatMember {
required int notify,
required DateTime? joinedAt,
required bool isBot,
DateTime? lastTyped,
}) = _SnChatMember;
factory SnChatMember.fromJson(Map<String, dynamic> json) =>

View File

@ -16,7 +16,7 @@ T _$identity<T>(T value) => value;
/// @nodoc
mixin _$SnChatRoom {
String get id; String? get name; String? get description; int get type; bool get isPublic; String? get pictureId; SnCloudFile? get picture; String? get backgroundId; SnCloudFile? get background; String? get realmId; SnRealm? get realm; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; List<SnChatMember>? get members;
String get id; String? get name; String? get description; int get type; bool get isPublic; SnCloudFile? get picture; SnCloudFile? get background; String? get realmId; SnRealm? get realm; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; List<SnChatMember>? get members;
/// Create a copy of SnChatRoom
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@ -29,16 +29,16 @@ $SnChatRoomCopyWith<SnChatRoom> get copyWith => _$SnChatRoomCopyWithImpl<SnChatR
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnChatRoom&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&(identical(other.type, type) || other.type == type)&&(identical(other.isPublic, isPublic) || other.isPublic == isPublic)&&(identical(other.pictureId, pictureId) || other.pictureId == pictureId)&&(identical(other.picture, picture) || other.picture == picture)&&(identical(other.backgroundId, backgroundId) || other.backgroundId == backgroundId)&&(identical(other.background, background) || other.background == background)&&(identical(other.realmId, realmId) || other.realmId == realmId)&&(identical(other.realm, realm) || other.realm == realm)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&const DeepCollectionEquality().equals(other.members, members));
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnChatRoom&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&(identical(other.type, type) || other.type == type)&&(identical(other.isPublic, isPublic) || other.isPublic == isPublic)&&(identical(other.picture, picture) || other.picture == picture)&&(identical(other.background, background) || other.background == background)&&(identical(other.realmId, realmId) || other.realmId == realmId)&&(identical(other.realm, realm) || other.realm == realm)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&const DeepCollectionEquality().equals(other.members, members));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,id,name,description,type,isPublic,pictureId,picture,backgroundId,background,realmId,realm,createdAt,updatedAt,deletedAt,const DeepCollectionEquality().hash(members));
int get hashCode => Object.hash(runtimeType,id,name,description,type,isPublic,picture,background,realmId,realm,createdAt,updatedAt,deletedAt,const DeepCollectionEquality().hash(members));
@override
String toString() {
return 'SnChatRoom(id: $id, name: $name, description: $description, type: $type, isPublic: $isPublic, pictureId: $pictureId, picture: $picture, backgroundId: $backgroundId, background: $background, realmId: $realmId, realm: $realm, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, members: $members)';
return 'SnChatRoom(id: $id, name: $name, description: $description, type: $type, isPublic: $isPublic, picture: $picture, background: $background, realmId: $realmId, realm: $realm, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, members: $members)';
}
@ -49,7 +49,7 @@ abstract mixin class $SnChatRoomCopyWith<$Res> {
factory $SnChatRoomCopyWith(SnChatRoom value, $Res Function(SnChatRoom) _then) = _$SnChatRoomCopyWithImpl;
@useResult
$Res call({
String id, String? name, String? description, int type, bool isPublic, String? pictureId, SnCloudFile? picture, String? backgroundId, SnCloudFile? background, String? realmId, SnRealm? realm, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, List<SnChatMember>? members
String id, String? name, String? description, int type, bool isPublic, SnCloudFile? picture, SnCloudFile? background, String? realmId, SnRealm? realm, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, List<SnChatMember>? members
});
@ -66,17 +66,15 @@ class _$SnChatRoomCopyWithImpl<$Res>
/// Create a copy of SnChatRoom
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? name = freezed,Object? description = freezed,Object? type = null,Object? isPublic = null,Object? pictureId = freezed,Object? picture = freezed,Object? backgroundId = freezed,Object? background = freezed,Object? realmId = freezed,Object? realm = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? members = freezed,}) {
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? name = freezed,Object? description = freezed,Object? type = null,Object? isPublic = null,Object? picture = freezed,Object? background = freezed,Object? realmId = freezed,Object? realm = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? members = freezed,}) {
return _then(_self.copyWith(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,name: freezed == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
as String?,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
as String?,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
as int,isPublic: null == isPublic ? _self.isPublic : isPublic // ignore: cast_nullable_to_non_nullable
as bool,pictureId: freezed == pictureId ? _self.pictureId : pictureId // ignore: cast_nullable_to_non_nullable
as String?,picture: freezed == picture ? _self.picture : picture // ignore: cast_nullable_to_non_nullable
as SnCloudFile?,backgroundId: freezed == backgroundId ? _self.backgroundId : backgroundId // ignore: cast_nullable_to_non_nullable
as String?,background: freezed == background ? _self.background : background // ignore: cast_nullable_to_non_nullable
as bool,picture: freezed == picture ? _self.picture : picture // ignore: cast_nullable_to_non_nullable
as SnCloudFile?,background: freezed == background ? _self.background : background // ignore: cast_nullable_to_non_nullable
as SnCloudFile?,realmId: freezed == realmId ? _self.realmId : realmId // ignore: cast_nullable_to_non_nullable
as String?,realm: freezed == realm ? _self.realm : realm // ignore: cast_nullable_to_non_nullable
as SnRealm?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
@ -130,7 +128,7 @@ $SnRealmCopyWith<$Res>? get realm {
@JsonSerializable()
class _SnChatRoom implements SnChatRoom {
const _SnChatRoom({required this.id, required this.name, required this.description, required this.type, required this.isPublic, required this.pictureId, required this.picture, required this.backgroundId, required this.background, required this.realmId, required this.realm, required this.createdAt, required this.updatedAt, required this.deletedAt, required final List<SnChatMember>? members}): _members = members;
const _SnChatRoom({required this.id, required this.name, required this.description, required this.type, required this.isPublic, required this.picture, required this.background, required this.realmId, required this.realm, required this.createdAt, required this.updatedAt, required this.deletedAt, required final List<SnChatMember>? members}): _members = members;
factory _SnChatRoom.fromJson(Map<String, dynamic> json) => _$SnChatRoomFromJson(json);
@override final String id;
@ -138,9 +136,7 @@ class _SnChatRoom implements SnChatRoom {
@override final String? description;
@override final int type;
@override final bool isPublic;
@override final String? pictureId;
@override final SnCloudFile? picture;
@override final String? backgroundId;
@override final SnCloudFile? background;
@override final String? realmId;
@override final SnRealm? realm;
@ -170,16 +166,16 @@ Map<String, dynamic> toJson() {
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnChatRoom&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&(identical(other.type, type) || other.type == type)&&(identical(other.isPublic, isPublic) || other.isPublic == isPublic)&&(identical(other.pictureId, pictureId) || other.pictureId == pictureId)&&(identical(other.picture, picture) || other.picture == picture)&&(identical(other.backgroundId, backgroundId) || other.backgroundId == backgroundId)&&(identical(other.background, background) || other.background == background)&&(identical(other.realmId, realmId) || other.realmId == realmId)&&(identical(other.realm, realm) || other.realm == realm)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&const DeepCollectionEquality().equals(other._members, _members));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnChatRoom&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&(identical(other.type, type) || other.type == type)&&(identical(other.isPublic, isPublic) || other.isPublic == isPublic)&&(identical(other.picture, picture) || other.picture == picture)&&(identical(other.background, background) || other.background == background)&&(identical(other.realmId, realmId) || other.realmId == realmId)&&(identical(other.realm, realm) || other.realm == realm)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&const DeepCollectionEquality().equals(other._members, _members));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,id,name,description,type,isPublic,pictureId,picture,backgroundId,background,realmId,realm,createdAt,updatedAt,deletedAt,const DeepCollectionEquality().hash(_members));
int get hashCode => Object.hash(runtimeType,id,name,description,type,isPublic,picture,background,realmId,realm,createdAt,updatedAt,deletedAt,const DeepCollectionEquality().hash(_members));
@override
String toString() {
return 'SnChatRoom(id: $id, name: $name, description: $description, type: $type, isPublic: $isPublic, pictureId: $pictureId, picture: $picture, backgroundId: $backgroundId, background: $background, realmId: $realmId, realm: $realm, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, members: $members)';
return 'SnChatRoom(id: $id, name: $name, description: $description, type: $type, isPublic: $isPublic, picture: $picture, background: $background, realmId: $realmId, realm: $realm, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, members: $members)';
}
@ -190,7 +186,7 @@ abstract mixin class _$SnChatRoomCopyWith<$Res> implements $SnChatRoomCopyWith<$
factory _$SnChatRoomCopyWith(_SnChatRoom value, $Res Function(_SnChatRoom) _then) = __$SnChatRoomCopyWithImpl;
@override @useResult
$Res call({
String id, String? name, String? description, int type, bool isPublic, String? pictureId, SnCloudFile? picture, String? backgroundId, SnCloudFile? background, String? realmId, SnRealm? realm, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, List<SnChatMember>? members
String id, String? name, String? description, int type, bool isPublic, SnCloudFile? picture, SnCloudFile? background, String? realmId, SnRealm? realm, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, List<SnChatMember>? members
});
@ -207,17 +203,15 @@ class __$SnChatRoomCopyWithImpl<$Res>
/// Create a copy of SnChatRoom
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? name = freezed,Object? description = freezed,Object? type = null,Object? isPublic = null,Object? pictureId = freezed,Object? picture = freezed,Object? backgroundId = freezed,Object? background = freezed,Object? realmId = freezed,Object? realm = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? members = freezed,}) {
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? name = freezed,Object? description = freezed,Object? type = null,Object? isPublic = null,Object? picture = freezed,Object? background = freezed,Object? realmId = freezed,Object? realm = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? members = freezed,}) {
return _then(_SnChatRoom(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,name: freezed == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
as String?,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
as String?,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
as int,isPublic: null == isPublic ? _self.isPublic : isPublic // ignore: cast_nullable_to_non_nullable
as bool,pictureId: freezed == pictureId ? _self.pictureId : pictureId // ignore: cast_nullable_to_non_nullable
as String?,picture: freezed == picture ? _self.picture : picture // ignore: cast_nullable_to_non_nullable
as SnCloudFile?,backgroundId: freezed == backgroundId ? _self.backgroundId : backgroundId // ignore: cast_nullable_to_non_nullable
as String?,background: freezed == background ? _self.background : background // ignore: cast_nullable_to_non_nullable
as bool,picture: freezed == picture ? _self.picture : picture // ignore: cast_nullable_to_non_nullable
as SnCloudFile?,background: freezed == background ? _self.background : background // ignore: cast_nullable_to_non_nullable
as SnCloudFile?,realmId: freezed == realmId ? _self.realmId : realmId // ignore: cast_nullable_to_non_nullable
as String?,realm: freezed == realm ? _self.realm : realm // ignore: cast_nullable_to_non_nullable
as SnRealm?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
@ -669,7 +663,7 @@ $SnChatMemberCopyWith<$Res> get sender {
/// @nodoc
mixin _$SnChatMember {
DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; String get id; String get chatRoomId; SnChatRoom? get chatRoom; String get accountId; SnAccount get account; String? get nick; int get role; int get notify; DateTime? get joinedAt; bool get isBot;
DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; String get id; String get chatRoomId; SnChatRoom? get chatRoom; String get accountId; SnAccount get account; String? get nick; int get role; int get notify; DateTime? get joinedAt; bool get isBot; DateTime? get lastTyped;
/// Create a copy of SnChatMember
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@ -682,16 +676,16 @@ $SnChatMemberCopyWith<SnChatMember> get copyWith => _$SnChatMemberCopyWithImpl<S
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnChatMember&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.id, id) || other.id == id)&&(identical(other.chatRoomId, chatRoomId) || other.chatRoomId == chatRoomId)&&(identical(other.chatRoom, chatRoom) || other.chatRoom == chatRoom)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.account, account) || other.account == account)&&(identical(other.nick, nick) || other.nick == nick)&&(identical(other.role, role) || other.role == role)&&(identical(other.notify, notify) || other.notify == notify)&&(identical(other.joinedAt, joinedAt) || other.joinedAt == joinedAt)&&(identical(other.isBot, isBot) || other.isBot == isBot));
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnChatMember&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.id, id) || other.id == id)&&(identical(other.chatRoomId, chatRoomId) || other.chatRoomId == chatRoomId)&&(identical(other.chatRoom, chatRoom) || other.chatRoom == chatRoom)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.account, account) || other.account == account)&&(identical(other.nick, nick) || other.nick == nick)&&(identical(other.role, role) || other.role == role)&&(identical(other.notify, notify) || other.notify == notify)&&(identical(other.joinedAt, joinedAt) || other.joinedAt == joinedAt)&&(identical(other.isBot, isBot) || other.isBot == isBot)&&(identical(other.lastTyped, lastTyped) || other.lastTyped == lastTyped));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,createdAt,updatedAt,deletedAt,id,chatRoomId,chatRoom,accountId,account,nick,role,notify,joinedAt,isBot);
int get hashCode => Object.hash(runtimeType,createdAt,updatedAt,deletedAt,id,chatRoomId,chatRoom,accountId,account,nick,role,notify,joinedAt,isBot,lastTyped);
@override
String toString() {
return 'SnChatMember(createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, id: $id, chatRoomId: $chatRoomId, chatRoom: $chatRoom, accountId: $accountId, account: $account, nick: $nick, role: $role, notify: $notify, joinedAt: $joinedAt, isBot: $isBot)';
return 'SnChatMember(createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, id: $id, chatRoomId: $chatRoomId, chatRoom: $chatRoom, accountId: $accountId, account: $account, nick: $nick, role: $role, notify: $notify, joinedAt: $joinedAt, isBot: $isBot, lastTyped: $lastTyped)';
}
@ -702,7 +696,7 @@ abstract mixin class $SnChatMemberCopyWith<$Res> {
factory $SnChatMemberCopyWith(SnChatMember value, $Res Function(SnChatMember) _then) = _$SnChatMemberCopyWithImpl;
@useResult
$Res call({
DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, String id, String chatRoomId, SnChatRoom? chatRoom, String accountId, SnAccount account, String? nick, int role, int notify, DateTime? joinedAt, bool isBot
DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, String id, String chatRoomId, SnChatRoom? chatRoom, String accountId, SnAccount account, String? nick, int role, int notify, DateTime? joinedAt, bool isBot, DateTime? lastTyped
});
@ -719,7 +713,7 @@ class _$SnChatMemberCopyWithImpl<$Res>
/// Create a copy of SnChatMember
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? id = null,Object? chatRoomId = null,Object? chatRoom = freezed,Object? accountId = null,Object? account = null,Object? nick = freezed,Object? role = null,Object? notify = null,Object? joinedAt = freezed,Object? isBot = null,}) {
@pragma('vm:prefer-inline') @override $Res call({Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? id = null,Object? chatRoomId = null,Object? chatRoom = freezed,Object? accountId = null,Object? account = null,Object? nick = freezed,Object? role = null,Object? notify = null,Object? joinedAt = freezed,Object? isBot = null,Object? lastTyped = freezed,}) {
return _then(_self.copyWith(
createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
@ -734,7 +728,8 @@ as String?,role: null == role ? _self.role : role // ignore: cast_nullable_to_no
as int,notify: null == notify ? _self.notify : notify // ignore: cast_nullable_to_non_nullable
as int,joinedAt: freezed == joinedAt ? _self.joinedAt : joinedAt // ignore: cast_nullable_to_non_nullable
as DateTime?,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
@ -766,7 +761,7 @@ $SnAccountCopyWith<$Res> get account {
@JsonSerializable()
class _SnChatMember implements SnChatMember {
const _SnChatMember({required this.createdAt, required this.updatedAt, required this.deletedAt, required this.id, required this.chatRoomId, required this.chatRoom, required this.accountId, required this.account, required this.nick, required this.role, required this.notify, required this.joinedAt, required this.isBot});
const _SnChatMember({required this.createdAt, required this.updatedAt, required this.deletedAt, required this.id, required this.chatRoomId, required this.chatRoom, required this.accountId, required this.account, required this.nick, required this.role, required this.notify, required this.joinedAt, required this.isBot, this.lastTyped});
factory _SnChatMember.fromJson(Map<String, dynamic> json) => _$SnChatMemberFromJson(json);
@override final DateTime createdAt;
@ -782,6 +777,7 @@ class _SnChatMember implements SnChatMember {
@override final int notify;
@override final DateTime? joinedAt;
@override final bool isBot;
@override final DateTime? lastTyped;
/// Create a copy of SnChatMember
/// with the given fields replaced by the non-null parameter values.
@ -796,16 +792,16 @@ Map<String, dynamic> toJson() {
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnChatMember&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.id, id) || other.id == id)&&(identical(other.chatRoomId, chatRoomId) || other.chatRoomId == chatRoomId)&&(identical(other.chatRoom, chatRoom) || other.chatRoom == chatRoom)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.account, account) || other.account == account)&&(identical(other.nick, nick) || other.nick == nick)&&(identical(other.role, role) || other.role == role)&&(identical(other.notify, notify) || other.notify == notify)&&(identical(other.joinedAt, joinedAt) || other.joinedAt == joinedAt)&&(identical(other.isBot, isBot) || other.isBot == isBot));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnChatMember&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.id, id) || other.id == id)&&(identical(other.chatRoomId, chatRoomId) || other.chatRoomId == chatRoomId)&&(identical(other.chatRoom, chatRoom) || other.chatRoom == chatRoom)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.account, account) || other.account == account)&&(identical(other.nick, nick) || other.nick == nick)&&(identical(other.role, role) || other.role == role)&&(identical(other.notify, notify) || other.notify == notify)&&(identical(other.joinedAt, joinedAt) || other.joinedAt == joinedAt)&&(identical(other.isBot, isBot) || other.isBot == isBot)&&(identical(other.lastTyped, lastTyped) || other.lastTyped == lastTyped));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,createdAt,updatedAt,deletedAt,id,chatRoomId,chatRoom,accountId,account,nick,role,notify,joinedAt,isBot);
int get hashCode => Object.hash(runtimeType,createdAt,updatedAt,deletedAt,id,chatRoomId,chatRoom,accountId,account,nick,role,notify,joinedAt,isBot,lastTyped);
@override
String toString() {
return 'SnChatMember(createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, id: $id, chatRoomId: $chatRoomId, chatRoom: $chatRoom, accountId: $accountId, account: $account, nick: $nick, role: $role, notify: $notify, joinedAt: $joinedAt, isBot: $isBot)';
return 'SnChatMember(createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, id: $id, chatRoomId: $chatRoomId, chatRoom: $chatRoom, accountId: $accountId, account: $account, nick: $nick, role: $role, notify: $notify, joinedAt: $joinedAt, isBot: $isBot, lastTyped: $lastTyped)';
}
@ -816,7 +812,7 @@ abstract mixin class _$SnChatMemberCopyWith<$Res> implements $SnChatMemberCopyWi
factory _$SnChatMemberCopyWith(_SnChatMember value, $Res Function(_SnChatMember) _then) = __$SnChatMemberCopyWithImpl;
@override @useResult
$Res call({
DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, String id, String chatRoomId, SnChatRoom? chatRoom, String accountId, SnAccount account, String? nick, int role, int notify, DateTime? joinedAt, bool isBot
DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, String id, String chatRoomId, SnChatRoom? chatRoom, String accountId, SnAccount account, String? nick, int role, int notify, DateTime? joinedAt, bool isBot, DateTime? lastTyped
});
@ -833,7 +829,7 @@ class __$SnChatMemberCopyWithImpl<$Res>
/// Create a copy of SnChatMember
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? id = null,Object? chatRoomId = null,Object? chatRoom = freezed,Object? accountId = null,Object? account = null,Object? nick = freezed,Object? role = null,Object? notify = null,Object? joinedAt = freezed,Object? isBot = null,}) {
@override @pragma('vm:prefer-inline') $Res call({Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? id = null,Object? chatRoomId = null,Object? chatRoom = freezed,Object? accountId = null,Object? account = null,Object? nick = freezed,Object? role = null,Object? notify = null,Object? joinedAt = freezed,Object? isBot = null,Object? lastTyped = freezed,}) {
return _then(_SnChatMember(
createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
@ -848,7 +844,8 @@ as String?,role: null == role ? _self.role : role // ignore: cast_nullable_to_no
as int,notify: null == notify ? _self.notify : notify // ignore: cast_nullable_to_non_nullable
as int,joinedAt: freezed == joinedAt ? _self.joinedAt : joinedAt // ignore: cast_nullable_to_non_nullable
as DateTime?,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?,
));
}

View File

@ -12,12 +12,10 @@ _SnChatRoom _$SnChatRoomFromJson(Map<String, dynamic> json) => _SnChatRoom(
description: json['description'] as String?,
type: (json['type'] as num).toInt(),
isPublic: json['is_public'] as bool,
pictureId: json['picture_id'] as String?,
picture:
json['picture'] == null
? null
: SnCloudFile.fromJson(json['picture'] as Map<String, dynamic>),
backgroundId: json['background_id'] as String?,
background:
json['background'] == null
? null
@ -46,9 +44,7 @@ Map<String, dynamic> _$SnChatRoomToJson(_SnChatRoom instance) =>
'description': instance.description,
'type': instance.type,
'is_public': instance.isPublic,
'picture_id': instance.pictureId,
'picture': instance.picture?.toJson(),
'background_id': instance.backgroundId,
'background': instance.background?.toJson(),
'realm_id': instance.realmId,
'realm': instance.realm?.toJson(),
@ -171,6 +167,10 @@ _SnChatMember _$SnChatMemberFromJson(Map<String, dynamic> json) =>
? null
: DateTime.parse(json['joined_at'] as String),
isBot: json['is_bot'] as bool,
lastTyped:
json['last_typed'] == null
? null
: DateTime.parse(json['last_typed'] as String),
);
Map<String, dynamic> _$SnChatMemberToJson(_SnChatMember instance) =>
@ -188,6 +188,7 @@ Map<String, dynamic> _$SnChatMemberToJson(_SnChatMember instance) =>
'notify': instance.notify,
'joined_at': instance.joinedAt?.toIso8601String(),
'is_bot': instance.isBot,
'last_typed': instance.lastTyped?.toIso8601String(),
};
_SnChatSummary _$SnChatSummaryFromJson(Map<String, dynamic> json) =>

View File

@ -43,7 +43,6 @@ sealed class SnCloudFile with _$SnCloudFile {
required int size,
required DateTime? uploadedAt,
required String? uploadedTo,
required int usedCount,
required DateTime createdAt,
required DateTime updatedAt,
required DateTime? deletedAt,

View File

@ -146,7 +146,7 @@ as UniversalFileType,
/// @nodoc
mixin _$SnCloudFile {
String get id; String get name; String? get description; Map<String, dynamic>? get fileMeta; Map<String, dynamic>? get userMeta; String? get mimeType; String? get hash; int get size; DateTime? get uploadedAt; String? get uploadedTo; int get usedCount; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt;
String get id; String get name; String? get description; Map<String, dynamic>? get fileMeta; Map<String, dynamic>? get userMeta; String? get mimeType; String? get hash; int get size; DateTime? get uploadedAt; String? get uploadedTo; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt;
/// Create a copy of SnCloudFile
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@ -159,16 +159,16 @@ $SnCloudFileCopyWith<SnCloudFile> get copyWith => _$SnCloudFileCopyWithImpl<SnCl
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnCloudFile&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&const DeepCollectionEquality().equals(other.fileMeta, fileMeta)&&const DeepCollectionEquality().equals(other.userMeta, userMeta)&&(identical(other.mimeType, mimeType) || other.mimeType == mimeType)&&(identical(other.hash, hash) || other.hash == hash)&&(identical(other.size, size) || other.size == size)&&(identical(other.uploadedAt, uploadedAt) || other.uploadedAt == uploadedAt)&&(identical(other.uploadedTo, uploadedTo) || other.uploadedTo == uploadedTo)&&(identical(other.usedCount, usedCount) || other.usedCount == usedCount)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnCloudFile&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&const DeepCollectionEquality().equals(other.fileMeta, fileMeta)&&const DeepCollectionEquality().equals(other.userMeta, userMeta)&&(identical(other.mimeType, mimeType) || other.mimeType == mimeType)&&(identical(other.hash, hash) || other.hash == hash)&&(identical(other.size, size) || other.size == size)&&(identical(other.uploadedAt, uploadedAt) || other.uploadedAt == uploadedAt)&&(identical(other.uploadedTo, uploadedTo) || other.uploadedTo == uploadedTo)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,id,name,description,const DeepCollectionEquality().hash(fileMeta),const DeepCollectionEquality().hash(userMeta),mimeType,hash,size,uploadedAt,uploadedTo,usedCount,createdAt,updatedAt,deletedAt);
int get hashCode => Object.hash(runtimeType,id,name,description,const DeepCollectionEquality().hash(fileMeta),const DeepCollectionEquality().hash(userMeta),mimeType,hash,size,uploadedAt,uploadedTo,createdAt,updatedAt,deletedAt);
@override
String toString() {
return 'SnCloudFile(id: $id, name: $name, description: $description, fileMeta: $fileMeta, userMeta: $userMeta, mimeType: $mimeType, hash: $hash, size: $size, uploadedAt: $uploadedAt, uploadedTo: $uploadedTo, usedCount: $usedCount, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
return 'SnCloudFile(id: $id, name: $name, description: $description, fileMeta: $fileMeta, userMeta: $userMeta, mimeType: $mimeType, hash: $hash, size: $size, uploadedAt: $uploadedAt, uploadedTo: $uploadedTo, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
}
@ -179,7 +179,7 @@ abstract mixin class $SnCloudFileCopyWith<$Res> {
factory $SnCloudFileCopyWith(SnCloudFile value, $Res Function(SnCloudFile) _then) = _$SnCloudFileCopyWithImpl;
@useResult
$Res call({
String id, String name, String? description, Map<String, dynamic>? fileMeta, Map<String, dynamic>? userMeta, String? mimeType, String? hash, int size, DateTime? uploadedAt, String? uploadedTo, int usedCount, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
String id, String name, String? description, Map<String, dynamic>? fileMeta, Map<String, dynamic>? userMeta, String? mimeType, String? hash, int size, DateTime? uploadedAt, String? uploadedTo, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
});
@ -196,7 +196,7 @@ class _$SnCloudFileCopyWithImpl<$Res>
/// Create a copy of SnCloudFile
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? name = null,Object? description = freezed,Object? fileMeta = freezed,Object? userMeta = freezed,Object? mimeType = freezed,Object? hash = freezed,Object? size = null,Object? uploadedAt = freezed,Object? uploadedTo = freezed,Object? usedCount = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? name = null,Object? description = freezed,Object? fileMeta = freezed,Object? userMeta = freezed,Object? mimeType = freezed,Object? hash = freezed,Object? size = null,Object? uploadedAt = freezed,Object? uploadedTo = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
return _then(_self.copyWith(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
@ -208,8 +208,7 @@ as String?,hash: freezed == hash ? _self.hash : hash // ignore: cast_nullable_to
as String?,size: null == size ? _self.size : size // ignore: cast_nullable_to_non_nullable
as int,uploadedAt: freezed == uploadedAt ? _self.uploadedAt : uploadedAt // ignore: cast_nullable_to_non_nullable
as DateTime?,uploadedTo: freezed == uploadedTo ? _self.uploadedTo : uploadedTo // ignore: cast_nullable_to_non_nullable
as String?,usedCount: null == usedCount ? _self.usedCount : usedCount // ignore: cast_nullable_to_non_nullable
as int,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
as String?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
as DateTime?,
@ -223,7 +222,7 @@ as DateTime?,
@JsonSerializable()
class _SnCloudFile implements SnCloudFile {
const _SnCloudFile({required this.id, required this.name, required this.description, required final Map<String, dynamic>? fileMeta, required final Map<String, dynamic>? userMeta, required this.mimeType, required this.hash, required this.size, required this.uploadedAt, required this.uploadedTo, required this.usedCount, required this.createdAt, required this.updatedAt, required this.deletedAt}): _fileMeta = fileMeta,_userMeta = userMeta;
const _SnCloudFile({required this.id, required this.name, required this.description, required final Map<String, dynamic>? fileMeta, required final Map<String, dynamic>? userMeta, required this.mimeType, required this.hash, required this.size, required this.uploadedAt, required this.uploadedTo, required this.createdAt, required this.updatedAt, required this.deletedAt}): _fileMeta = fileMeta,_userMeta = userMeta;
factory _SnCloudFile.fromJson(Map<String, dynamic> json) => _$SnCloudFileFromJson(json);
@override final String id;
@ -252,7 +251,6 @@ class _SnCloudFile implements SnCloudFile {
@override final int size;
@override final DateTime? uploadedAt;
@override final String? uploadedTo;
@override final int usedCount;
@override final DateTime createdAt;
@override final DateTime updatedAt;
@override final DateTime? deletedAt;
@ -270,16 +268,16 @@ Map<String, dynamic> toJson() {
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnCloudFile&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&const DeepCollectionEquality().equals(other._fileMeta, _fileMeta)&&const DeepCollectionEquality().equals(other._userMeta, _userMeta)&&(identical(other.mimeType, mimeType) || other.mimeType == mimeType)&&(identical(other.hash, hash) || other.hash == hash)&&(identical(other.size, size) || other.size == size)&&(identical(other.uploadedAt, uploadedAt) || other.uploadedAt == uploadedAt)&&(identical(other.uploadedTo, uploadedTo) || other.uploadedTo == uploadedTo)&&(identical(other.usedCount, usedCount) || other.usedCount == usedCount)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnCloudFile&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&const DeepCollectionEquality().equals(other._fileMeta, _fileMeta)&&const DeepCollectionEquality().equals(other._userMeta, _userMeta)&&(identical(other.mimeType, mimeType) || other.mimeType == mimeType)&&(identical(other.hash, hash) || other.hash == hash)&&(identical(other.size, size) || other.size == size)&&(identical(other.uploadedAt, uploadedAt) || other.uploadedAt == uploadedAt)&&(identical(other.uploadedTo, uploadedTo) || other.uploadedTo == uploadedTo)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,id,name,description,const DeepCollectionEquality().hash(_fileMeta),const DeepCollectionEquality().hash(_userMeta),mimeType,hash,size,uploadedAt,uploadedTo,usedCount,createdAt,updatedAt,deletedAt);
int get hashCode => Object.hash(runtimeType,id,name,description,const DeepCollectionEquality().hash(_fileMeta),const DeepCollectionEquality().hash(_userMeta),mimeType,hash,size,uploadedAt,uploadedTo,createdAt,updatedAt,deletedAt);
@override
String toString() {
return 'SnCloudFile(id: $id, name: $name, description: $description, fileMeta: $fileMeta, userMeta: $userMeta, mimeType: $mimeType, hash: $hash, size: $size, uploadedAt: $uploadedAt, uploadedTo: $uploadedTo, usedCount: $usedCount, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
return 'SnCloudFile(id: $id, name: $name, description: $description, fileMeta: $fileMeta, userMeta: $userMeta, mimeType: $mimeType, hash: $hash, size: $size, uploadedAt: $uploadedAt, uploadedTo: $uploadedTo, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
}
@ -290,7 +288,7 @@ abstract mixin class _$SnCloudFileCopyWith<$Res> implements $SnCloudFileCopyWith
factory _$SnCloudFileCopyWith(_SnCloudFile value, $Res Function(_SnCloudFile) _then) = __$SnCloudFileCopyWithImpl;
@override @useResult
$Res call({
String id, String name, String? description, Map<String, dynamic>? fileMeta, Map<String, dynamic>? userMeta, String? mimeType, String? hash, int size, DateTime? uploadedAt, String? uploadedTo, int usedCount, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
String id, String name, String? description, Map<String, dynamic>? fileMeta, Map<String, dynamic>? userMeta, String? mimeType, String? hash, int size, DateTime? uploadedAt, String? uploadedTo, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
});
@ -307,7 +305,7 @@ class __$SnCloudFileCopyWithImpl<$Res>
/// Create a copy of SnCloudFile
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? name = null,Object? description = freezed,Object? fileMeta = freezed,Object? userMeta = freezed,Object? mimeType = freezed,Object? hash = freezed,Object? size = null,Object? uploadedAt = freezed,Object? uploadedTo = freezed,Object? usedCount = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? name = null,Object? description = freezed,Object? fileMeta = freezed,Object? userMeta = freezed,Object? mimeType = freezed,Object? hash = freezed,Object? size = null,Object? uploadedAt = freezed,Object? uploadedTo = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
return _then(_SnCloudFile(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
@ -319,8 +317,7 @@ as String?,hash: freezed == hash ? _self.hash : hash // ignore: cast_nullable_to
as String?,size: null == size ? _self.size : size // ignore: cast_nullable_to_non_nullable
as int,uploadedAt: freezed == uploadedAt ? _self.uploadedAt : uploadedAt // ignore: cast_nullable_to_non_nullable
as DateTime?,uploadedTo: freezed == uploadedTo ? _self.uploadedTo : uploadedTo // ignore: cast_nullable_to_non_nullable
as String?,usedCount: null == usedCount ? _self.usedCount : usedCount // ignore: cast_nullable_to_non_nullable
as int,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
as String?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
as DateTime?,

View File

@ -20,7 +20,6 @@ _SnCloudFile _$SnCloudFileFromJson(Map<String, dynamic> json) => _SnCloudFile(
? null
: DateTime.parse(json['uploaded_at'] as String),
uploadedTo: json['uploaded_to'] as String?,
usedCount: (json['used_count'] as num).toInt(),
createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String),
deletedAt:
@ -41,7 +40,6 @@ Map<String, dynamic> _$SnCloudFileToJson(_SnCloudFile instance) =>
'size': instance.size,
'uploaded_at': instance.uploadedAt?.toIso8601String(),
'uploaded_to': instance.uploadedTo,
'used_count': instance.usedCount,
'created_at': instance.createdAt.toIso8601String(),
'updated_at': instance.updatedAt.toIso8601String(),
'deleted_at': instance.deletedAt?.toIso8601String(),

View File

@ -51,9 +51,7 @@ sealed class SnPublisher with _$SnPublisher {
required String name,
required String nick,
@Default('') String bio,
required String? pictureId,
required SnCloudFile? picture,
required String? backgroundId,
required SnCloudFile? background,
required SnAccount? account,
required String? accountId,

View File

@ -370,7 +370,7 @@ $SnPublisherCopyWith<$Res> get publisher {
/// @nodoc
mixin _$SnPublisher {
String get id; int get type; String get name; String get nick; String get bio; String? get pictureId; SnCloudFile? get picture; String? get backgroundId; SnCloudFile? get background; 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;
/// Create a copy of SnPublisher
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@ -383,16 +383,16 @@ $SnPublisherCopyWith<SnPublisher> get copyWith => _$SnPublisherCopyWithImpl<SnPu
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnPublisher&&(identical(other.id, id) || other.id == id)&&(identical(other.type, type) || other.type == type)&&(identical(other.name, name) || other.name == name)&&(identical(other.nick, nick) || other.nick == nick)&&(identical(other.bio, bio) || other.bio == bio)&&(identical(other.pictureId, pictureId) || other.pictureId == pictureId)&&(identical(other.picture, picture) || other.picture == picture)&&(identical(other.backgroundId, backgroundId) || other.backgroundId == backgroundId)&&(identical(other.background, background) || other.background == background)&&(identical(other.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));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,id,type,name,nick,bio,pictureId,picture,backgroundId,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);
@override
String toString() {
return 'SnPublisher(id: $id, type: $type, name: $name, nick: $nick, bio: $bio, pictureId: $pictureId, picture: $picture, backgroundId: $backgroundId, 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)';
}
@ -403,7 +403,7 @@ abstract mixin class $SnPublisherCopyWith<$Res> {
factory $SnPublisherCopyWith(SnPublisher value, $Res Function(SnPublisher) _then) = _$SnPublisherCopyWithImpl;
@useResult
$Res call({
String id, int type, String name, String nick, String bio, String? pictureId, SnCloudFile? picture, String? backgroundId, SnCloudFile? background, 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
});
@ -420,17 +420,15 @@ class _$SnPublisherCopyWithImpl<$Res>
/// Create a copy of SnPublisher
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? type = null,Object? name = null,Object? nick = null,Object? bio = null,Object? pictureId = freezed,Object? picture = freezed,Object? backgroundId = freezed,Object? background = freezed,Object? 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,}) {
return _then(_self.copyWith(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
as int,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
as String,nick: null == nick ? _self.nick : nick // ignore: cast_nullable_to_non_nullable
as String,bio: null == bio ? _self.bio : bio // ignore: cast_nullable_to_non_nullable
as String,pictureId: freezed == pictureId ? _self.pictureId : pictureId // ignore: cast_nullable_to_non_nullable
as String?,picture: freezed == picture ? _self.picture : picture // ignore: cast_nullable_to_non_nullable
as SnCloudFile?,backgroundId: freezed == backgroundId ? _self.backgroundId : backgroundId // ignore: cast_nullable_to_non_nullable
as String?,background: freezed == background ? _self.background : background // ignore: cast_nullable_to_non_nullable
as String,picture: freezed == picture ? _self.picture : picture // ignore: cast_nullable_to_non_nullable
as SnCloudFile?,background: freezed == background ? _self.background : background // ignore: cast_nullable_to_non_nullable
as SnCloudFile?,account: freezed == account ? _self.account : account // ignore: cast_nullable_to_non_nullable
as SnAccount?,accountId: freezed == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable
as String?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
@ -484,7 +482,7 @@ $SnAccountCopyWith<$Res>? get account {
@JsonSerializable()
class _SnPublisher implements SnPublisher {
const _SnPublisher({required this.id, required this.type, required this.name, required this.nick, this.bio = '', required this.pictureId, required this.picture, required this.backgroundId, required this.background, required this.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});
factory _SnPublisher.fromJson(Map<String, dynamic> json) => _$SnPublisherFromJson(json);
@override final String id;
@ -492,9 +490,7 @@ class _SnPublisher implements SnPublisher {
@override final String name;
@override final String nick;
@override@JsonKey() final String bio;
@override final String? pictureId;
@override final SnCloudFile? picture;
@override final String? backgroundId;
@override final SnCloudFile? background;
@override final SnAccount? account;
@override final String? accountId;
@ -516,16 +512,16 @@ Map<String, dynamic> toJson() {
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnPublisher&&(identical(other.id, id) || other.id == id)&&(identical(other.type, type) || other.type == type)&&(identical(other.name, name) || other.name == name)&&(identical(other.nick, nick) || other.nick == nick)&&(identical(other.bio, bio) || other.bio == bio)&&(identical(other.pictureId, pictureId) || other.pictureId == pictureId)&&(identical(other.picture, picture) || other.picture == picture)&&(identical(other.backgroundId, backgroundId) || other.backgroundId == backgroundId)&&(identical(other.background, background) || other.background == background)&&(identical(other.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));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,id,type,name,nick,bio,pictureId,picture,backgroundId,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);
@override
String toString() {
return 'SnPublisher(id: $id, type: $type, name: $name, nick: $nick, bio: $bio, pictureId: $pictureId, picture: $picture, backgroundId: $backgroundId, 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)';
}
@ -536,7 +532,7 @@ abstract mixin class _$SnPublisherCopyWith<$Res> implements $SnPublisherCopyWith
factory _$SnPublisherCopyWith(_SnPublisher value, $Res Function(_SnPublisher) _then) = __$SnPublisherCopyWithImpl;
@override @useResult
$Res call({
String id, int type, String name, String nick, String bio, String? pictureId, SnCloudFile? picture, String? backgroundId, SnCloudFile? background, 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
});
@ -553,17 +549,15 @@ class __$SnPublisherCopyWithImpl<$Res>
/// Create a copy of SnPublisher
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? type = null,Object? name = null,Object? nick = null,Object? bio = null,Object? pictureId = freezed,Object? picture = freezed,Object? backgroundId = freezed,Object? background = freezed,Object? 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,}) {
return _then(_SnPublisher(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
as int,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
as String,nick: null == nick ? _self.nick : nick // ignore: cast_nullable_to_non_nullable
as String,bio: null == bio ? _self.bio : bio // ignore: cast_nullable_to_non_nullable
as String,pictureId: freezed == pictureId ? _self.pictureId : pictureId // ignore: cast_nullable_to_non_nullable
as String?,picture: freezed == picture ? _self.picture : picture // ignore: cast_nullable_to_non_nullable
as SnCloudFile?,backgroundId: freezed == backgroundId ? _self.backgroundId : backgroundId // ignore: cast_nullable_to_non_nullable
as String?,background: freezed == background ? _self.background : background // ignore: cast_nullable_to_non_nullable
as String,picture: freezed == picture ? _self.picture : picture // ignore: cast_nullable_to_non_nullable
as SnCloudFile?,background: freezed == background ? _self.background : background // ignore: cast_nullable_to_non_nullable
as SnCloudFile?,account: freezed == account ? _self.account : account // ignore: cast_nullable_to_non_nullable
as SnAccount?,accountId: freezed == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable
as String?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable

View File

@ -100,12 +100,10 @@ _SnPublisher _$SnPublisherFromJson(Map<String, dynamic> json) => _SnPublisher(
name: json['name'] as String,
nick: json['nick'] as String,
bio: json['bio'] as String? ?? '',
pictureId: json['picture_id'] as String?,
picture:
json['picture'] == null
? null
: SnCloudFile.fromJson(json['picture'] as Map<String, dynamic>),
backgroundId: json['background_id'] as String?,
background:
json['background'] == null
? null
@ -131,9 +129,7 @@ Map<String, dynamic> _$SnPublisherToJson(_SnPublisher instance) =>
'name': instance.name,
'nick': instance.nick,
'bio': instance.bio,
'picture_id': instance.pictureId,
'picture': instance.picture?.toJson(),
'background_id': instance.backgroundId,
'background': instance.background?.toJson(),
'account': instance.account?.toJson(),
'account_id': instance.accountId,

View File

@ -16,9 +16,7 @@ sealed class SnRealm with _$SnRealm {
required DateTime? verifiedAt,
required bool isCommunity,
required bool isPublic,
required String? pictureId,
required SnCloudFile? picture,
required String? backgroundId,
required SnCloudFile? background,
required String accountId,
required DateTime createdAt,

View File

@ -16,7 +16,7 @@ T _$identity<T>(T value) => value;
/// @nodoc
mixin _$SnRealm {
String get id; String get slug; String get name; String get description; String? get verifiedAs; DateTime? get verifiedAt; bool get isCommunity; bool get isPublic; String? get pictureId; SnCloudFile? get picture; String? get backgroundId; SnCloudFile? get background; String get accountId; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt;
String get id; String get slug; String get name; String get description; String? get verifiedAs; DateTime? get verifiedAt; bool get isCommunity; bool get isPublic; SnCloudFile? get picture; SnCloudFile? get background; String get accountId; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt;
/// Create a copy of SnRealm
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@ -29,16 +29,16 @@ $SnRealmCopyWith<SnRealm> get copyWith => _$SnRealmCopyWithImpl<SnRealm>(this as
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnRealm&&(identical(other.id, id) || other.id == id)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&(identical(other.verifiedAs, verifiedAs) || other.verifiedAs == verifiedAs)&&(identical(other.verifiedAt, verifiedAt) || other.verifiedAt == verifiedAt)&&(identical(other.isCommunity, isCommunity) || other.isCommunity == isCommunity)&&(identical(other.isPublic, isPublic) || other.isPublic == isPublic)&&(identical(other.pictureId, pictureId) || other.pictureId == pictureId)&&(identical(other.picture, picture) || other.picture == picture)&&(identical(other.backgroundId, backgroundId) || other.backgroundId == backgroundId)&&(identical(other.background, background) || other.background == background)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnRealm&&(identical(other.id, id) || other.id == id)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&(identical(other.verifiedAs, verifiedAs) || other.verifiedAs == verifiedAs)&&(identical(other.verifiedAt, verifiedAt) || other.verifiedAt == verifiedAt)&&(identical(other.isCommunity, isCommunity) || other.isCommunity == isCommunity)&&(identical(other.isPublic, isPublic) || other.isPublic == isPublic)&&(identical(other.picture, picture) || other.picture == picture)&&(identical(other.background, background) || other.background == background)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,id,slug,name,description,verifiedAs,verifiedAt,isCommunity,isPublic,pictureId,picture,backgroundId,background,accountId,createdAt,updatedAt,deletedAt);
int get hashCode => Object.hash(runtimeType,id,slug,name,description,verifiedAs,verifiedAt,isCommunity,isPublic,picture,background,accountId,createdAt,updatedAt,deletedAt);
@override
String toString() {
return 'SnRealm(id: $id, slug: $slug, name: $name, description: $description, verifiedAs: $verifiedAs, verifiedAt: $verifiedAt, isCommunity: $isCommunity, isPublic: $isPublic, pictureId: $pictureId, picture: $picture, backgroundId: $backgroundId, background: $background, accountId: $accountId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
return 'SnRealm(id: $id, slug: $slug, name: $name, description: $description, verifiedAs: $verifiedAs, verifiedAt: $verifiedAt, isCommunity: $isCommunity, isPublic: $isPublic, picture: $picture, background: $background, accountId: $accountId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
}
@ -49,7 +49,7 @@ abstract mixin class $SnRealmCopyWith<$Res> {
factory $SnRealmCopyWith(SnRealm value, $Res Function(SnRealm) _then) = _$SnRealmCopyWithImpl;
@useResult
$Res call({
String id, String slug, String name, String description, String? verifiedAs, DateTime? verifiedAt, bool isCommunity, bool isPublic, String? pictureId, SnCloudFile? picture, String? backgroundId, SnCloudFile? background, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
String id, String slug, String name, String description, String? verifiedAs, DateTime? verifiedAt, bool isCommunity, bool isPublic, SnCloudFile? picture, SnCloudFile? background, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
});
@ -66,7 +66,7 @@ class _$SnRealmCopyWithImpl<$Res>
/// Create a copy of SnRealm
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? slug = null,Object? name = null,Object? description = null,Object? verifiedAs = freezed,Object? verifiedAt = freezed,Object? isCommunity = null,Object? isPublic = null,Object? pictureId = freezed,Object? picture = freezed,Object? backgroundId = freezed,Object? background = freezed,Object? accountId = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? slug = null,Object? name = null,Object? description = null,Object? verifiedAs = freezed,Object? verifiedAt = freezed,Object? isCommunity = null,Object? isPublic = null,Object? picture = freezed,Object? background = freezed,Object? accountId = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
return _then(_self.copyWith(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,slug: null == slug ? _self.slug : slug // ignore: cast_nullable_to_non_nullable
@ -76,10 +76,8 @@ as String,verifiedAs: freezed == verifiedAs ? _self.verifiedAs : verifiedAs // i
as String?,verifiedAt: freezed == verifiedAt ? _self.verifiedAt : verifiedAt // ignore: cast_nullable_to_non_nullable
as DateTime?,isCommunity: null == isCommunity ? _self.isCommunity : isCommunity // ignore: cast_nullable_to_non_nullable
as bool,isPublic: null == isPublic ? _self.isPublic : isPublic // ignore: cast_nullable_to_non_nullable
as bool,pictureId: freezed == pictureId ? _self.pictureId : pictureId // ignore: cast_nullable_to_non_nullable
as String?,picture: freezed == picture ? _self.picture : picture // ignore: cast_nullable_to_non_nullable
as SnCloudFile?,backgroundId: freezed == backgroundId ? _self.backgroundId : backgroundId // ignore: cast_nullable_to_non_nullable
as String?,background: freezed == background ? _self.background : background // ignore: cast_nullable_to_non_nullable
as bool,picture: freezed == picture ? _self.picture : picture // ignore: cast_nullable_to_non_nullable
as SnCloudFile?,background: freezed == background ? _self.background : background // ignore: cast_nullable_to_non_nullable
as SnCloudFile?,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable
as String,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
@ -119,7 +117,7 @@ $SnCloudFileCopyWith<$Res>? get background {
@JsonSerializable()
class _SnRealm implements SnRealm {
const _SnRealm({required this.id, required this.slug, required this.name, required this.description, required this.verifiedAs, required this.verifiedAt, required this.isCommunity, required this.isPublic, required this.pictureId, required this.picture, required this.backgroundId, required this.background, required this.accountId, required this.createdAt, required this.updatedAt, required this.deletedAt});
const _SnRealm({required this.id, required this.slug, required this.name, required this.description, required this.verifiedAs, required this.verifiedAt, required this.isCommunity, required this.isPublic, required this.picture, required this.background, required this.accountId, required this.createdAt, required this.updatedAt, required this.deletedAt});
factory _SnRealm.fromJson(Map<String, dynamic> json) => _$SnRealmFromJson(json);
@override final String id;
@ -130,9 +128,7 @@ class _SnRealm implements SnRealm {
@override final DateTime? verifiedAt;
@override final bool isCommunity;
@override final bool isPublic;
@override final String? pictureId;
@override final SnCloudFile? picture;
@override final String? backgroundId;
@override final SnCloudFile? background;
@override final String accountId;
@override final DateTime createdAt;
@ -152,16 +148,16 @@ Map<String, dynamic> toJson() {
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnRealm&&(identical(other.id, id) || other.id == id)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&(identical(other.verifiedAs, verifiedAs) || other.verifiedAs == verifiedAs)&&(identical(other.verifiedAt, verifiedAt) || other.verifiedAt == verifiedAt)&&(identical(other.isCommunity, isCommunity) || other.isCommunity == isCommunity)&&(identical(other.isPublic, isPublic) || other.isPublic == isPublic)&&(identical(other.pictureId, pictureId) || other.pictureId == pictureId)&&(identical(other.picture, picture) || other.picture == picture)&&(identical(other.backgroundId, backgroundId) || other.backgroundId == backgroundId)&&(identical(other.background, background) || other.background == background)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnRealm&&(identical(other.id, id) || other.id == id)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&(identical(other.verifiedAs, verifiedAs) || other.verifiedAs == verifiedAs)&&(identical(other.verifiedAt, verifiedAt) || other.verifiedAt == verifiedAt)&&(identical(other.isCommunity, isCommunity) || other.isCommunity == isCommunity)&&(identical(other.isPublic, isPublic) || other.isPublic == isPublic)&&(identical(other.picture, picture) || other.picture == picture)&&(identical(other.background, background) || other.background == background)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,id,slug,name,description,verifiedAs,verifiedAt,isCommunity,isPublic,pictureId,picture,backgroundId,background,accountId,createdAt,updatedAt,deletedAt);
int get hashCode => Object.hash(runtimeType,id,slug,name,description,verifiedAs,verifiedAt,isCommunity,isPublic,picture,background,accountId,createdAt,updatedAt,deletedAt);
@override
String toString() {
return 'SnRealm(id: $id, slug: $slug, name: $name, description: $description, verifiedAs: $verifiedAs, verifiedAt: $verifiedAt, isCommunity: $isCommunity, isPublic: $isPublic, pictureId: $pictureId, picture: $picture, backgroundId: $backgroundId, background: $background, accountId: $accountId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
return 'SnRealm(id: $id, slug: $slug, name: $name, description: $description, verifiedAs: $verifiedAs, verifiedAt: $verifiedAt, isCommunity: $isCommunity, isPublic: $isPublic, picture: $picture, background: $background, accountId: $accountId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
}
@ -172,7 +168,7 @@ abstract mixin class _$SnRealmCopyWith<$Res> implements $SnRealmCopyWith<$Res> {
factory _$SnRealmCopyWith(_SnRealm value, $Res Function(_SnRealm) _then) = __$SnRealmCopyWithImpl;
@override @useResult
$Res call({
String id, String slug, String name, String description, String? verifiedAs, DateTime? verifiedAt, bool isCommunity, bool isPublic, String? pictureId, SnCloudFile? picture, String? backgroundId, SnCloudFile? background, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
String id, String slug, String name, String description, String? verifiedAs, DateTime? verifiedAt, bool isCommunity, bool isPublic, SnCloudFile? picture, SnCloudFile? background, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
});
@ -189,7 +185,7 @@ class __$SnRealmCopyWithImpl<$Res>
/// Create a copy of SnRealm
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? slug = null,Object? name = null,Object? description = null,Object? verifiedAs = freezed,Object? verifiedAt = freezed,Object? isCommunity = null,Object? isPublic = null,Object? pictureId = freezed,Object? picture = freezed,Object? backgroundId = freezed,Object? background = freezed,Object? accountId = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? slug = null,Object? name = null,Object? description = null,Object? verifiedAs = freezed,Object? verifiedAt = freezed,Object? isCommunity = null,Object? isPublic = null,Object? picture = freezed,Object? background = freezed,Object? accountId = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
return _then(_SnRealm(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,slug: null == slug ? _self.slug : slug // ignore: cast_nullable_to_non_nullable
@ -199,10 +195,8 @@ as String,verifiedAs: freezed == verifiedAs ? _self.verifiedAs : verifiedAs // i
as String?,verifiedAt: freezed == verifiedAt ? _self.verifiedAt : verifiedAt // ignore: cast_nullable_to_non_nullable
as DateTime?,isCommunity: null == isCommunity ? _self.isCommunity : isCommunity // ignore: cast_nullable_to_non_nullable
as bool,isPublic: null == isPublic ? _self.isPublic : isPublic // ignore: cast_nullable_to_non_nullable
as bool,pictureId: freezed == pictureId ? _self.pictureId : pictureId // ignore: cast_nullable_to_non_nullable
as String?,picture: freezed == picture ? _self.picture : picture // ignore: cast_nullable_to_non_nullable
as SnCloudFile?,backgroundId: freezed == backgroundId ? _self.backgroundId : backgroundId // ignore: cast_nullable_to_non_nullable
as String?,background: freezed == background ? _self.background : background // ignore: cast_nullable_to_non_nullable
as bool,picture: freezed == picture ? _self.picture : picture // ignore: cast_nullable_to_non_nullable
as SnCloudFile?,background: freezed == background ? _self.background : background // ignore: cast_nullable_to_non_nullable
as SnCloudFile?,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable
as String,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable

View File

@ -18,12 +18,10 @@ _SnRealm _$SnRealmFromJson(Map<String, dynamic> json) => _SnRealm(
: DateTime.parse(json['verified_at'] as String),
isCommunity: json['is_community'] as bool,
isPublic: json['is_public'] as bool,
pictureId: json['picture_id'] as String?,
picture:
json['picture'] == null
? null
: SnCloudFile.fromJson(json['picture'] as Map<String, dynamic>),
backgroundId: json['background_id'] as String?,
background:
json['background'] == null
? null
@ -46,9 +44,7 @@ Map<String, dynamic> _$SnRealmToJson(_SnRealm instance) => <String, dynamic>{
'verified_at': instance.verifiedAt?.toIso8601String(),
'is_community': instance.isCommunity,
'is_public': instance.isPublic,
'picture_id': instance.pictureId,
'picture': instance.picture?.toJson(),
'background_id': instance.backgroundId,
'background': instance.background?.toJson(),
'account_id': instance.accountId,
'created_at': instance.createdAt.toIso8601String(),

View File

@ -31,12 +31,10 @@ sealed class SnAccountProfile with _$SnAccountProfile {
required String? middleName,
required String? lastName,
@Default('') String bio,
required String? pictureId,
required int experience,
required int level,
required double levelingProgress,
required SnCloudFile? picture,
required String? backgroundId,
required SnCloudFile? background,
required DateTime createdAt,
required DateTime updatedAt,
@ -87,6 +85,24 @@ sealed class SnAccountBadge with _$SnAccountBadge {
_$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
sealed class SnNotification with _$SnNotification {
const factory SnNotification({

View File

@ -200,7 +200,7 @@ $SnAccountProfileCopyWith<$Res> get profile {
/// @nodoc
mixin _$SnAccountProfile {
String get id; String? get firstName; String? get middleName; String? get lastName; String get bio; String? get pictureId; int get experience; int get level; double get levelingProgress; SnCloudFile? get picture; String? get backgroundId; SnCloudFile? get background; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt;
String get id; String? get firstName; String? get middleName; String? get lastName; String get bio; int get experience; int get level; double get levelingProgress; SnCloudFile? get picture; SnCloudFile? get background; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt;
/// Create a copy of SnAccountProfile
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@ -213,16 +213,16 @@ $SnAccountProfileCopyWith<SnAccountProfile> get copyWith => _$SnAccountProfileCo
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnAccountProfile&&(identical(other.id, id) || other.id == id)&&(identical(other.firstName, firstName) || other.firstName == firstName)&&(identical(other.middleName, middleName) || other.middleName == middleName)&&(identical(other.lastName, lastName) || other.lastName == lastName)&&(identical(other.bio, bio) || other.bio == bio)&&(identical(other.pictureId, pictureId) || other.pictureId == pictureId)&&(identical(other.experience, experience) || other.experience == experience)&&(identical(other.level, level) || other.level == level)&&(identical(other.levelingProgress, levelingProgress) || other.levelingProgress == levelingProgress)&&(identical(other.picture, picture) || other.picture == picture)&&(identical(other.backgroundId, backgroundId) || other.backgroundId == backgroundId)&&(identical(other.background, background) || other.background == background)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnAccountProfile&&(identical(other.id, id) || other.id == id)&&(identical(other.firstName, firstName) || other.firstName == firstName)&&(identical(other.middleName, middleName) || other.middleName == middleName)&&(identical(other.lastName, lastName) || other.lastName == lastName)&&(identical(other.bio, bio) || other.bio == bio)&&(identical(other.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));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,id,firstName,middleName,lastName,bio,pictureId,experience,level,levelingProgress,picture,backgroundId,background,createdAt,updatedAt,deletedAt);
int get hashCode => Object.hash(runtimeType,id,firstName,middleName,lastName,bio,experience,level,levelingProgress,picture,background,createdAt,updatedAt,deletedAt);
@override
String toString() {
return 'SnAccountProfile(id: $id, firstName: $firstName, middleName: $middleName, lastName: $lastName, bio: $bio, pictureId: $pictureId, experience: $experience, level: $level, levelingProgress: $levelingProgress, picture: $picture, backgroundId: $backgroundId, background: $background, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
return 'SnAccountProfile(id: $id, firstName: $firstName, middleName: $middleName, lastName: $lastName, bio: $bio, experience: $experience, level: $level, levelingProgress: $levelingProgress, picture: $picture, background: $background, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
}
@ -233,7 +233,7 @@ abstract mixin class $SnAccountProfileCopyWith<$Res> {
factory $SnAccountProfileCopyWith(SnAccountProfile value, $Res Function(SnAccountProfile) _then) = _$SnAccountProfileCopyWithImpl;
@useResult
$Res call({
String id, String? firstName, String? middleName, String? lastName, String bio, String? pictureId, int experience, int level, double levelingProgress, SnCloudFile? picture, String? backgroundId, SnCloudFile? background, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
String id, String? firstName, String? middleName, String? lastName, String bio, int experience, int level, double levelingProgress, SnCloudFile? picture, SnCloudFile? background, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
});
@ -250,20 +250,18 @@ class _$SnAccountProfileCopyWithImpl<$Res>
/// Create a copy of SnAccountProfile
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? firstName = freezed,Object? middleName = freezed,Object? lastName = freezed,Object? bio = null,Object? pictureId = freezed,Object? experience = null,Object? level = null,Object? levelingProgress = null,Object? picture = freezed,Object? backgroundId = freezed,Object? background = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? firstName = 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,}) {
return _then(_self.copyWith(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,firstName: freezed == firstName ? _self.firstName : firstName // ignore: cast_nullable_to_non_nullable
as String?,middleName: freezed == middleName ? _self.middleName : middleName // ignore: cast_nullable_to_non_nullable
as String?,lastName: freezed == lastName ? _self.lastName : lastName // ignore: cast_nullable_to_non_nullable
as String?,bio: null == bio ? _self.bio : bio // ignore: cast_nullable_to_non_nullable
as String,pictureId: freezed == pictureId ? _self.pictureId : pictureId // ignore: cast_nullable_to_non_nullable
as String?,experience: null == experience ? _self.experience : experience // ignore: cast_nullable_to_non_nullable
as String,experience: null == experience ? _self.experience : experience // ignore: cast_nullable_to_non_nullable
as int,level: null == level ? _self.level : level // ignore: cast_nullable_to_non_nullable
as int,levelingProgress: null == levelingProgress ? _self.levelingProgress : levelingProgress // ignore: cast_nullable_to_non_nullable
as double,picture: freezed == picture ? _self.picture : picture // ignore: cast_nullable_to_non_nullable
as SnCloudFile?,backgroundId: freezed == backgroundId ? _self.backgroundId : backgroundId // ignore: cast_nullable_to_non_nullable
as String?,background: freezed == background ? _self.background : background // ignore: cast_nullable_to_non_nullable
as SnCloudFile?,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 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
@ -302,7 +300,7 @@ $SnCloudFileCopyWith<$Res>? get background {
@JsonSerializable()
class _SnAccountProfile implements SnAccountProfile {
const _SnAccountProfile({required this.id, required this.firstName, required this.middleName, required this.lastName, this.bio = '', required this.pictureId, required this.experience, required this.level, required this.levelingProgress, required this.picture, required this.backgroundId, required this.background, required this.createdAt, required this.updatedAt, required this.deletedAt});
const _SnAccountProfile({required this.id, 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});
factory _SnAccountProfile.fromJson(Map<String, dynamic> json) => _$SnAccountProfileFromJson(json);
@override final String id;
@ -310,12 +308,10 @@ class _SnAccountProfile implements SnAccountProfile {
@override final String? middleName;
@override final String? lastName;
@override@JsonKey() final String bio;
@override final String? pictureId;
@override final int experience;
@override final int level;
@override final double levelingProgress;
@override final SnCloudFile? picture;
@override final String? backgroundId;
@override final SnCloudFile? background;
@override final DateTime createdAt;
@override final DateTime updatedAt;
@ -334,16 +330,16 @@ Map<String, dynamic> toJson() {
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnAccountProfile&&(identical(other.id, id) || other.id == id)&&(identical(other.firstName, firstName) || other.firstName == firstName)&&(identical(other.middleName, middleName) || other.middleName == middleName)&&(identical(other.lastName, lastName) || other.lastName == lastName)&&(identical(other.bio, bio) || other.bio == bio)&&(identical(other.pictureId, pictureId) || other.pictureId == pictureId)&&(identical(other.experience, experience) || other.experience == experience)&&(identical(other.level, level) || other.level == level)&&(identical(other.levelingProgress, levelingProgress) || other.levelingProgress == levelingProgress)&&(identical(other.picture, picture) || other.picture == picture)&&(identical(other.backgroundId, backgroundId) || other.backgroundId == backgroundId)&&(identical(other.background, background) || other.background == background)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnAccountProfile&&(identical(other.id, id) || other.id == id)&&(identical(other.firstName, firstName) || other.firstName == firstName)&&(identical(other.middleName, middleName) || other.middleName == middleName)&&(identical(other.lastName, lastName) || other.lastName == lastName)&&(identical(other.bio, bio) || other.bio == bio)&&(identical(other.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));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,id,firstName,middleName,lastName,bio,pictureId,experience,level,levelingProgress,picture,backgroundId,background,createdAt,updatedAt,deletedAt);
int get hashCode => Object.hash(runtimeType,id,firstName,middleName,lastName,bio,experience,level,levelingProgress,picture,background,createdAt,updatedAt,deletedAt);
@override
String toString() {
return 'SnAccountProfile(id: $id, firstName: $firstName, middleName: $middleName, lastName: $lastName, bio: $bio, pictureId: $pictureId, experience: $experience, level: $level, levelingProgress: $levelingProgress, picture: $picture, backgroundId: $backgroundId, background: $background, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
return 'SnAccountProfile(id: $id, firstName: $firstName, middleName: $middleName, lastName: $lastName, bio: $bio, experience: $experience, level: $level, levelingProgress: $levelingProgress, picture: $picture, background: $background, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
}
@ -354,7 +350,7 @@ abstract mixin class _$SnAccountProfileCopyWith<$Res> implements $SnAccountProfi
factory _$SnAccountProfileCopyWith(_SnAccountProfile value, $Res Function(_SnAccountProfile) _then) = __$SnAccountProfileCopyWithImpl;
@override @useResult
$Res call({
String id, String? firstName, String? middleName, String? lastName, String bio, String? pictureId, int experience, int level, double levelingProgress, SnCloudFile? picture, String? backgroundId, SnCloudFile? background, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
String id, String? firstName, String? middleName, String? lastName, String bio, int experience, int level, double levelingProgress, SnCloudFile? picture, SnCloudFile? background, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
});
@ -371,20 +367,18 @@ class __$SnAccountProfileCopyWithImpl<$Res>
/// Create a copy of SnAccountProfile
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? firstName = freezed,Object? middleName = freezed,Object? lastName = freezed,Object? bio = null,Object? pictureId = freezed,Object? experience = null,Object? level = null,Object? levelingProgress = null,Object? picture = freezed,Object? backgroundId = freezed,Object? background = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? firstName = 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,}) {
return _then(_SnAccountProfile(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,firstName: freezed == firstName ? _self.firstName : firstName // ignore: cast_nullable_to_non_nullable
as String?,middleName: freezed == middleName ? _self.middleName : middleName // ignore: cast_nullable_to_non_nullable
as String?,lastName: freezed == lastName ? _self.lastName : lastName // ignore: cast_nullable_to_non_nullable
as String?,bio: null == bio ? _self.bio : bio // ignore: cast_nullable_to_non_nullable
as String,pictureId: freezed == pictureId ? _self.pictureId : pictureId // ignore: cast_nullable_to_non_nullable
as String?,experience: null == experience ? _self.experience : experience // ignore: cast_nullable_to_non_nullable
as String,experience: null == experience ? _self.experience : experience // ignore: cast_nullable_to_non_nullable
as int,level: null == level ? _self.level : level // ignore: cast_nullable_to_non_nullable
as int,levelingProgress: null == levelingProgress ? _self.levelingProgress : levelingProgress // ignore: cast_nullable_to_non_nullable
as double,picture: freezed == picture ? _self.picture : picture // ignore: cast_nullable_to_non_nullable
as SnCloudFile?,backgroundId: freezed == backgroundId ? _self.backgroundId : backgroundId // ignore: cast_nullable_to_non_nullable
as String?,background: freezed == background ? _self.background : background // ignore: cast_nullable_to_non_nullable
as SnCloudFile?,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 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
@ -752,6 +746,163 @@ 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?,
));
}
}
/// @nodoc
mixin _$SnNotification {

View File

@ -47,7 +47,6 @@ _SnAccountProfile _$SnAccountProfileFromJson(Map<String, dynamic> json) =>
middleName: json['middle_name'] as String?,
lastName: json['last_name'] as String?,
bio: json['bio'] as String? ?? '',
pictureId: json['picture_id'] as String?,
experience: (json['experience'] as num).toInt(),
level: (json['level'] as num).toInt(),
levelingProgress: (json['leveling_progress'] as num).toDouble(),
@ -55,7 +54,6 @@ _SnAccountProfile _$SnAccountProfileFromJson(Map<String, dynamic> json) =>
json['picture'] == null
? null
: SnCloudFile.fromJson(json['picture'] as Map<String, dynamic>),
backgroundId: json['background_id'] as String?,
background:
json['background'] == null
? null
@ -77,12 +75,10 @@ Map<String, dynamic> _$SnAccountProfileToJson(_SnAccountProfile instance) =>
'middle_name': instance.middleName,
'last_name': instance.lastName,
'bio': instance.bio,
'picture_id': instance.pictureId,
'experience': instance.experience,
'level': instance.level,
'leveling_progress': instance.levelingProgress,
'picture': instance.picture?.toJson(),
'background_id': instance.backgroundId,
'background': instance.background?.toJson(),
'created_at': instance.createdAt.toIso8601String(),
'updated_at': instance.updatedAt.toIso8601String(),
@ -161,6 +157,38 @@ Map<String, dynamic> _$SnAccountBadgeToJson(_SnAccountBadge instance) =>
'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(),
};
_SnNotification _$SnNotificationFromJson(Map<String, dynamic> json) =>
_SnNotification(
createdAt: DateTime.parse(json['created_at'] as String),

View File

@ -1,9 +1,12 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:island/pods/theme.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:shared_preferences/shared_preferences.dart';
part 'config.freezed.dart';
part 'config.g.dart';
const kTokenPairStoreKey = 'dyn_user_tk';
@ -14,13 +17,8 @@ const kAppbarTransparentStoreKey = 'app_bar_transparent';
const kAppBackgroundStoreKey = 'app_has_background';
const kAppColorSchemeStoreKey = 'app_color_scheme';
const kAppNotifyWithHaptic = 'app_notify_with_haptic';
const kAppExpandPostLink = 'app_expand_post_link';
const kAppExpandChatLink = 'app_expand_chat_link';
const kAppRealmCompactView = 'app_realm_compact_view';
const kAppCustomFonts = 'app_custom_fonts';
const kAppMixedFeed = 'app_mixed_feed';
const kAppAutoTranslate = 'app_auto_translate';
const kAppHideBottomNav = 'app_hide_bottom_nav';
const kAppSoundEffects = 'app_sound_effects';
const kAppAprilFoolFeatures = 'app_april_fool_features';
const kAppWindowSize = 'app_window_size';
@ -57,48 +55,73 @@ sealed class AppSettings with _$AppSettings {
required bool soundEffects,
required bool aprilFoolFeatures,
required bool enterToSend,
required bool appBarTransparent,
required String? customFonts,
required int? appColorScheme, // The color stored via the int type
}) = _AppSettings;
}
class AppSettingsNotifier extends StateNotifier<AppSettings> {
final SharedPreferences prefs;
AppSettingsNotifier(this.prefs)
: super(
AppSettings(
autoTranslate: prefs.getBool(kAppAutoTranslate) ?? false,
soundEffects: prefs.getBool(kAppSoundEffects) ?? true,
aprilFoolFeatures: prefs.getBool(kAppAprilFoolFeatures) ?? true,
enterToSend: prefs.getBool(kAppEnterToSend) ?? true,
),
);
@riverpod
class AppSettingsNotifier extends _$AppSettingsNotifier {
@override
AppSettings build() {
final prefs = ref.watch(sharedPreferencesProvider);
return AppSettings(
autoTranslate: prefs.getBool(kAppAutoTranslate) ?? false,
soundEffects: prefs.getBool(kAppSoundEffects) ?? true,
aprilFoolFeatures: prefs.getBool(kAppAprilFoolFeatures) ?? true,
enterToSend: prefs.getBool(kAppEnterToSend) ?? true,
appBarTransparent: prefs.getBool(kAppbarTransparentStoreKey) ?? false,
customFonts: prefs.getString(kAppCustomFonts),
appColorScheme: prefs.getInt(kAppColorSchemeStoreKey),
);
}
void setAutoTranslate(bool value) {
final prefs = ref.read(sharedPreferencesProvider);
prefs.setBool(kAppAutoTranslate, value);
state = state.copyWith(autoTranslate: value);
}
void setSoundEffects(bool value) {
final prefs = ref.read(sharedPreferencesProvider);
prefs.setBool(kAppSoundEffects, value);
state = state.copyWith(soundEffects: value);
}
void setAprilFoolFeatures(bool value) {
final prefs = ref.read(sharedPreferencesProvider);
prefs.setBool(kAppAprilFoolFeatures, value);
state = state.copyWith(aprilFoolFeatures: value);
}
void setEnterToSend(bool value) {
final prefs = ref.read(sharedPreferencesProvider);
prefs.setBool(kAppEnterToSend, value);
state = state.copyWith(enterToSend: value);
}
}
final appSettingsProvider =
StateNotifierProvider<AppSettingsNotifier, AppSettings>((ref) {
final prefs = ref.watch(sharedPreferencesProvider);
return AppSettingsNotifier(prefs);
});
void setAppBarTransparent(bool value) {
final prefs = ref.read(sharedPreferencesProvider);
prefs.setBool(kAppbarTransparentStoreKey, value);
state = state.copyWith(appBarTransparent: value);
ref.read(themeProvider.notifier).reloadTheme();
}
void setCustomFonts(String? value) {
final prefs = ref.read(sharedPreferencesProvider);
prefs.setString(kAppCustomFonts, value ?? '');
state = state.copyWith(customFonts: value);
ref.read(themeProvider.notifier).reloadTheme();
}
void setAppColorScheme(int? value) {
final prefs = ref.read(sharedPreferencesProvider);
prefs.setInt(kAppColorSchemeStoreKey, value ?? 0);
state = state.copyWith(appColorScheme: value);
ref.read(themeProvider.notifier).reloadTheme();
}
}
final updateInfoProvider =
StateNotifierProvider<UpdateInfoNotifier, (String?, String?)>((ref) {

View File

@ -15,7 +15,7 @@ T _$identity<T>(T value) => value;
/// @nodoc
mixin _$AppSettings {
bool get autoTranslate; bool get soundEffects; bool get aprilFoolFeatures; bool get enterToSend;
bool get autoTranslate; bool get soundEffects; bool get aprilFoolFeatures; bool get enterToSend; bool get appBarTransparent; String? get customFonts; int? get appColorScheme;
/// Create a copy of AppSettings
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@ -26,16 +26,16 @@ $AppSettingsCopyWith<AppSettings> get copyWith => _$AppSettingsCopyWithImpl<AppS
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is AppSettings&&(identical(other.autoTranslate, autoTranslate) || other.autoTranslate == autoTranslate)&&(identical(other.soundEffects, soundEffects) || other.soundEffects == soundEffects)&&(identical(other.aprilFoolFeatures, aprilFoolFeatures) || other.aprilFoolFeatures == aprilFoolFeatures)&&(identical(other.enterToSend, enterToSend) || other.enterToSend == enterToSend));
return identical(this, other) || (other.runtimeType == runtimeType&&other is AppSettings&&(identical(other.autoTranslate, autoTranslate) || other.autoTranslate == autoTranslate)&&(identical(other.soundEffects, soundEffects) || other.soundEffects == soundEffects)&&(identical(other.aprilFoolFeatures, aprilFoolFeatures) || other.aprilFoolFeatures == aprilFoolFeatures)&&(identical(other.enterToSend, enterToSend) || other.enterToSend == enterToSend)&&(identical(other.appBarTransparent, appBarTransparent) || other.appBarTransparent == appBarTransparent)&&(identical(other.customFonts, customFonts) || other.customFonts == customFonts)&&(identical(other.appColorScheme, appColorScheme) || other.appColorScheme == appColorScheme));
}
@override
int get hashCode => Object.hash(runtimeType,autoTranslate,soundEffects,aprilFoolFeatures,enterToSend);
int get hashCode => Object.hash(runtimeType,autoTranslate,soundEffects,aprilFoolFeatures,enterToSend,appBarTransparent,customFonts,appColorScheme);
@override
String toString() {
return 'AppSettings(autoTranslate: $autoTranslate, soundEffects: $soundEffects, aprilFoolFeatures: $aprilFoolFeatures, enterToSend: $enterToSend)';
return 'AppSettings(autoTranslate: $autoTranslate, soundEffects: $soundEffects, aprilFoolFeatures: $aprilFoolFeatures, enterToSend: $enterToSend, appBarTransparent: $appBarTransparent, customFonts: $customFonts, appColorScheme: $appColorScheme)';
}
@ -46,7 +46,7 @@ abstract mixin class $AppSettingsCopyWith<$Res> {
factory $AppSettingsCopyWith(AppSettings value, $Res Function(AppSettings) _then) = _$AppSettingsCopyWithImpl;
@useResult
$Res call({
bool autoTranslate, bool soundEffects, bool aprilFoolFeatures, bool enterToSend
bool autoTranslate, bool soundEffects, bool aprilFoolFeatures, bool enterToSend, bool appBarTransparent, String? customFonts, int? appColorScheme
});
@ -63,13 +63,16 @@ class _$AppSettingsCopyWithImpl<$Res>
/// Create a copy of AppSettings
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? autoTranslate = null,Object? soundEffects = null,Object? aprilFoolFeatures = null,Object? enterToSend = null,}) {
@pragma('vm:prefer-inline') @override $Res call({Object? autoTranslate = null,Object? soundEffects = null,Object? aprilFoolFeatures = null,Object? enterToSend = null,Object? appBarTransparent = null,Object? customFonts = freezed,Object? appColorScheme = freezed,}) {
return _then(_self.copyWith(
autoTranslate: null == autoTranslate ? _self.autoTranslate : autoTranslate // ignore: cast_nullable_to_non_nullable
as bool,soundEffects: null == soundEffects ? _self.soundEffects : soundEffects // ignore: cast_nullable_to_non_nullable
as bool,aprilFoolFeatures: null == aprilFoolFeatures ? _self.aprilFoolFeatures : aprilFoolFeatures // ignore: cast_nullable_to_non_nullable
as bool,enterToSend: null == enterToSend ? _self.enterToSend : enterToSend // ignore: cast_nullable_to_non_nullable
as bool,
as bool,appBarTransparent: null == appBarTransparent ? _self.appBarTransparent : appBarTransparent // ignore: cast_nullable_to_non_nullable
as bool,customFonts: freezed == customFonts ? _self.customFonts : customFonts // ignore: cast_nullable_to_non_nullable
as String?,appColorScheme: freezed == appColorScheme ? _self.appColorScheme : appColorScheme // ignore: cast_nullable_to_non_nullable
as int?,
));
}
@ -80,13 +83,16 @@ as bool,
class _AppSettings implements AppSettings {
const _AppSettings({required this.autoTranslate, required this.soundEffects, required this.aprilFoolFeatures, required this.enterToSend});
const _AppSettings({required this.autoTranslate, required this.soundEffects, required this.aprilFoolFeatures, required this.enterToSend, required this.appBarTransparent, required this.customFonts, required this.appColorScheme});
@override final bool autoTranslate;
@override final bool soundEffects;
@override final bool aprilFoolFeatures;
@override final bool enterToSend;
@override final bool appBarTransparent;
@override final String? customFonts;
@override final int? appColorScheme;
/// Create a copy of AppSettings
/// with the given fields replaced by the non-null parameter values.
@ -98,16 +104,16 @@ _$AppSettingsCopyWith<_AppSettings> get copyWith => __$AppSettingsCopyWithImpl<_
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _AppSettings&&(identical(other.autoTranslate, autoTranslate) || other.autoTranslate == autoTranslate)&&(identical(other.soundEffects, soundEffects) || other.soundEffects == soundEffects)&&(identical(other.aprilFoolFeatures, aprilFoolFeatures) || other.aprilFoolFeatures == aprilFoolFeatures)&&(identical(other.enterToSend, enterToSend) || other.enterToSend == enterToSend));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _AppSettings&&(identical(other.autoTranslate, autoTranslate) || other.autoTranslate == autoTranslate)&&(identical(other.soundEffects, soundEffects) || other.soundEffects == soundEffects)&&(identical(other.aprilFoolFeatures, aprilFoolFeatures) || other.aprilFoolFeatures == aprilFoolFeatures)&&(identical(other.enterToSend, enterToSend) || other.enterToSend == enterToSend)&&(identical(other.appBarTransparent, appBarTransparent) || other.appBarTransparent == appBarTransparent)&&(identical(other.customFonts, customFonts) || other.customFonts == customFonts)&&(identical(other.appColorScheme, appColorScheme) || other.appColorScheme == appColorScheme));
}
@override
int get hashCode => Object.hash(runtimeType,autoTranslate,soundEffects,aprilFoolFeatures,enterToSend);
int get hashCode => Object.hash(runtimeType,autoTranslate,soundEffects,aprilFoolFeatures,enterToSend,appBarTransparent,customFonts,appColorScheme);
@override
String toString() {
return 'AppSettings(autoTranslate: $autoTranslate, soundEffects: $soundEffects, aprilFoolFeatures: $aprilFoolFeatures, enterToSend: $enterToSend)';
return 'AppSettings(autoTranslate: $autoTranslate, soundEffects: $soundEffects, aprilFoolFeatures: $aprilFoolFeatures, enterToSend: $enterToSend, appBarTransparent: $appBarTransparent, customFonts: $customFonts, appColorScheme: $appColorScheme)';
}
@ -118,7 +124,7 @@ abstract mixin class _$AppSettingsCopyWith<$Res> implements $AppSettingsCopyWith
factory _$AppSettingsCopyWith(_AppSettings value, $Res Function(_AppSettings) _then) = __$AppSettingsCopyWithImpl;
@override @useResult
$Res call({
bool autoTranslate, bool soundEffects, bool aprilFoolFeatures, bool enterToSend
bool autoTranslate, bool soundEffects, bool aprilFoolFeatures, bool enterToSend, bool appBarTransparent, String? customFonts, int? appColorScheme
});
@ -135,13 +141,16 @@ class __$AppSettingsCopyWithImpl<$Res>
/// Create a copy of AppSettings
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? autoTranslate = null,Object? soundEffects = null,Object? aprilFoolFeatures = null,Object? enterToSend = null,}) {
@override @pragma('vm:prefer-inline') $Res call({Object? autoTranslate = null,Object? soundEffects = null,Object? aprilFoolFeatures = null,Object? enterToSend = null,Object? appBarTransparent = null,Object? customFonts = freezed,Object? appColorScheme = freezed,}) {
return _then(_AppSettings(
autoTranslate: null == autoTranslate ? _self.autoTranslate : autoTranslate // ignore: cast_nullable_to_non_nullable
as bool,soundEffects: null == soundEffects ? _self.soundEffects : soundEffects // ignore: cast_nullable_to_non_nullable
as bool,aprilFoolFeatures: null == aprilFoolFeatures ? _self.aprilFoolFeatures : aprilFoolFeatures // ignore: cast_nullable_to_non_nullable
as bool,enterToSend: null == enterToSend ? _self.enterToSend : enterToSend // ignore: cast_nullable_to_non_nullable
as bool,
as bool,appBarTransparent: null == appBarTransparent ? _self.appBarTransparent : appBarTransparent // ignore: cast_nullable_to_non_nullable
as bool,customFonts: freezed == customFonts ? _self.customFonts : customFonts // ignore: cast_nullable_to_non_nullable
as String?,appColorScheme: freezed == appColorScheme ? _self.appColorScheme : appColorScheme // ignore: cast_nullable_to_non_nullable
as int?,
));
}

28
lib/pods/config.g.dart Normal file
View File

@ -0,0 +1,28 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'config.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$appSettingsNotifierHash() =>
r'4f727d448ee17a87b5698b8e36ef67521655406c';
/// See also [AppSettingsNotifier].
@ProviderFor(AppSettingsNotifier)
final appSettingsNotifierProvider =
AutoDisposeNotifierProvider<AppSettingsNotifier, AppSettings>.internal(
AppSettingsNotifier.new,
name: r'appSettingsNotifierProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$appSettingsNotifierHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$AppSettingsNotifier = AutoDisposeNotifier<AppSettings>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package

View File

@ -1,6 +1,5 @@
import 'dart:async';
import 'dart:convert';
import 'dart:developer';
import 'dart:io';
import 'package:flutter/foundation.dart';
@ -68,16 +67,9 @@ final apiClientProvider = Provider<Dio>((ref) {
RequestInterceptorHandler handler,
) async {
try {
final atk = await getFreshAtk(
ref.watch(tokenPairProvider),
ref.watch(serverUrlProvider),
onRefreshed: (atk, rtk) {
setTokenPair(ref.watch(sharedPreferencesProvider), atk, rtk);
ref.invalidate(tokenPairProvider);
},
);
if (atk != null) {
options.headers['Authorization'] = 'Bearer $atk';
final token = await getToken(ref.watch(tokenProvider));
if (token != null) {
options.headers['Authorization'] = 'AtField $token';
}
} catch (err) {
// ignore
@ -95,105 +87,21 @@ final apiClientProvider = Provider<Dio>((ref) {
return dio;
});
final tokenPairProvider = Provider<AppTokenPair?>((ref) {
final tokenProvider = Provider<AppToken?>((ref) {
final prefs = ref.watch(sharedPreferencesProvider);
final tkPairString = prefs.getString(kTokenPairStoreKey);
if (tkPairString == null) return null;
return AppTokenPair.fromJson(jsonDecode(tkPairString));
final tokenString = prefs.getString(kTokenPairStoreKey);
if (tokenString == null) return null;
return AppToken.fromJson(jsonDecode(tokenString));
});
Future<(String, String)?> refreshToken(String baseUrl, String? rtk) async {
if (rtk == null) return null;
// Token refresh functionality removed as per backend changes
final dio = Dio();
dio.options.baseUrl = baseUrl;
final resp = await dio.post(
'/auth/token',
data: {'grant_type': 'refresh_token', 'refresh_token': rtk},
);
final String atk = resp.data['access_token'];
final String nRtk = resp.data['refresh_token'];
return (atk, nRtk);
Future<String?> getToken(AppToken? token) async {
return token?.token;
}
Completer<String?>? _refreshCompleter;
Future<String?> getFreshAtk(
AppTokenPair? tkPair,
String baseUrl, {
Function(String, String)? onRefreshed,
}) async {
var atk = tkPair?.accessToken;
var rtk = tkPair?.refreshToken;
if (_refreshCompleter != null) {
return await _refreshCompleter!.future;
} else {
_refreshCompleter = Completer<String?>();
}
try {
if (atk != null) {
final atkParts = atk.split('.');
if (atkParts.length != 3) {
throw Exception('invalid format of access token');
}
var rawPayload = atkParts[1].replaceAll('-', '+').replaceAll('_', '/');
switch (rawPayload.length % 4) {
case 0:
break;
case 2:
rawPayload += '==';
break;
case 3:
rawPayload += '=';
break;
default:
throw Exception('illegal format of access token payload');
}
final b64 = utf8.fuse(base64Url);
final payload = b64.decode(rawPayload);
final exp = jsonDecode(payload)['exp'];
if (exp <= DateTime.now().millisecondsSinceEpoch ~/ 1000) {
log('[Auth] Access token need refresh, doing it at ${DateTime.now()}');
final result = await refreshToken(baseUrl, rtk);
if (result == null) {
atk = null;
} else {
onRefreshed?.call(result.$1, result.$2);
atk = result.$1;
}
}
if (atk != null) {
_refreshCompleter!.complete(atk);
return atk;
} else {
log('[Auth] Access token refresh failed...');
_refreshCompleter!.complete(null);
}
}
} catch (err) {
log('[Auth] Failed to authenticate user... $err');
_refreshCompleter!.completeError(err);
} finally {
_refreshCompleter = null;
}
return null;
}
Future<void> setTokenPair(
SharedPreferences prefs,
String atk,
String rtk,
) async {
final tkPair = AppTokenPair(accessToken: atk, refreshToken: rtk);
final tkPairString = jsonEncode(tkPair);
prefs.setString(kTokenPairStoreKey, tkPairString);
Future<void> setToken(SharedPreferences prefs, String token) async {
final appToken = AppToken(token: token);
final tokenString = jsonEncode(appToken);
prefs.setString(kTokenPairStoreKey, tokenString);
}

View File

@ -13,7 +13,7 @@ class UserInfoNotifier extends StateNotifier<AsyncValue<SnAccount?>> {
Future<String?> getAccessToken() async {
final prefs = _ref.read(sharedPreferencesProvider);
return prefs.getString('dyn_user_atk');
return prefs.getString(kTokenPairStoreKey);
}
Future<void> fetchUser() async {

View File

@ -54,25 +54,18 @@ class WebSocketService {
_statusStreamController.sink.add(WebSocketState.connecting());
final baseUrl = ref.watch(serverUrlProvider);
final atk = await getFreshAtk(
ref.watch(tokenPairProvider),
baseUrl,
onRefreshed: (atk, rtk) {
setTokenPair(ref.watch(sharedPreferencesProvider), atk, rtk);
ref.invalidate(tokenPairProvider);
},
);
final token = await getToken(ref.watch(tokenProvider));
final url = '$baseUrl/ws'.replaceFirst('http', 'ws');
log('[WebSocket] Trying connecting to $url');
try {
if (kIsWeb) {
_channel = WebSocketChannel.connect(Uri.parse('$url?tk=$atk'));
_channel = WebSocketChannel.connect(Uri.parse('$url?tk=$token'));
} else {
_channel = IOWebSocketChannel.connect(
Uri.parse(url),
headers: {'Authorization': 'Bearer $atk'},
headers: {'Authorization': 'Bearer $token'},
);
}
await _channel!.ready;

View File

@ -329,7 +329,7 @@ class ChatListRouteArgs {
/// [_i7.ChatRoomScreen]
class ChatRoomRoute extends _i27.PageRouteInfo<ChatRoomRouteArgs> {
ChatRoomRoute({
_i28.Key? key,
_i29.Key? key,
required String id,
List<_i27.PageRouteInfo>? children,
}) : super(
@ -356,7 +356,7 @@ class ChatRoomRoute extends _i27.PageRouteInfo<ChatRoomRouteArgs> {
class ChatRoomRouteArgs {
const ChatRoomRouteArgs({this.key, required this.id});
final _i28.Key? key;
final _i29.Key? key;
final String id;
@ -1071,10 +1071,17 @@ class PostComposeRoute extends _i27.PageRouteInfo<PostComposeRouteArgs> {
PostComposeRoute({
_i28.Key? key,
_i30.SnPost? originalPost,
_i30.SnPost? repliedPost,
_i30.SnPost? forwardedPost,
List<_i27.PageRouteInfo>? children,
}) : super(
PostComposeRoute.name,
args: PostComposeRouteArgs(key: key, originalPost: originalPost),
args: PostComposeRouteArgs(
key: key,
originalPost: originalPost,
repliedPost: repliedPost,
forwardedPost: forwardedPost,
),
initialChildren: children,
);
@ -1089,32 +1096,50 @@ class PostComposeRoute extends _i27.PageRouteInfo<PostComposeRouteArgs> {
return _i18.PostComposeScreen(
key: args.key,
originalPost: args.originalPost,
repliedPost: args.repliedPost,
forwardedPost: args.forwardedPost,
);
},
);
}
class PostComposeRouteArgs {
const PostComposeRouteArgs({this.key, this.originalPost});
const PostComposeRouteArgs({
this.key,
this.originalPost,
this.repliedPost,
this.forwardedPost,
});
final _i28.Key? key;
final _i30.SnPost? originalPost;
final _i30.SnPost? repliedPost;
final _i30.SnPost? forwardedPost;
@override
String toString() {
return 'PostComposeRouteArgs{key: $key, originalPost: $originalPost}';
return 'PostComposeRouteArgs{key: $key, originalPost: $originalPost, repliedPost: $repliedPost, forwardedPost: $forwardedPost}';
}
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
if (other is! PostComposeRouteArgs) return false;
return key == other.key && originalPost == other.originalPost;
return key == other.key &&
originalPost == other.originalPost &&
repliedPost == other.repliedPost &&
forwardedPost == other.forwardedPost;
}
@override
int get hashCode => key.hashCode ^ originalPost.hashCode;
int get hashCode =>
key.hashCode ^
originalPost.hashCode ^
repliedPost.hashCode ^
forwardedPost.hashCode;
}
/// generated route for

View File

@ -74,7 +74,7 @@ class AccountScreen extends HookConsumerWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (user.value?.profile.backgroundId != null)
if (user.value?.profile.background?.id != null)
ClipRRect(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(8),
@ -83,7 +83,7 @@ class AccountScreen extends HookConsumerWidget {
child: AspectRatio(
aspectRatio: 16 / 7,
child: CloudImageWidget(
fileId: user.value!.profile.backgroundId!,
fileId: user.value!.profile.background!.id,
fit: BoxFit.cover,
),
),
@ -94,7 +94,7 @@ class AccountScreen extends HookConsumerWidget {
children: [
GestureDetector(
child: ProfilePictureWidget(
fileId: user.value?.profile.pictureId,
fileId: user.value?.profile.picture?.id,
radius: 24,
),
onTap: () {
@ -254,8 +254,8 @@ class AccountScreen extends HookConsumerWidget {
contentPadding: EdgeInsets.symmetric(horizontal: 24),
title: Text('Copy access token'),
onTap: () async {
final tk = ref.watch(tokenPairProvider);
Clipboard.setData(ClipboardData(text: tk!.accessToken));
final tk = ref.watch(tokenProvider);
Clipboard.setData(ClipboardData(text: tk!.token));
},
),
if (kDebugMode)

View File

@ -202,7 +202,7 @@ class EventCalanderScreen extends HookConsumerWidget {
color: Colors.transparent,
child: ListTile(
leading: ProfilePictureWidget(
fileId: user.value!.profile.pictureId,
fileId: user.value!.profile.picture?.id,
),
title: Text(user.value!.nick).bold(),
subtitle: Text('@${user.value!.name}'),

View File

@ -1,3 +1,4 @@
import 'dart:convert';
import 'dart:io';
import 'package:auto_route/annotations.dart';
@ -5,16 +6,45 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
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/models/user.dart';
import 'package:island/pods/network.dart';
import 'package:island/pods/userinfo.dart';
import 'package:island/screens/auth/captcha.dart';
import 'package:island/screens/auth/login.dart';
import 'package:island/services/responsive.dart';
import 'package:island/widgets/account/account_session_sheet.dart';
import 'package:island/widgets/alert.dart';
import 'package:island/widgets/app_scaffold.dart';
import 'package:island/widgets/content/sheet.dart';
import 'package:island/widgets/response.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:qr_flutter/qr_flutter.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:styled_widget/styled_widget.dart';
part 'settings.g.dart';
@riverpod
Future<List<SnAuthFactor>> authFactors(Ref ref) async {
final client = ref.read(apiClientProvider);
final res = await client.get('/accounts/me/factors');
return res.data.map<SnAuthFactor>((e) => SnAuthFactor.fromJson(e)).toList();
}
@riverpod
Future<List<SnContactMethod>> contactMethods(Ref ref) async {
final client = ref.read(apiClientProvider);
final resp = await client.get('/accounts/me/contacts');
return resp.data
.map<SnContactMethod>((e) => SnContactMethod.fromJson(e))
.toList();
}
@RoutePage()
class AccountSettingsScreen extends HookConsumerWidget {
const AccountSettingsScreen({super.key});
@ -32,6 +62,7 @@ class AccountSettingsScreen extends HookConsumerWidget {
);
if (!confirm || !context.mounted) return;
try {
showLoadingModal(context);
final client = ref.read(apiClientProvider);
await client.delete('/accounts/me');
if (context.mounted) {
@ -39,13 +70,15 @@ class AccountSettingsScreen extends HookConsumerWidget {
}
} catch (err) {
showErrorAlert(err);
} finally {
if (context.mounted) hideLoadingModal(context);
}
}
Future<void> requestResetPassword() async {
final confirm = await showConfirmAlert(
'accountPasswordChangeDescription'.tr(),
'accountPassword'.tr(),
'accountPasswordChange'.tr(),
);
if (!confirm || !context.mounted) return;
final captchaTk = await Navigator.of(
@ -53,6 +86,7 @@ class AccountSettingsScreen extends HookConsumerWidget {
).push(MaterialPageRoute(builder: (context) => CaptchaScreen()));
if (captchaTk == null) return;
try {
showLoadingModal(context);
final userInfo = ref.read(userInfoProvider);
final client = ref.read(apiClientProvider);
await client.post(
@ -64,84 +98,237 @@ class AccountSettingsScreen extends HookConsumerWidget {
}
} catch (err) {
showErrorAlert(err);
} finally {
if (context.mounted) hideLoadingModal(context);
}
}
final authFactors = ref.watch(authFactorsProvider);
// Group settings into categories for better organization
final securitySettings = [
ListTile(
minLeadingWidth: 48,
title: Text('accountPassword').tr(),
subtitle: Text('accountPasswordDescription').tr().fontSize(12),
leading: const Icon(Symbols.devices),
title: Text('authSessions').tr(),
subtitle: Text('authSessionsDescription').tr().fontSize(12),
contentPadding: const EdgeInsets.only(left: 24, right: 17),
leading: const Icon(Symbols.password),
trailing: const Icon(Symbols.chevron_right),
onTap: () {
requestResetPassword();
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (context) => const AccountSessionSheet(),
);
},
),
ListTile(
minLeadingWidth: 48,
title: Text('accountTwoFactor').tr(),
subtitle: Text('accountTwoFactorDescription').tr().fontSize(12),
contentPadding: const EdgeInsets.only(left: 24, right: 17),
leading: const Icon(Symbols.security),
trailing: const Icon(Symbols.chevron_right),
onTap: () {
// Navigate to two-factor authentication settings
showDialog(
context: context,
builder:
(context) => AlertDialog(
title: Text('accountTwoFactor').tr(),
content: Text('accountTwoFactorSetupDescription').tr(),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text('Close').tr(),
),
TextButton(
onPressed: () {
Navigator.of(context).pop();
// Add navigation to 2FA setup screen
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(),
),
],
),
];
final privacySettings = [
// ListTile(
// minLeadingWidth: 48,
// title: Text('accountPrivacy').tr(),
// subtitle: Text('accountPrivacyDescription').tr().fontSize(12),
// contentPadding: const EdgeInsets.only(left: 24, right: 17),
// leading: const Icon(Symbols.visibility),
// trailing: const Icon(Symbols.chevron_right),
// onTap: () {
// // Navigate to privacy settings
// },
// ),
ListTile(
minLeadingWidth: 48,
title: Text('accountDataExport').tr(),
subtitle: Text('accountDataExportDescription').tr().fontSize(12),
contentPadding: const EdgeInsets.only(left: 24, right: 17),
leading: const Icon(Symbols.download),
trailing: const Icon(Symbols.chevron_right),
onTap: () async {
final confirm = await showConfirmAlert(
'accountDataExportConfirmation'.tr(),
'accountDataExport'.tr(),
);
if (!confirm || !context.mounted) return;
// Add data export logic
showSnackBar(context, 'accountDataExportRequested'.tr());
},
ExpansionTile(
leading: const Icon(
Symbols.contact_mail,
).alignment(Alignment.centerLeft).width(48),
title: Text('accountContactMethod').tr(),
subtitle: Text('accountContactMethodDescription').tr().fontSize(12),
tilePadding: const EdgeInsets.only(left: 24, right: 17),
children: [
ref
.watch(contactMethodsProvider)
.when(
data:
(contacts) => Column(
children: [
for (final contact in contacts)
ListTile(
minLeadingWidth: 48,
contentPadding: const EdgeInsets.only(
left: 16,
right: 17,
top: 2,
bottom: 4,
),
title: Text(
contact.content,
style:
contact.verifiedAt == null
? TextStyle(
decoration: TextDecoration.lineThrough,
)
: null,
),
subtitle: Text(
contact.type == 0
? 'contactMethodTypeEmail'.tr()
: 'contactMethodTypePhone'.tr(),
style:
contact.verifiedAt == null
? TextStyle(
decoration: TextDecoration.lineThrough,
)
: null,
),
leading: CircleAvatar(
backgroundColor:
contact.verifiedAt == null
? Theme.of(
context,
).colorScheme.secondaryContainer
: Theme.of(
context,
).colorScheme.primaryContainer,
child: Icon(
contact.type == 0
? Symbols.mail
: Symbols.phone,
),
).padding(top: 4),
trailing: const Icon(Symbols.chevron_right),
isThreeLine: false,
onTap: () {
showModalBottomSheet(
context: context,
builder:
(context) =>
_ContactMethodSheet(contact: contact),
).then((value) {
if (value == true) {
ref.invalidate(contactMethodsProvider);
}
});
},
),
if (contacts.isNotEmpty) const Divider(height: 1),
ListTile(
minLeadingWidth: 48,
contentPadding: const EdgeInsets.only(
left: 24,
right: 17,
),
title: Text('contactMethodNew').tr(),
leading: const Icon(Symbols.add),
trailing: const Icon(Symbols.chevron_right),
onTap: () {
showModalBottomSheet(
context: context,
builder:
(context) => const _ContactMethodNewSheet(),
).then((value) {
if (value == true) {
ref.invalidate(contactMethodsProvider);
}
});
},
),
],
),
error:
(err, _) => ResponseErrorWidget(
error: err,
onRetry: () => ref.invalidate(contactMethodsProvider),
),
loading: () => const ResponseLoadingWidget(),
),
],
),
];
@ -172,10 +359,6 @@ class AccountSettingsScreen extends HookConsumerWidget {
title: 'accountSecurityTitle',
children: securitySettings,
),
_SettingsSection(
title: 'accountPrivacyTitle',
children: privacySettings,
),
],
),
),
@ -201,10 +384,6 @@ class AccountSettingsScreen extends HookConsumerWidget {
title: 'accountSecurityTitle',
children: securitySettings,
),
_SettingsSection(
title: 'accountPrivacyTitle',
children: privacySettings,
),
_SettingsSection(
title: 'accountDangerZoneTitle',
children: dangerZoneSettings,
@ -292,3 +471,599 @@ class _SettingsSection extends StatelessWidget {
);
}
}
class _AuthFactorSheet extends HookConsumerWidget {
final SnAuthFactor factor;
const _AuthFactorSheet({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();
@override
Widget build(BuildContext context, WidgetRef ref) {
final factorType = useState<int>(0);
final secretController = useTextEditingController();
Future<void> addFactor() async {
try {
showLoadingModal(context);
final apiClient = ref.read(apiClientProvider);
final resp = await apiClient.post(
'/accounts/me/factors',
data: {'type': factorType.value, 'secret': secretController.text},
);
final factor = SnAuthFactor.fromJson(resp.data);
if (!context.mounted) return;
hideLoadingModal(context);
if (factor.type == 3) {
showModalBottomSheet(
context: context,
builder: (context) => _AuthFactorNewAdditonalSheet(factor: factor),
).then((_) {
if (context.mounted) {
showSnackBar(context, 'contactMethodVerificationNeeded'.tr());
}
if (context.mounted) Navigator.pop(context, true);
});
} else {
Navigator.pop(context, true);
}
} catch (err) {
showErrorAlert(err);
if (context.mounted) hideLoadingModal(context);
}
}
return SheetScaffold(
titleText: 'authFactorNew'.tr(),
child: Column(
spacing: 16,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
DropdownButtonFormField<int>(
value: factorType.value,
decoration: InputDecoration(
labelText: 'authFactor'.tr(),
border: const OutlineInputBorder(),
),
items:
kFactorTypes.entries.map((entry) {
return DropdownMenuItem<int>(
value: entry.key,
child: Row(
children: [
Icon(entry.value.$3),
const Gap(8),
Text(entry.value.$1).tr(),
],
),
);
}).toList(),
onChanged: (value) {
if (value != null) {
factorType.value = value;
}
},
),
if (factorType.value == 0)
TextField(
controller: secretController,
decoration: InputDecoration(
prefixIcon: const Icon(Symbols.password_2),
labelText: 'authFactorSecret'.tr(),
hintText: 'authFactorSecretHint'.tr(),
border: const OutlineInputBorder(),
),
onTapOutside:
(_) => FocusManager.instance.primaryFocus?.unfocus(),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Text(kFactorTypes[factorType.value]!.$2).tr(),
),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton.icon(
onPressed: addFactor,
icon: Icon(Symbols.add),
label: Text('create').tr(),
),
],
),
],
).padding(horizontal: 20, vertical: 24),
);
}
}
class _AuthFactorNewAdditonalSheet extends StatelessWidget {
final SnAuthFactor factor;
const _AuthFactorNewAdditonalSheet({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()),
),
),
],
),
);
}
}
class _ContactMethodSheet extends HookConsumerWidget {
final SnContactMethod contact;
const _ContactMethodSheet({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();
@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),
);
}
}

View File

@ -0,0 +1,48 @@
// 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>>;
// 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

View File

@ -6,6 +6,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:image_picker/image_picker.dart';
import 'package:island/models/file.dart';
import 'package:island/pods/config.dart';
import 'package:island/pods/network.dart';
import 'package:island/pods/userinfo.dart';
@ -59,19 +60,15 @@ class UpdateProfileScreen extends HookConsumerWidget {
submitting.value = true;
try {
final baseUrl = ref.watch(serverUrlProvider);
final atk = await getFreshAtk(
ref.watch(tokenPairProvider),
baseUrl,
onRefreshed: (atk, rtk) {
setTokenPair(ref.watch(sharedPreferencesProvider), atk, rtk);
ref.invalidate(tokenPairProvider);
},
);
if (atk == null) throw ArgumentError('Access token is null');
final token = await getToken(ref.watch(tokenProvider));
if (token == null) throw ArgumentError('Token is null');
final cloudFile =
await putMediaToCloud(
fileData: result,
atk: atk,
fileData: UniversalFile(
data: result,
type: UniversalFileType.image,
),
atk: token,
baseUrl: baseUrl,
filename: result.name,
mimetype: result.mimeType ?? 'image/jpeg',
@ -166,9 +163,9 @@ class UpdateProfileScreen extends HookConsumerWidget {
child: Container(
color: Theme.of(context).colorScheme.surfaceContainerHigh,
child:
user.value!.profile.backgroundId != null
user.value!.profile.background?.id != null
? CloudImageWidget(
fileId: user.value!.profile.backgroundId!,
fileId: user.value!.profile.background!.id,
fit: BoxFit.cover,
)
: const SizedBox.shrink(),
@ -182,7 +179,7 @@ class UpdateProfileScreen extends HookConsumerWidget {
bottom: -32,
child: GestureDetector(
child: ProfilePictureWidget(
fileId: user.value!.profile.pictureId,
fileId: user.value!.profile.picture?.id,
radius: 40,
),
onTap: () {

View File

@ -67,9 +67,9 @@ class AccountProfileScreen extends HookConsumerWidget {
leading: PageBackButton(shadows: [iconShadow]),
flexibleSpace: FlexibleSpaceBar(
background:
data.profile.backgroundId != null
data.profile.background?.id != null
? CloudImageWidget(
fileId: data.profile.backgroundId!,
fileId: data.profile.background!.id,
)
: Container(
color:
@ -91,7 +91,7 @@ class AccountProfileScreen extends HookConsumerWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ProfilePictureWidget(
fileId: data.profile.pictureId,
fileId: data.profile.picture?.id,
radius: 32,
),
const Gap(20),

View File

@ -100,7 +100,7 @@ class RelationshipListTile extends StatelessWidget {
return ListTile(
contentPadding: const EdgeInsets.only(left: 16, right: 12),
leading: ProfilePictureWidget(fileId: account.profile.pictureId),
leading: ProfilePictureWidget(fileId: account.profile.picture?.id),
title: Row(
spacing: 6,
children: [

View File

@ -1,9 +1,16 @@
import 'dart:convert';
import 'dart:io';
import 'dart:math' as math;
import 'package:animations/animations.dart';
import 'package:auto_route/auto_route.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:dio/dio.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_otp_text_field/flutter_otp_text_field.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:gap/gap.dart';
import 'package:island/models/auth.dart';
@ -24,12 +31,12 @@ import 'captcha.dart';
final Map<int, (String, String, IconData)> kFactorTypes = {
0: ('authFactorPassword', 'authFactorPasswordDescription', Symbols.password),
1: ('authFactorEmail', 'authFactorEmailDescription', Symbols.email),
2: ('authFactorTOTP', 'authFactorTOTPDescription', Symbols.timer),
3: (
2: (
'authFactorInAppNotify',
'authFactorInAppNotifyDescription',
Symbols.notifications_active,
),
3: ('authFactorTOTP', 'authFactorTOTPDescription', Symbols.timer),
};
@RoutePage()
@ -38,10 +45,13 @@ class LoginScreen extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final isBusy = useState(false);
final period = useState(0);
final currentTicket = useState<SnAuthChallenge?>(null);
final factors = useState<List<SnAuthFactor>>([]);
final factorPicked = useState<SnAuthFactor?>(null);
return AppScaffold(
noBackground: false,
appBar: AppBar(
@ -50,54 +60,87 @@ class LoginScreen extends HookConsumerWidget {
),
body: Theme(
data: Theme.of(context).copyWith(canvasColor: Colors.transparent),
child:
SingleChildScrollView(
child: PageTransitionSwitcher(
transitionBuilder: (
Widget child,
Animation<double> primaryAnimation,
Animation<double> secondaryAnimation,
) {
return SharedAxisTransition(
animation: primaryAnimation,
secondaryAnimation: secondaryAnimation,
transitionType: SharedAxisTransitionType.horizontal,
child: Container(
constraints: BoxConstraints(maxWidth: 380),
child: child,
),
);
},
child: switch (period.value % 3) {
1 => _LoginPickerScreen(
key: const ValueKey(1),
ticket: currentTicket.value,
factors: factors.value,
onChallenge:
(SnAuthChallenge? p0) => currentTicket.value = p0,
onPickFactor: (SnAuthFactor p0) => factorPicked.value = p0,
onNext: () => period.value++,
),
2 => _LoginCheckScreen(
key: const ValueKey(2),
challenge: currentTicket.value,
factor: factorPicked.value,
onChallenge:
(SnAuthChallenge? p0) => currentTicket.value = p0,
onNext: () => period.value++,
),
_ => _LoginLookupScreen(
key: const ValueKey(0),
ticket: currentTicket.value,
onChallenge:
(SnAuthChallenge? p0) => currentTicket.value = p0,
onFactor:
(List<SnAuthFactor>? p0) => factors.value = p0 ?? [],
onNext: () => period.value++,
),
},
).padding(all: 24),
).center(),
child: Column(
children: [
if (isBusy.value)
LinearProgressIndicator(
minHeight: 4,
borderRadius: BorderRadius.zero,
trackGap: 0,
stopIndicatorRadius: 0,
)
else if (currentTicket.value != null)
LinearProgressIndicator(
minHeight: 4,
borderRadius: BorderRadius.zero,
trackGap: 0,
stopIndicatorRadius: 0,
value:
1 -
(currentTicket.value!.stepRemain /
currentTicket.value!.stepTotal),
)
else
const Gap(4),
Expanded(
child:
SingleChildScrollView(
child: PageTransitionSwitcher(
transitionBuilder: (
Widget child,
Animation<double> primaryAnimation,
Animation<double> secondaryAnimation,
) {
return SharedAxisTransition(
animation: primaryAnimation,
secondaryAnimation: secondaryAnimation,
transitionType: SharedAxisTransitionType.horizontal,
child: Container(
constraints: BoxConstraints(maxWidth: 380),
child: child,
),
);
},
child: switch (period.value % 3) {
1 => _LoginPickerScreen(
key: const ValueKey(1),
ticket: currentTicket.value,
factors: factors.value,
onChallenge:
(SnAuthChallenge? p0) => currentTicket.value = p0,
onPickFactor:
(SnAuthFactor p0) => factorPicked.value = p0,
onNext: () => period.value++,
onBusy: (value) => isBusy.value = value,
),
2 => _LoginCheckScreen(
key: const ValueKey(2),
challenge: currentTicket.value,
factor: factorPicked.value,
onChallenge:
(SnAuthChallenge? p0) => currentTicket.value = p0,
onNext: () => period.value = 1,
onBusy: (value) => isBusy.value = value,
),
_ => _LoginLookupScreen(
key: const ValueKey(0),
ticket: currentTicket.value,
onChallenge:
(SnAuthChallenge? p0) => currentTicket.value = p0,
onFactor:
(List<SnAuthFactor>? p0) =>
factors.value = p0 ?? [],
onNext: () => period.value++,
onBusy: (value) => isBusy.value = value,
),
},
).padding(all: 24),
).center(),
),
const Gap(4),
],
),
),
);
}
@ -107,7 +150,8 @@ class _LoginCheckScreen extends HookConsumerWidget {
final SnAuthChallenge? challenge;
final SnAuthFactor? factor;
final Function(SnAuthChallenge?) onChallenge;
final Function onNext;
final VoidCallback onNext;
final Function(bool) onBusy;
const _LoginCheckScreen({
super.key,
@ -115,6 +159,7 @@ class _LoginCheckScreen extends HookConsumerWidget {
required this.factor,
required this.onChallenge,
required this.onNext,
required this.onBusy,
});
@override
@ -122,11 +167,17 @@ class _LoginCheckScreen extends HookConsumerWidget {
final isBusy = useState(false);
final passwordController = useTextEditingController();
useEffect(() {
onBusy.call(isBusy.value);
return null;
}, [isBusy]);
Future<void> performCheckTicket() async {
final pwd = passwordController.value.text;
if (pwd.isEmpty) return;
isBusy.value = true;
try {
// Pass challenge
final client = ref.watch(apiClientProvider);
final resp = await client.patch(
'/auth/challenge/${challenge!.id}',
@ -138,15 +189,18 @@ class _LoginCheckScreen extends HookConsumerWidget {
onNext();
return;
}
// Get token if challenge is completed
final tokenResp = await client.post(
'/auth/token',
data: {'grant_type': 'authorization_code', 'code': result.id},
);
final atk = tokenResp.data['access_token'];
final rtk = tokenResp.data['refresh_token'];
setTokenPair(ref.watch(sharedPreferencesProvider), atk, rtk);
ref.invalidate(tokenPairProvider);
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);
@ -155,6 +209,31 @@ class _LoginCheckScreen extends HookConsumerWidget {
wsNotifier.connect();
if (context.mounted) Navigator.pop(context, true);
});
// Update the sessions' device name is available
if (!kIsWeb) {
String? name;
if (Platform.isIOS) {
return;
// TODO waiting for apple to respond to grant my access to com.apple.developer.device-information.user-assigned-device-name
// ignore: dead_code
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),
);
}
}
} catch (err) {
showErrorAlert(err);
return;
@ -163,6 +242,8 @@ class _LoginCheckScreen extends HookConsumerWidget {
}
}
final width = math.min(380, MediaQuery.of(context).size.width);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@ -177,24 +258,49 @@ class _LoginCheckScreen extends HookConsumerWidget {
'loginEnterPassword'.tr(),
style: const TextStyle(fontSize: 28, fontWeight: FontWeight.w900),
).padding(left: 4, bottom: 16),
TextField(
autocorrect: false,
enableSuggestions: false,
controller: passwordController,
obscureText: true,
autofillHints: [
factor!.type == 0
? AutofillHints.password
: AutofillHints.oneTimeCode,
],
decoration: InputDecoration(
isDense: true,
border: const UnderlineInputBorder(),
labelText: 'password'.tr(),
if ([0].contains(factor!.type))
TextField(
autocorrect: false,
enableSuggestions: false,
controller: passwordController,
obscureText: true,
autofillHints: [
factor!.type == 0
? AutofillHints.password
: AutofillHints.oneTimeCode,
],
decoration: InputDecoration(
isDense: true,
border: const OutlineInputBorder(),
labelText: 'password'.tr(),
),
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
onSubmitted: isBusy.value ? null : (_) => performCheckTicket(),
).padding(horizontal: 7)
else
OtpTextField(
showCursor: false,
numberOfFields: 6,
obscureText: false,
showFieldAsBox: true,
focusedBorderColor: Theme.of(context).colorScheme.primary,
fieldWidth: (width / 6) - 10,
onSubmit: (value) {
passwordController.text = value;
performCheckTicket();
},
textStyle: Theme.of(context).textTheme.titleLarge!,
),
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
onSubmitted: isBusy.value ? null : (_) => performCheckTicket(),
).padding(horizontal: 7),
const Gap(12),
Card(
child: ListTile(
leading: Icon(
kFactorTypes[factor!.type]?.$3 ?? Symbols.question_mark,
),
title: Text(kFactorTypes[factor!.type]?.$1 ?? 'unknown').tr(),
subtitle: Text(kFactorTypes[factor!.type]?.$2 ?? 'unknown').tr(),
),
),
const Gap(12),
Row(
mainAxisAlignment: MainAxisAlignment.end,
@ -221,7 +327,8 @@ class _LoginPickerScreen extends HookConsumerWidget {
final List<SnAuthFactor>? factors;
final Function(SnAuthChallenge?) onChallenge;
final Function(SnAuthFactor) onPickFactor;
final Function onNext;
final VoidCallback onNext;
final Function(bool) onBusy;
const _LoginPickerScreen({
super.key,
@ -230,17 +337,25 @@ class _LoginPickerScreen extends HookConsumerWidget {
required this.onChallenge,
required this.onPickFactor,
required this.onNext,
required this.onBusy,
});
@override
Widget build(BuildContext context, WidgetRef ref) {
final isBusy = useState(false);
final factorPicked = useState<String?>(null);
final factorPicked = useState<SnAuthFactor?>(null);
useEffect(() {
onBusy.call(isBusy.value);
return null;
}, [isBusy]);
final unfocusColor = Theme.of(
context,
).colorScheme.onSurface.withAlpha((255 * 0.75).round());
final hintController = useTextEditingController();
void performGetFactorCode() async {
if (factorPicked.value == null) return;
@ -248,13 +363,24 @@ class _LoginPickerScreen extends HookConsumerWidget {
final client = ref.watch(apiClientProvider);
try {
// Request one-time-password code
await client.post(
'/auth/challenge/${ticket!.id}/factors/${factorPicked.value}',
'/auth/challenge/${ticket!.id}/factors/${factorPicked.value!.id}',
data:
hintController.text.isNotEmpty
? jsonEncode(hintController.text)
: null,
);
onPickFactor(factors!.where((x) => x.id == factorPicked.value).first);
onPickFactor(factors!.where((x) => x == factorPicked.value).first);
onNext();
} catch (err) {
if (err is DioException && err.response?.statusCode == 400) {
onPickFactor(factors!.where((x) => x == factorPicked.value).first);
onNext();
if (context.mounted) {
showSnackBar(context, err.response!.data.toString());
}
return;
}
showErrorAlert(err);
return;
} finally {
@ -262,6 +388,20 @@ class _LoginPickerScreen extends HookConsumerWidget {
}
}
useEffect(() {
if (ticket == null || (factors?.isEmpty ?? true)) return;
if (ticket!.blacklistFactors.isEmpty) {
Future(() {
var password = factors!.where((x) => x.type == 0).firstOrNull;
if (password != null) {
factorPicked.value = password;
performGetFactorCode();
}
});
}
return null;
}, [ticket, factors]);
return Column(
key: const ValueKey<int>(1),
crossAxisAlignment: CrossAxisAlignment.start,
@ -293,10 +433,10 @@ class _LoginPickerScreen extends HookConsumerWidget {
),
title: Text(kFactorTypes[x.type]?.$1 ?? 'unknown').tr(),
enabled: !ticket!.blacklistFactors.contains(x.id),
value: factorPicked.value == x.id,
value: factorPicked.value == x,
onChanged: (value) {
if (value == true) {
factorPicked.value = x.id;
factorPicked.value = x;
}
},
),
@ -305,6 +445,16 @@ class _LoginPickerScreen extends HookConsumerWidget {
List.empty(),
),
),
if ([1].contains(factorPicked.value?.type))
TextField(
controller: hintController,
decoration: InputDecoration(
isDense: true,
border: const OutlineInputBorder(),
labelText: 'authFactorHint'.tr(),
helperText: 'authFactorHintHelper'.tr(),
),
).padding(top: 12, bottom: 4, horizontal: 4),
const Gap(8),
Text(
'loginMultiFactor'.plural(ticket!.stepRemain),
@ -335,7 +485,8 @@ class _LoginLookupScreen extends HookConsumerWidget {
final SnAuthChallenge? ticket;
final Function(SnAuthChallenge?) onChallenge;
final Function(List<SnAuthFactor>?) onFactor;
final Function onNext;
final VoidCallback onNext;
final Function(bool) onBusy;
const _LoginLookupScreen({
super.key,
@ -343,6 +494,7 @@ class _LoginLookupScreen extends HookConsumerWidget {
required this.onChallenge,
required this.onFactor,
required this.onNext,
required this.onBusy,
});
@override
@ -350,6 +502,11 @@ class _LoginLookupScreen extends HookConsumerWidget {
final isBusy = useState(false);
final usernameController = useTextEditingController();
useEffect(() {
onBusy.call(isBusy.value);
return null;
}, [isBusy]);
Future<void> requestResetPassword() async {
final uname = usernameController.value.text;
if (uname.isEmpty) {

View File

@ -1,3 +1,5 @@
import 'dart:developer';
import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
@ -17,6 +19,7 @@ class TabNavigationObserver extends AutoRouterObserver {
@override
void didPush(Route route, Route? previousRoute) {
log('pushed ${previousRoute?.settings.name} -> ${route.settings.name}');
if (route is DialogRoute) return;
Future(() {
onChange(route.settings.name);
@ -25,6 +28,7 @@ class TabNavigationObserver extends AutoRouterObserver {
@override
void didPop(Route route, Route? previousRoute) {
log('popped ${route.settings.name} -> ${previousRoute?.settings.name}');
if (route is DialogRoute) return;
Future(() {
onChange(previousRoute?.settings.name);
@ -45,7 +49,6 @@ class TabsNavigationWidget extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final useHorizontalLayout = isWideScreen(context);
final useExpandableLayout = isWidestScreen(context);
final currentRoute = ref.watch(currentRouteProvider);
final notificationUnreadCount = ref.watch(
@ -82,6 +85,7 @@ class TabsNavigationWidget extends HookConsumerWidget {
];
final routeNames = [
ExploreRoute.name,
ExploreShellRoute.name,
ChatListRoute.name,
RealmListRoute.name,
AccountRoute.name,
@ -112,8 +116,6 @@ class TabsNavigationWidget extends HookConsumerWidget {
Gap(MediaQuery.of(context).padding.top + 8),
Expanded(
child: NavigationRail(
minExtendedWidth: 200,
extended: useExpandableLayout,
selectedIndex: activeIndex,
onDestinationSelected: (index) {
router.replace(routes[index]);

View File

@ -166,7 +166,8 @@ class CallScreen extends HookConsumerWidget {
.profile
?.account
.profile
.pictureId,
.picture
?.id,
size: 72,
),
),

View File

@ -24,6 +24,7 @@ import 'package:island/widgets/alert.dart';
import 'package:island/widgets/app_scaffold.dart';
import 'package:island/widgets/chat/call_overlay.dart';
import 'package:island/widgets/content/cloud_files.dart';
import 'package:island/widgets/content/sheet.dart';
import 'package:island/widgets/realms/selection_dropdown.dart';
import 'package:island/widgets/response.dart';
import 'package:material_symbols_icons/symbols.dart';
@ -109,7 +110,7 @@ class ChatRoomListTile extends HookConsumerWidget {
},
loading: () => const SizedBox.shrink(),
error:
(_, __) =>
(_, _) =>
isDirect && room.description == null
? Text(
room.members!.map((e) => '@${e.account.name}').join(', '),
@ -127,19 +128,19 @@ class ChatRoomListTile extends HookConsumerWidget {
isLabelVisible: summary.when(
data: (data) => (data?.unreadCount ?? 0) > 0,
loading: () => false,
error: (_, __) => false,
error: (_, _) => false,
),
child:
(isDirect && room.pictureId == null)
(isDirect && room.picture?.id == null)
? SplitAvatarWidget(
filesId:
room.members!
.map((e) => e.account.profile.pictureId)
.map((e) => e.account.profile.picture?.id)
.toList(),
)
: room.pictureId == null
: room.picture?.id == null
? CircleAvatar(child: Text(room.name![0].toUpperCase()))
: ProfilePictureWidget(fileId: room.pictureId),
: ProfilePictureWidget(fileId: room.picture?.id),
),
title: Text(
(isDirect && room.name == null)
@ -147,6 +148,7 @@ class ChatRoomListTile extends HookConsumerWidget {
: room.name ?? '',
),
subtitle: buildSubtitle(),
trailing: trailing, // Add this line
onTap: () async {
// Clear unread count if there are unread messages
ref.read(chatSummaryProvider.future).then((summary) {
@ -279,13 +281,13 @@ class ChatListScreen extends HookConsumerWidget {
label: Text(
chatInvites.when(
data: (invites) => invites.length.toString(),
error: (_, __) => '0',
error: (_, _) => '0',
loading: () => '0',
),
),
isLabelVisible: chatInvites.when(
data: (invites) => invites.isNotEmpty,
error: (_, __) => false,
error: (_, _) => false,
loading: () => false,
),
child: const Icon(Symbols.email),
@ -345,7 +347,11 @@ class ChatListScreen extends HookConsumerWidget {
builder: (context, ref, _) {
final summaryState = ref.watch(chatSummaryProvider);
return summaryState.maybeWhen(
loading: () => const LinearProgressIndicator(),
loading:
() => const LinearProgressIndicator(
minHeight: 2,
borderRadius: BorderRadius.zero,
),
orElse: () => const SizedBox.shrink(),
);
},
@ -522,19 +528,15 @@ class EditChatScreen extends HookConsumerWidget {
submitting.value = true;
try {
final baseUrl = ref.watch(serverUrlProvider);
final atk = await getFreshAtk(
ref.watch(tokenPairProvider),
baseUrl,
onRefreshed: (atk, rtk) {
setTokenPair(ref.watch(sharedPreferencesProvider), atk, rtk);
ref.invalidate(tokenPairProvider);
},
);
if (atk == null) throw ArgumentError('Access token is null');
final token = await getToken(ref.watch(tokenProvider));
if (token == null) throw ArgumentError('Token is null');
final cloudFile =
await putMediaToCloud(
fileData: result,
atk: atk,
fileData: UniversalFile(
data: result,
type: UniversalFileType.image,
),
atk: token,
baseUrl: baseUrl,
filename: result.name,
mimetype: result.mimeType ?? 'image/jpeg',
@ -595,7 +597,7 @@ class EditChatScreen extends HookConsumerWidget {
realms: joinedRealms.when(
data: (realms) => realms,
loading: () => [],
error: (_, __) => [],
error: (_, _) => [],
),
onChanged: (SnRealm? value) {
currentRealm.value = value;
@ -717,109 +719,77 @@ class _ChatInvitesSheet extends HookConsumerWidget {
}
}
return Container(
constraints: BoxConstraints(
maxHeight: MediaQuery.of(context).size.height * 0.8,
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: EdgeInsets.only(top: 16, left: 20, right: 16, bottom: 12),
child: Row(
children: [
Text(
'invites'.tr(),
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
fontWeight: FontWeight.w600,
letterSpacing: -0.5,
),
),
const Spacer(),
IconButton(
icon: const Icon(Symbols.refresh),
style: IconButton.styleFrom(minimumSize: const Size(36, 36)),
onPressed: () {
ref.invalidate(chatroomInvitesProvider);
},
),
IconButton(
icon: const Icon(Symbols.close),
onPressed: () => Navigator.pop(context),
style: IconButton.styleFrom(minimumSize: const Size(36, 36)),
),
],
),
),
const Divider(height: 1),
Expanded(
child: invites.when(
data:
(items) =>
items.isEmpty
? Center(
child:
Text(
'invitesEmpty',
textAlign: TextAlign.center,
).tr(),
)
: ListView.builder(
shrinkWrap: true,
itemCount: items.length,
itemBuilder: (context, index) {
final invite = items[index];
return ChatRoomListTile(
room: invite.chatRoom!,
isDirect: invite.chatRoom!.type == 1,
subtitle: Row(
spacing: 6,
children: [
Flexible(
child:
Text(
invite.role >= 100
? 'permissionOwner'
: invite.role >= 50
? 'permissionModerator'
: 'permissionMember',
).tr(),
),
if (invite.chatRoom!.type == 1)
Badge(
label: Text('directMessage').tr(),
backgroundColor:
Theme.of(
context,
).colorScheme.primary,
textColor:
Theme.of(
context,
).colorScheme.onPrimary,
),
],
return SheetScaffold(
titleText: 'invites'.tr(),
actions: [
IconButton(
icon: const Icon(Symbols.refresh),
style: IconButton.styleFrom(minimumSize: const Size(36, 36)),
onPressed: () {
ref.invalidate(realmInvitesProvider);
},
),
],
child: invites.when(
data:
(items) =>
items.isEmpty
? Center(
child:
Text(
'invitesEmpty',
textAlign: TextAlign.center,
).tr(),
)
: ListView.builder(
shrinkWrap: true,
itemCount: items.length,
itemBuilder: (context, index) {
final invite = items[index];
return ChatRoomListTile(
room: invite.chatRoom!,
isDirect: invite.chatRoom!.type == 1,
subtitle: Row(
spacing: 6,
children: [
Flexible(
child:
Text(
invite.role >= 100
? 'permissionOwner'
: invite.role >= 50
? 'permissionModerator'
: 'permissionMember',
).tr(),
),
if (invite.chatRoom!.type == 1)
Badge(
label: Text('directMessage').tr(),
backgroundColor:
Theme.of(context).colorScheme.primary,
textColor:
Theme.of(context).colorScheme.onPrimary,
),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: const Icon(Symbols.check),
onPressed: () => acceptInvite(invite),
),
IconButton(
icon: const Icon(Symbols.close),
onPressed: () => declineInvite(invite),
),
],
),
);
},
],
),
loading: () => const Center(child: CircularProgressIndicator()),
error: (error, stack) => Center(child: Text('Error: $error')),
),
),
],
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: const Icon(Symbols.check),
onPressed: () => acceptInvite(invite),
),
IconButton(
icon: const Icon(Symbols.close),
onPressed: () => declineInvite(invite),
),
],
),
);
},
),
loading: () => const Center(child: CircularProgressIndicator()),
error: (error, stack) => Center(child: Text('Error: $error')),
),
);
}

View File

@ -3,6 +3,7 @@ import 'dart:convert';
import 'dart:io';
import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
@ -18,21 +19,21 @@ import 'package:island/pods/database.dart';
import 'package:island/pods/network.dart';
import 'package:island/pods/websocket.dart';
import 'package:island/route.gr.dart';
import 'package:island/screens/posts/compose.dart';
import 'package:island/services/responsive.dart';
import 'package:island/widgets/alert.dart';
import 'package:island/widgets/app_scaffold.dart';
import 'package:island/widgets/chat/call_overlay.dart';
import 'package:island/widgets/chat/message_item.dart';
import 'package:island/widgets/content/attachment_preview.dart';
import 'package:island/widgets/content/cloud_files.dart';
import 'package:island/widgets/response.dart';
import 'package:material_symbols_icons/material_symbols_icons.dart';
import 'package:pasteboard/pasteboard.dart';
import 'package:styled_widget/styled_widget.dart';
import 'package:super_sliver_list/super_sliver_list.dart';
import 'package:uuid/uuid.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:super_clipboard/super_clipboard.dart';
import 'chat.dart';
import 'package:island/widgets/chat/call_button.dart';
@ -117,19 +118,12 @@ class MessagesNotifier extends _$MessagesNotifier {
messageRepositoryProvider(_roomId).future,
);
final baseUrl = ref.read(serverUrlProvider);
final atk = await getFreshAtk(
ref.watch(tokenPairProvider),
baseUrl,
onRefreshed: (atk, rtk) {
setTokenPair(ref.watch(sharedPreferencesProvider), atk, rtk);
ref.invalidate(tokenPairProvider);
},
);
if (atk == null) throw ArgumentError('Access token is null');
final token = await getToken(ref.watch(tokenProvider));
if (token == null) throw ArgumentError('Access token is null');
final currentMessages = state.value ?? [];
await repository.sendMessage(
atk,
token,
baseUrl,
_roomId,
content,
@ -327,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: 1000), () {
typingDebouncer.value = null;
});
}
// Add timer to remove typing status after inactivity
useEffect(() {
final removeTypingTimer = Timer.periodic(const Duration(seconds: 5), (_) {
if (typingStatuses.value.isNotEmpty) {
// Remove typing statuses older than 5 seconds
final now = DateTime.now();
typingStatuses.value =
typingStatuses.value.where((member) {
final lastTyped =
member.lastTyped ??
DateTime.now().subtract(const Duration(milliseconds: 1350));
return now.difference(lastTyped).inSeconds < 5;
}).toList();
}
});
return () => removeTypingTimer.cancel();
}, []);
var isLoading = false;
// Add scroll listener for pagination
@ -349,6 +383,28 @@ class ChatRoomScreen extends HookConsumerWidget {
void onMessage(WebSocketPacket pkt) {
if (!pkt.type.startsWith('messages')) return;
if (['messages.read'].contains(pkt.type)) return;
if (pkt.type == 'messages.typing') {
final sender = SnChatMember.fromJson(
pkt.data!['sender'],
).copyWith(lastTyped: DateTime.now());
// Check if the sender is already in the typing list
final existingIndex = typingStatuses.value.indexWhere(
(member) => member.id == sender.id,
);
if (existingIndex >= 0) {
// Update the existing entry with new timestamp
final updatedList = [...typingStatuses.value];
updatedList[existingIndex] = sender;
typingStatuses.value = updatedList;
} else {
// Add new typing status
typingStatuses.value = [...typingStatuses.value, sender];
}
return;
}
final message = SnChatMessage.fromJson(pkt.data!);
if (message.chatRoomId != chatRoom.value?.id) return;
switch (pkt.type) {
@ -422,8 +478,22 @@ class ChatRoomScreen extends HookConsumerWidget {
}
}
// Add listener to message controller for typing status
useEffect(() {
void onTextChange() {
if (messageController.text.isNotEmpty) {
sendTypingStatus();
}
}
messageController.addListener(onTextChange);
return () => messageController.removeListener(onTextChange);
}, [messageController]);
final compactHeader = isWideScreen(context);
final listController = useMemoized(() => ListController(), []);
return AppScaffold(
appBar: AppBar(
leading: !compactHeader ? const Center(child: PageBackButton()) : null,
@ -441,19 +511,23 @@ class ChatRoomScreen extends HookConsumerWidget {
height: 26,
width: 26,
child:
(room!.type == 1 && room.pictureId == null)
(room!.type == 1 && room.picture?.id == null)
? SplitAvatarWidget(
filesId:
room.members!
.map(
(e) =>
e.account.profile.pictureId,
e
.account
.profile
.picture
?.id,
)
.toList(),
)
: room.pictureId != null
: room.picture?.id != null
? ProfilePictureWidget(
fileId: room.pictureId,
fileId: room.picture?.id,
fallbackIcon: Symbols.chat,
)
: CircleAvatar(
@ -481,19 +555,23 @@ class ChatRoomScreen extends HookConsumerWidget {
height: 26,
width: 26,
child:
(room!.type == 1 && room.pictureId == null)
(room!.type == 1 && room.picture?.id == null)
? SplitAvatarWidget(
filesId:
room.members!
.map(
(e) =>
e.account.profile.pictureId,
e
.account
.profile
.picture
?.id,
)
.toList(),
)
: room.pictureId != null
: room.picture?.id != null
? ProfilePictureWidget(
fileId: room.pictureId,
fileId: room.picture?.id,
fallbackIcon: Symbols.chat,
)
: CircleAvatar(
@ -514,7 +592,7 @@ class ChatRoomScreen extends HookConsumerWidget {
),
loading: () => const Text('Loading...'),
error:
(err, __) => ResponseErrorWidget(
(err, _) => ResponseErrorWidget(
error: err,
onRetry: () => messagesNotifier.loadInitial(),
),
@ -541,11 +619,19 @@ class ChatRoomScreen extends HookConsumerWidget {
messageList.isEmpty
? Center(child: Text('No messages yet'.tr()))
: SuperListView.builder(
listController: listController,
padding: EdgeInsets.symmetric(vertical: 16),
controller: scrollController,
reverse:
true, // Show newest messages at the bottom
itemCount: messageList.length,
findChildIndexCallback: (key) {
final valueKey = key as ValueKey;
final messageId = valueKey.value as String;
return messageList.indexWhere(
(m) => m.id == messageId,
);
},
itemBuilder: (context, index) {
final message = messageList[index];
final nextMessage =
@ -602,6 +688,18 @@ class ChatRoomScreen extends HookConsumerWidget {
message.toRemoteMessage();
}
},
onJump: (messageId) {
final messageIndex = messageList
.indexWhere(
(m) => m.id == messageId,
);
listController.jumpToItem(
index: messageIndex,
scrollController:
scrollController,
alignment: 0.5,
);
},
progress:
attachmentProgress.value[message
.id],
@ -614,8 +712,9 @@ class ChatRoomScreen extends HookConsumerWidget {
onAction: null,
progress: null,
showAvatar: false,
onJump: (_) {},
),
error: (_, __) => const SizedBox.shrink(),
error: (_, _) => const SizedBox.shrink(),
);
},
),
@ -630,57 +729,135 @@ class ChatRoomScreen extends HookConsumerWidget {
),
chatRoom.when(
data:
(room) => _ChatInput(
messageController: messageController,
chatRoom: room!,
onSend: sendMessage,
onClear: () {
if (messageEditingTo.value != null) {
attachments.value.clear();
messageController.clear();
}
messageEditingTo.value = null;
messageReplyingTo.value = null;
messageForwardingTo.value = null;
},
messageEditingTo: messageEditingTo.value,
messageReplyingTo: messageReplyingTo.value,
messageForwardingTo: messageForwardingTo.value,
onPickFile: (bool isPhoto) {
if (isPhoto) {
pickPhotoMedia();
} else {
pickVideoMedia();
}
},
attachments: attachments.value,
onUploadAttachment: (_) {
// not going to do anything, only upload when send the message
},
onDeleteAttachment: (index) async {
final attachment = attachments.value[index];
if (attachment.isOnCloud) {
final client = ref.watch(apiClientProvider);
await client.delete('/files/${attachment.data.id}');
}
final clone = List.of(attachments.value);
clone.removeAt(index);
attachments.value = clone;
},
onMoveAttachment: (idx, delta) {
if (idx + delta < 0 ||
idx + delta >= attachments.value.length) {
return;
}
final clone = List.of(attachments.value);
clone.insert(idx + delta, clone.removeAt(idx));
attachments.value = clone;
},
onAttachmentsChanged: (newAttachments) {
attachments.value = newAttachments;
},
(room) => Column(
mainAxisSize: MainAxisSize.min,
children: [
AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
switchInCurve: Curves.fastEaseInToSlowEaseOut,
switchOutCurve: Curves.fastEaseInToSlowEaseOut,
transitionBuilder: (
Widget child,
Animation<double> animation,
) {
return SlideTransition(
position: Tween<Offset>(
begin: const Offset(0, -0.3),
end: Offset.zero,
).animate(
CurvedAnimation(
parent: animation,
curve: Curves.easeOutCubic,
),
),
child: SizeTransition(
sizeFactor: animation,
axisAlignment: -1.0,
child: FadeTransition(
opacity: animation,
child: child,
),
),
);
},
child:
typingStatuses.value.isNotEmpty
? Container(
key: const ValueKey('typing-indicator'),
width: double.infinity,
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 4,
),
child: Row(
children: [
const Icon(
Symbols.more_horiz,
size: 16,
).padding(horizontal: 8),
const Gap(8),
Expanded(
child: Text(
'typingHint'.plural(
typingStatuses.value.length,
args: [
typingStatuses.value
.map(
(x) =>
x.nick ??
x.account.nick,
)
.join(', '),
],
),
style:
Theme.of(
context,
).textTheme.bodySmall,
),
),
],
),
)
: const SizedBox.shrink(
key: ValueKey('no_typing'),
),
),
_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(),
),
],
@ -697,7 +874,7 @@ class ChatRoomScreen extends HookConsumerWidget {
}
}
class _ChatInput extends ConsumerWidget {
class _ChatInput extends HookConsumerWidget {
final TextEditingController messageController;
final SnChatRoom chatRoom;
final VoidCallback onSend;
@ -728,62 +905,58 @@ class _ChatInput extends ConsumerWidget {
required this.onAttachmentsChanged,
});
void _handleKeyPress(BuildContext context, WidgetRef ref, RawKeyEvent event) {
if (event is! RawKeyDownEvent) return;
final isPaste = event.logicalKey == LogicalKeyboardKey.keyV;
final isModifierPressed = event.isMetaPressed || event.isControlPressed;
if (isPaste && isModifierPressed) {
_handlePaste();
return;
}
final enterToSend = ref.read(appSettingsProvider).enterToSend;
final isEnter = event.logicalKey == LogicalKeyboardKey.enter;
if (isEnter) {
if (enterToSend && !isModifierPressed) {
onSend();
} else if (!enterToSend && isModifierPressed) {
onSend();
}
}
}
Future<void> _handlePaste() async {
final clipboard = SystemClipboard.instance;
if (clipboard == null) return;
final reader = await clipboard.read();
if (reader.canProvide(Formats.png)) {
reader.getFile(Formats.png, (file) async {
final stream = file.getStream();
final bytes = await stream.toList();
final imageBytes = bytes.expand((e) => e).toList();
// Create a temporary file to store the image
final tempDir = Directory.systemTemp;
final tempFile = File(
'${tempDir.path}/pasted_image_${DateTime.now().millisecondsSinceEpoch}.png',
);
await tempFile.writeAsBytes(imageBytes);
// Add the file to attachments
onAttachmentsChanged([
...attachments,
UniversalFile(
data: XFile(tempFile.path),
type: UniversalFileType.image,
),
]);
});
}
}
@override
Widget build(BuildContext context, WidgetRef ref) {
final enterToSend = ref.watch(appSettingsProvider).enterToSend;
final inputFocusNode = useFocusNode();
final enterToSend = ref.watch(appSettingsNotifierProvider).enterToSend;
final isMobile = !kIsWeb && (Platform.isAndroid || Platform.isIOS);
void send() {
inputFocusNode.requestFocus();
onSend.call();
}
Future<void> handlePaste() async {
final clipboard = await Pasteboard.image;
if (clipboard == null) return;
onAttachmentsChanged([
...attachments,
UniversalFile(
data: XFile.fromData(clipboard, mimeType: "image/jpeg"),
type: UniversalFileType.image,
),
]);
}
void handleKeyPress(
BuildContext context,
WidgetRef ref,
RawKeyEvent event,
) {
if (event is! RawKeyDownEvent) return;
final isPaste = event.logicalKey == LogicalKeyboardKey.keyV;
final isModifierPressed = event.isMetaPressed || event.isControlPressed;
if (isPaste && isModifierPressed) {
handlePaste();
return;
}
final enterToSend = ref.read(appSettingsNotifierProvider).enterToSend;
final isEnter = event.logicalKey == LogicalKeyboardKey.enter;
if (isEnter) {
if (enterToSend && !isModifierPressed) {
send();
} else if (!enterToSend && isModifierPressed) {
send();
}
}
}
return Material(
elevation: 8,
@ -805,7 +978,7 @@ class _ChatInput extends ConsumerWidget {
onMove: (delta) => onMoveAttachment(idx, delta),
);
},
separatorBuilder: (_, __) => const Gap(8),
separatorBuilder: (_, _) => const Gap(8),
),
).padding(top: 12),
if (messageReplyingTo != null ||
@ -886,12 +1059,23 @@ class _ChatInput extends ConsumerWidget {
Expanded(
child: RawKeyboardListener(
focusNode: FocusNode(),
onKey: (event) => _handleKeyPress(context, ref, event),
onKey: (event) => handleKeyPress(context, ref, event),
child: TextField(
focusNode: inputFocusNode,
controller: messageController,
onSubmitted: enterToSend ? (_) => onSend() : null,
onSubmitted:
(enterToSend && isMobile)
? (_) {
send();
}
: null,
keyboardType:
(enterToSend && isMobile)
? TextInputType.text
: TextInputType.multiline,
textInputAction: TextInputAction.send,
inputFormatters: [
if (enterToSend)
if (enterToSend && !isMobile)
TextInputFormatter.withFunction((oldValue, newValue) {
if (newValue.text.endsWith('\n')) {
return oldValue;
@ -926,7 +1110,7 @@ class _ChatInput extends ConsumerWidget {
IconButton(
icon: const Icon(Icons.send),
color: Theme.of(context).colorScheme.primary,
onPressed: onSend,
onPressed: send,
),
],
).padding(bottom: MediaQuery.of(context).padding.bottom),

View File

@ -6,7 +6,7 @@ part of 'room.dart';
// RiverpodGenerator
// **************************************************************************
String _$messagesNotifierHash() => r'71a9fc1c6d024f6203f06225384c19335b9b6f2c';
String _$messagesNotifierHash() => r'afc4d43f4948ec571118cef0321838a6cefc89c0';
/// Copied from Dart SDK
class _SystemHash {

View File

@ -14,10 +14,14 @@ import 'package:island/widgets/account/account_picker.dart';
import 'package:island/widgets/alert.dart';
import 'package:island/widgets/app_scaffold.dart';
import 'package:island/widgets/content/cloud_files.dart';
import 'package:island/widgets/content/paging_helper_ext.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
import 'package:styled_widget/styled_widget.dart';
part 'room_detail.freezed.dart';
part 'room_detail.g.dart';
@RoutePage()
class ChatDetailScreen extends HookConsumerWidget {
@ -48,9 +52,9 @@ class ChatDetailScreen extends HookConsumerWidget {
flexibleSpace: FlexibleSpaceBar(
background:
(currentRoom!.type == 1 &&
currentRoom.backgroundId != null)
currentRoom.background?.id != null)
? CloudImageWidget(
fileId: currentRoom.backgroundId!,
fileId: currentRoom.background!.id,
)
: (currentRoom.type == 1 &&
currentRoom.members!.length == 1 &&
@ -59,7 +63,8 @@ class ChatDetailScreen extends HookConsumerWidget {
.first
.account
.profile
.backgroundId !=
.background
?.id !=
null)
? CloudImageWidget(
fileId:
@ -68,11 +73,12 @@ class ChatDetailScreen extends HookConsumerWidget {
.first
.account
.profile
.backgroundId!,
.background!
.id,
)
: currentRoom.backgroundId != null
: currentRoom.background?.id != null
? CloudImageWidget(
fileId: currentRoom.backgroundId!,
fileId: currentRoom.background!.id,
fit: BoxFit.cover,
)
: Container(
@ -285,12 +291,51 @@ class ChatMemberNotifier extends StateNotifier<ChatRoomMemberState> {
}
}
@riverpod
class ChatMemberListNotifier extends _$ChatMemberListNotifier
with CursorPagingNotifierMixin<SnChatMember> {
@override
Future<CursorPagingData<SnChatMember>> build(String roomId) {
return fetch();
}
@override
Future<CursorPagingData<SnChatMember>> fetch({String? cursor}) async {
final offset = cursor == null ? 0 : int.parse(cursor);
final take = 20;
final apiClient = ref.watch(apiClientProvider);
final response = await apiClient.get(
'/chat/$roomId/members',
queryParameters: {'offset': offset, 'take': take},
);
final total = int.parse(response.headers.value('X-Total') ?? '0');
final List<dynamic> data = response.data;
final members = data.map((e) => SnChatMember.fromJson(e)).toList();
// Calculate next cursor based on total count
final nextOffset = offset + members.length;
final String? nextCursor =
nextOffset < total ? nextOffset.toString() : null;
return CursorPagingData(
items: members,
nextCursor: nextCursor,
hasMore: members.length < total,
);
}
}
class _ChatMemberListSheet extends HookConsumerWidget {
final String roomId;
const _ChatMemberListSheet({required this.roomId});
@override
Widget build(BuildContext context, WidgetRef ref) {
final memberListProvider = chatMemberListNotifierProvider(roomId);
// For backward compatibility and to show total count in the header
final memberState = ref.watch(chatMemberStateProvider(roomId));
final memberNotifier = ref.read(chatMemberStateProvider(roomId).notifier);
@ -316,8 +361,10 @@ class _ChatMemberListSheet extends HookConsumerWidget {
'/chat/invites/$roomId',
data: {'related_user_id': result.id, 'role': 0},
);
// Refresh both providers
memberNotifier.reset();
await memberNotifier.loadMore();
ref.invalidate(memberListProvider);
} catch (err) {
showErrorAlert(err);
}
@ -349,8 +396,10 @@ class _ChatMemberListSheet extends HookConsumerWidget {
IconButton(
icon: const Icon(Symbols.refresh),
onPressed: () {
// Refresh both providers
memberNotifier.reset();
memberNotifier.loadMore();
ref.invalidate(memberListProvider);
},
),
IconButton(
@ -363,108 +412,103 @@ class _ChatMemberListSheet extends HookConsumerWidget {
),
const Divider(height: 1),
Expanded(
child:
memberState.error != null
? Center(child: Text(memberState.error!))
: ListView.builder(
itemCount: memberState.members.length + 1,
itemBuilder: (context, index) {
if (index == memberState.members.length) {
if (memberState.isLoading) {
return const Center(
child: Padding(
padding: EdgeInsets.all(16.0),
child: CircularProgressIndicator(),
),
);
}
if (memberState.members.length < memberState.total) {
memberNotifier.loadMore(
offset: memberState.members.length,
);
}
return const SizedBox.shrink();
}
child: PagingHelperView(
provider: memberListProvider,
futureRefreshable: memberListProvider.future,
notifierRefreshable: memberListProvider.notifier,
contentBuilder: (data, widgetCount, endItemView) {
return ListView.builder(
itemCount: widgetCount,
itemBuilder: (context, index) {
if (index == data.items.length) {
return endItemView;
}
final member = memberState.members[index];
return ListTile(
contentPadding: EdgeInsets.only(left: 16, right: 12),
leading: ProfilePictureWidget(
fileId: member.account.profile.pictureId,
),
title: Row(
spacing: 6,
children: [
Flexible(child: Text(member.account.nick)),
if (member.joinedAt == null)
const Icon(Symbols.pending_actions, size: 20),
],
),
subtitle: Row(
children: [
Text(
member.role >= 100
? 'permissionOwner'
: member.role >= 50
? 'permissionModerator'
: 'permissionMember',
).tr(),
Text('·').bold().padding(horizontal: 6),
Expanded(child: Text("@${member.account.name}")),
],
),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
if ((roomIdentity.value?.role ?? 0) >= 50)
IconButton(
icon: const Icon(Symbols.edit),
onPressed: () {
showModalBottomSheet(
isScrollControlled: true,
context: context,
builder:
(context) => _ChatMemberRoleSheet(
roomId: roomId,
member: member,
),
).then((value) {
if (value != null) {
memberNotifier.reset();
memberNotifier.loadMore();
}
});
},
),
if ((roomIdentity.value?.role ?? 0) >= 50)
IconButton(
icon: const Icon(Symbols.delete),
onPressed: () {
showConfirmAlert(
'removeChatMemberHint'.tr(),
'removeChatMember'.tr(),
).then((confirm) async {
if (confirm != true) return;
try {
final apiClient = ref.watch(
apiClientProvider,
);
await apiClient.delete(
'/chat/$roomId/members/${member.accountId}',
);
memberNotifier.reset();
memberNotifier.loadMore();
} catch (err) {
showErrorAlert(err);
}
});
},
),
],
),
);
},
),
final member = data.items[index];
return ListTile(
contentPadding: EdgeInsets.only(left: 16, right: 12),
leading: ProfilePictureWidget(
fileId: member.account.profile.picture?.id,
),
title: Row(
spacing: 6,
children: [
Flexible(child: Text(member.account.nick)),
if (member.joinedAt == null)
const Icon(Symbols.pending_actions, size: 20),
],
),
subtitle: Row(
children: [
Text(
member.role >= 100
? 'permissionOwner'
: member.role >= 50
? 'permissionModerator'
: 'permissionMember',
).tr(),
Text('·').bold().padding(horizontal: 6),
Expanded(child: Text("@${member.account.name}")),
],
),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
if ((roomIdentity.value?.role ?? 0) >= 50)
IconButton(
icon: const Icon(Symbols.edit),
onPressed: () {
showModalBottomSheet(
isScrollControlled: true,
context: context,
builder:
(context) => _ChatMemberRoleSheet(
roomId: roomId,
member: member,
),
).then((value) {
if (value != null) {
// Refresh both providers
memberNotifier.reset();
memberNotifier.loadMore();
ref.invalidate(memberListProvider);
}
});
},
),
if ((roomIdentity.value?.role ?? 0) >= 50)
IconButton(
icon: const Icon(Symbols.delete),
onPressed: () {
showConfirmAlert(
'removeChatMemberHint'.tr(),
'removeChatMember'.tr(),
).then((confirm) async {
if (confirm != true) return;
try {
final apiClient = ref.watch(
apiClientProvider,
);
await apiClient.delete(
'/chat/$roomId/members/${member.accountId}',
);
// Refresh both providers
memberNotifier.reset();
memberNotifier.loadMore();
ref.invalidate(memberListProvider);
} catch (err) {
showErrorAlert(err);
}
});
},
),
],
),
);
},
);
},
),
),
],
),

View 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

View File

@ -101,7 +101,7 @@ class CreatorHubScreen extends HookConsumerWidget {
minTileHeight: 48,
leading: ProfilePictureWidget(
radius: 16,
fileId: item.pictureId,
fileId: item.picture?.id,
),
title: Text(item.nick),
subtitle: Text('@${item.name}'),
@ -115,7 +115,7 @@ class CreatorHubScreen extends HookConsumerWidget {
)
.toList(),
loading: () => [],
error: (_, __) => [],
error: (_, _) => [],
);
final publisherStats = ref.watch(
@ -150,7 +150,7 @@ class CreatorHubScreen extends HookConsumerWidget {
...publishersMenu.map(
(e) => ProfilePictureWidget(
radius: 16,
fileId: e.value?.pictureId,
fileId: e.value?.picture?.id,
).center().padding(right: 8),
),
];
@ -204,7 +204,7 @@ class CreatorHubScreen extends HookConsumerWidget {
...(publishers.value?.map(
(publisher) => ListTile(
leading: ProfilePictureWidget(
fileId: publisher.pictureId,
fileId: publisher.picture?.id,
),
title: Text(publisher.nick),
subtitle: Text('@${publisher.name}'),
@ -293,7 +293,7 @@ class CreatorHubScreen extends HookConsumerWidget {
),
),
loading: () => const Center(child: CircularProgressIndicator()),
error: (_, __) => const SizedBox.shrink(),
error: (_, _) => const SizedBox.shrink(),
),
);
}

View File

@ -7,6 +7,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:image_picker/image_picker.dart';
import 'package:island/models/file.dart';
import 'package:island/models/post.dart';
import 'package:island/models/realm.dart';
import 'package:island/pods/config.dart';
@ -95,19 +96,15 @@ class EditPublisherScreen extends HookConsumerWidget {
submitting.value = true;
try {
final baseUrl = ref.watch(serverUrlProvider);
final atk = await getFreshAtk(
ref.watch(tokenPairProvider),
baseUrl,
onRefreshed: (atk, rtk) {
setTokenPair(ref.watch(sharedPreferencesProvider), atk, rtk);
ref.invalidate(tokenPairProvider);
},
);
if (atk == null) throw ArgumentError('Access token is null');
final token = await getToken(ref.watch(tokenProvider));
if (token == null) throw ArgumentError('Token is null');
final cloudFile =
await putMediaToCloud(
fileData: result,
atk: atk,
fileData: UniversalFile(
data: result,
type: UniversalFileType.image,
),
atk: token,
baseUrl: baseUrl,
filename: result.name,
mimetype: result.mimeType ?? 'image/jpeg',
@ -145,8 +142,8 @@ class EditPublisherScreen extends HookConsumerWidget {
useEffect(() {
if (publisher.value != null) {
picture.value = publisher.value!.pictureId;
background.value = publisher.value!.backgroundId;
picture.value = publisher.value!.picture?.id;
background.value = publisher.value!.background?.id;
nameController.text = publisher.value!.name;
nickController.text = publisher.value!.nick;
bioController.text = publisher.value!.bio;
@ -200,7 +197,7 @@ class EditPublisherScreen extends HookConsumerWidget {
realms: joinedRealms.when(
data: (realms) => realms,
loading: () => [],
error: (_, __) => [],
error: (_, _) => [],
),
onChanged: (SnRealm? value) {
currentRealm.value = value;
@ -286,14 +283,14 @@ class EditPublisherScreen extends HookConsumerWidget {
nameController.text = user.value!.name;
nickController.text = user.value!.nick;
bioController.text = user.value!.profile.bio;
picture.value = user.value!.profile.pictureId;
background.value = user.value!.profile.backgroundId;
picture.value = user.value!.profile.picture?.id;
background.value = user.value!.profile.background?.id;
} else {
nameController.text = currentRealm.value!.slug;
nickController.text = currentRealm.value!.name;
bioController.text = currentRealm.value!.description;
picture.value = currentRealm.value!.pictureId;
background.value = currentRealm.value!.backgroundId;
picture.value = currentRealm.value!.picture?.id;
background.value = currentRealm.value!.background?.id;
}
},
label:

View File

@ -298,7 +298,7 @@ class _StickerPackActionMenu extends HookConsumerWidget {
if (confirm) {
final client = ref.watch(apiClientProvider);
client.delete('/stickers/$packId');
ref.invalidate(stickerPacksProvider);
ref.invalidate(stickerPacksNotifierProvider);
if (context.mounted) context.router.maybePop(true);
}
});

View File

@ -13,7 +13,7 @@ import 'package:island/widgets/app_scaffold.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:styled_widget/styled_widget.dart';
import 'package:very_good_infinite_list/very_good_infinite_list.dart';
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
part 'stickers.g.dart';
@ -24,9 +24,6 @@ class StickersScreen extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final stickersState = ref.watch(stickerPacksProvider);
final stickersNotifier = ref.watch(stickerPacksProvider.notifier);
return AppScaffold(
appBar: AppBar(
title: const Text('stickers').tr(),
@ -37,7 +34,7 @@ class StickersScreen extends HookConsumerWidget {
value,
) {
if (value != null) {
stickersNotifier.refresh();
ref.invalidate(stickerPacksNotifierProvider(pubName));
}
});
},
@ -46,104 +43,90 @@ class StickersScreen extends HookConsumerWidget {
const Gap(8),
],
),
body: stickersState.when(
data:
(stickers) => RefreshIndicator(
onRefresh: stickersNotifier.refresh,
child: InfiniteList(
padding: EdgeInsets.zero,
itemCount: stickers.length,
hasReachedMax: stickersNotifier.isReachedMax,
isLoading: stickersNotifier.isLoading,
onFetchData: stickersNotifier.fetchMore,
itemBuilder: (context, index) {
return ListTile(
title: Text(stickers[index].name),
subtitle: Text(stickers[index].description),
trailing: const Icon(Symbols.chevron_right),
onTap: () {
context.router.push(
StickerPackDetailRoute(
pubName: pubName,
id: stickers[index].id,
),
);
},
);
},
),
),
loading: () => const CircularProgressIndicator(),
error: (error, stack) => Text('Error: $error'),
),
body: SliverStickerPacksList(pubName: pubName),
);
}
}
final stickerPacksProvider = StateNotifierProvider<
StickerPacksNotifier,
AsyncValue<List<SnStickerPack>>
>((ref) {
return StickerPacksNotifier(ref.watch(apiClientProvider));
});
class SliverStickerPacksList extends HookConsumerWidget {
final String pubName;
const SliverStickerPacksList({super.key, required this.pubName});
class StickerPacksNotifier
extends StateNotifier<AsyncValue<List<SnStickerPack>>> {
final Dio _apiClient;
StickerPacksNotifier(this._apiClient) : super(const AsyncValue.loading()) {
fetchStickers();
@override
Widget build(BuildContext context, WidgetRef ref) {
return PagingHelperView(
provider: stickerPacksNotifierProvider(pubName),
futureRefreshable: stickerPacksNotifierProvider(pubName).future,
notifierRefreshable: stickerPacksNotifierProvider(pubName).notifier,
contentBuilder:
(data, widgetCount, endItemView) => ListView.builder(
padding: EdgeInsets.zero,
itemCount: widgetCount,
itemBuilder: (context, index) {
if (index == widgetCount - 1) {
return endItemView;
}
final sticker = data.items[index];
return ListTile(
title: Text(sticker.name),
subtitle: Text(sticker.description),
trailing: const Icon(Symbols.chevron_right),
onTap: () {
context.router.push(
StickerPackDetailRoute(pubName: pubName, id: sticker.id),
);
},
);
},
),
);
}
}
@riverpod
class StickerPacksNotifier extends _$StickerPacksNotifier
with CursorPagingNotifierMixin<SnStickerPack> {
static const int _pageSize = 20;
@override
Future<CursorPagingData<SnStickerPack>> build(String pubName) {
return fetch(cursor: null);
}
int offset = 0;
int take = 20;
int total = 0;
bool isLoading = false;
bool get isReachedMax =>
state.valueOrNull != null && state.valueOrNull!.length >= total;
Future<void> fetchStickers() async {
if (isLoading) return;
isLoading = true;
@override
Future<CursorPagingData<SnStickerPack>> fetch({
required String? cursor,
}) async {
final client = ref.read(apiClientProvider);
final offset = cursor == null ? 0 : int.parse(cursor);
try {
final response = await _apiClient.get(
'/stickers?offset=$offset&take=$take',
final response = await client.get(
'/stickers',
queryParameters: {
'offset': offset,
'take': _pageSize,
'pubName': pubName,
},
);
if (response.statusCode == 200) {
total = int.parse(response.headers.value('X-Total') ?? '0');
final newStickers =
response.data
.map((e) => SnStickerPack.fromJson(e))
.cast<SnStickerPack>()
.toList();
state = AsyncValue.data(
state.valueOrNull != null
? [...state.value!, ...newStickers]
: newStickers,
);
offset += take;
} else {
state = AsyncValue.error('Failed to load stickers', StackTrace.current);
}
} catch (err, stackTrace) {
state = AsyncValue.error(err, stackTrace);
} finally {
isLoading = false;
final total = int.parse(response.headers.value('X-Total') ?? '0');
final List<dynamic> data = response.data;
final stickers = data.map((e) => SnStickerPack.fromJson(e)).toList();
final hasMore = offset + stickers.length < total;
final nextCursor = hasMore ? (offset + stickers.length).toString() : null;
return CursorPagingData(
items: stickers,
hasMore: hasMore,
nextCursor: nextCursor,
);
} catch (err) {
rethrow;
}
}
Future<void> fetchMore() async {
if (state.valueOrNull == null || state.valueOrNull!.length >= total) return;
await fetchStickers();
}
Future<void> refresh() async {
offset = 0;
state = const AsyncValue.loading();
await fetchStickers();
}
}
@riverpod

View File

@ -147,5 +147,154 @@ class _StickerPackProviderElement
String? get packId => (origin as StickerPackProvider).packId;
}
String _$stickerPacksNotifierHash() =>
r'dc0cc4ec27fdd6d5da28f982ff10c852f8107a18';
abstract class _$StickerPacksNotifier
extends BuildlessAutoDisposeAsyncNotifier<CursorPagingData<SnStickerPack>> {
late final String pubName;
FutureOr<CursorPagingData<SnStickerPack>> build(String pubName);
}
/// See also [StickerPacksNotifier].
@ProviderFor(StickerPacksNotifier)
const stickerPacksNotifierProvider = StickerPacksNotifierFamily();
/// See also [StickerPacksNotifier].
class StickerPacksNotifierFamily
extends Family<AsyncValue<CursorPagingData<SnStickerPack>>> {
/// See also [StickerPacksNotifier].
const StickerPacksNotifierFamily();
/// See also [StickerPacksNotifier].
StickerPacksNotifierProvider call(String pubName) {
return StickerPacksNotifierProvider(pubName);
}
@override
StickerPacksNotifierProvider getProviderOverride(
covariant StickerPacksNotifierProvider 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'stickerPacksNotifierProvider';
}
/// See also [StickerPacksNotifier].
class StickerPacksNotifierProvider
extends
AutoDisposeAsyncNotifierProviderImpl<
StickerPacksNotifier,
CursorPagingData<SnStickerPack>
> {
/// See also [StickerPacksNotifier].
StickerPacksNotifierProvider(String pubName)
: this._internal(
() => StickerPacksNotifier()..pubName = pubName,
from: stickerPacksNotifierProvider,
name: r'stickerPacksNotifierProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$stickerPacksNotifierHash,
dependencies: StickerPacksNotifierFamily._dependencies,
allTransitiveDependencies:
StickerPacksNotifierFamily._allTransitiveDependencies,
pubName: pubName,
);
StickerPacksNotifierProvider._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
FutureOr<CursorPagingData<SnStickerPack>> runNotifierBuild(
covariant StickerPacksNotifier notifier,
) {
return notifier.build(pubName);
}
@override
Override overrideWith(StickerPacksNotifier Function() create) {
return ProviderOverride(
origin: this,
override: StickerPacksNotifierProvider._internal(
() => create()..pubName = pubName,
from: from,
name: null,
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
pubName: pubName,
),
);
}
@override
AutoDisposeAsyncNotifierProviderElement<
StickerPacksNotifier,
CursorPagingData<SnStickerPack>
>
createElement() {
return _StickerPacksNotifierProviderElement(this);
}
@override
bool operator ==(Object other) {
return other is StickerPacksNotifierProvider && 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 StickerPacksNotifierRef
on AutoDisposeAsyncNotifierProviderRef<CursorPagingData<SnStickerPack>> {
/// The parameter `pubName` of this provider.
String get pubName;
}
class _StickerPacksNotifierProviderElement
extends
AutoDisposeAsyncNotifierProviderElement<
StickerPacksNotifier,
CursorPagingData<SnStickerPack>
>
with StickerPacksNotifierRef {
_StickerPacksNotifierProviderElement(super.provider);
@override
String get pubName => (origin as StickerPacksNotifierProvider).pubName;
}
// 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

View File

@ -17,6 +17,7 @@ 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:island/pods/network.dart';
import 'package:styled_widget/styled_widget.dart';
part 'explore.g.dart';
@ -131,10 +132,16 @@ class _ActivityListView extends HookConsumerWidget {
switch (item.type) {
case 'posts.new':
case 'posts.new.replies':
final isReply = item.type == 'posts.new.replies';
itemWidget = PostItem(
backgroundColor:
isWideScreen(context) ? Colors.transparent : null,
item: SnPost.fromJson(item.data),
padding:
isReply
? EdgeInsets.only(left: 16, right: 16, bottom: 16)
: null,
onRefresh: (_) {
activitiesNotifier.forceRefresh();
},
@ -145,12 +152,21 @@ class _ActivityListView extends HookConsumerWidget {
);
},
);
break;
case 'accounts.check-in':
itemWidget = CheckInActivityWidget(item: item);
break;
case 'accounts.status':
itemWidget = StatusActivityWidget(item: item);
if (isReply) {
itemWidget = Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Row(
children: [
const Icon(Symbols.reply),
const Gap(8),
Text('Replying your post'),
],
).padding(horizontal: 20, vertical: 8),
itemWidget,
],
);
}
break;
default:
itemWidget = const Placeholder();
@ -174,12 +190,14 @@ class ActivityListNotifier extends _$ActivityListNotifier
@override
Future<CursorPagingData<SnActivity>> fetch({required String? cursor}) async {
final client = ref.read(apiClientProvider);
final offset = cursor == null ? 0 : int.parse(cursor);
final take = 20;
final response = await client.get(
'/activities',
queryParameters: {'offset': offset, 'take': take},
queryParameters: {
if (cursor != null) 'reading_cursor': cursor,
'take': take,
},
);
final List<SnActivity> items =
@ -187,9 +205,9 @@ class ActivityListNotifier extends _$ActivityListNotifier
.map((e) => SnActivity.fromJson(e as Map<String, dynamic>))
.toList();
final total = int.tryParse(response.headers['x-total']?.first ?? '') ?? 0;
final hasMore = offset + items.length < total;
final nextCursor = hasMore ? (offset + items.length).toString() : null;
final hasMore = (items.firstOrNull?.type ?? 'empty') != 'empty';
final nextCursor =
items.map((x) => x.createdAt).lastOrNull?.toIso8601String().toString();
return CursorPagingData(
items: items,

View File

@ -7,7 +7,7 @@ part of 'explore.dart';
// **************************************************************************
String _$activityListNotifierHash() =>
r'8a67d302e828408c7c4cf724d84c2c5958f2dc7e';
r'1baf0bb961bc02bfc8a5b5f515981072c6ce1750';
/// See also [ActivityListNotifier].
@ProviderFor(ActivityListNotifier)

View File

@ -7,7 +7,7 @@ part of 'notification.dart';
// **************************************************************************
String _$notificationUnreadCountNotifierHash() =>
r'074143cf208a3afe1495be405198532a23ef77c8';
r'372a2cc259d7d838cd4f33a9129f7396ef31dbb9';
/// See also [NotificationUnreadCountNotifier].
@ProviderFor(NotificationUnreadCountNotifier)

View File

@ -1,5 +1,3 @@
import 'dart:io';
import 'package:auto_route/auto_route.dart';
import 'package:collection/collection.dart';
import 'package:dio/dio.dart';
@ -21,10 +19,11 @@ import 'package:island/services/responsive.dart';
import 'package:island/widgets/alert.dart';
import 'package:island/widgets/app_scaffold.dart';
import 'package:island/widgets/content/cloud_files.dart';
import 'package:island/widgets/content/attachment_preview.dart';
import 'package:island/widgets/post/publishers_modal.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:pasteboard/pasteboard.dart';
import 'package:styled_widget/styled_widget.dart';
import 'package:super_clipboard/super_clipboard.dart';
@RoutePage()
class PostEditScreen extends HookConsumerWidget {
@ -53,7 +52,14 @@ class PostEditScreen extends HookConsumerWidget {
@RoutePage()
class PostComposeScreen extends HookConsumerWidget {
final SnPost? originalPost;
const PostComposeScreen({super.key, this.originalPost});
final SnPost? repliedPost;
final SnPost? forwardedPost;
const PostComposeScreen({
super.key,
this.originalPost,
this.repliedPost,
this.forwardedPost,
});
@override
Widget build(BuildContext context, WidgetRef ref) {
@ -90,9 +96,14 @@ class PostComposeScreen extends HookConsumerWidget {
text: originalPost?.description,
);
final contentController = useTextEditingController(
text: originalPost?.content,
text:
originalPost?.content ??
(forwardedPost != null ? '> ${forwardedPost!.content}\n\n' : null),
);
// Add visibility state with default value from original post or 0 (public)
final visibility = useState<int>(originalPost?.visibility ?? 0);
final submitting = useState(false);
Future<void> pickPhotoMedia() async {
@ -125,21 +136,14 @@ class PostComposeScreen extends HookConsumerWidget {
final attachment = attachments.value[index];
if (attachment is SnCloudFile) return;
final baseUrl = ref.watch(serverUrlProvider);
final atk = await getFreshAtk(
ref.watch(tokenPairProvider),
baseUrl,
onRefreshed: (atk, rtk) {
setTokenPair(ref.watch(sharedPreferencesProvider), atk, rtk);
ref.invalidate(tokenPairProvider);
},
);
if (atk == null) throw ArgumentError('Access token is null');
final token = await getToken(ref.watch(tokenProvider));
if (token == null) throw ArgumentError('Token is null');
try {
attachmentProgress.value = {...attachmentProgress.value, index: 0};
final cloudFile =
await putMediaToCloud(
fileData: attachment.data,
atk: atk,
fileData: attachment,
atk: token,
baseUrl: baseUrl,
filename: attachment.data.name ?? 'Post media',
mimetype:
@ -195,12 +199,18 @@ class PostComposeScreen extends HookConsumerWidget {
await client.request(
originalPost == null ? '/posts' : '/posts/${originalPost!.id}',
data: {
'title': titleController.text,
'description': descriptionController.text,
'content': contentController.text,
'visibility':
visibility.value, // Add visibility field to API request
'attachments':
attachments.value
.where((e) => e.isOnCloud)
.map((e) => e.data.id)
.toList(),
if (repliedPost != null) 'replied_post_id': repliedPost!.id,
if (forwardedPost != null) 'forwarded_post_id': forwardedPost!.id,
},
options: Options(
headers: {'X-Pub': currentPublisher.value?.name},
@ -217,44 +227,106 @@ class PostComposeScreen extends HookConsumerWidget {
}
}
Future<void> _handlePaste() async {
final clipboard = SystemClipboard.instance;
Future<void> handlePaste() async {
final clipboard = await Pasteboard.image;
if (clipboard == null) return;
final reader = await clipboard.read();
if (reader.canProvide(Formats.png)) {
reader.getFile(Formats.png, (file) async {
final stream = file.getStream();
final bytes = await stream.toList();
final imageBytes = bytes.expand((e) => e).toList();
// Create a temporary file to store the image
final tempDir = Directory.systemTemp;
final tempFile = File(
'${tempDir.path}/pasted_image_${DateTime.now().millisecondsSinceEpoch}.png',
);
await tempFile.writeAsBytes(imageBytes);
// Add the file to attachments
attachments.value = [
...attachments.value,
UniversalFile(
data: XFile(tempFile.path),
type: UniversalFileType.image,
),
];
});
}
attachments.value = [
...attachments.value,
UniversalFile(
data: XFile.fromData(clipboard, mimeType: "image/jpeg"),
type: UniversalFileType.image,
),
];
}
void _handleKeyPress(RawKeyEvent event) {
void handleKeyPress(RawKeyEvent event) {
if (event is! RawKeyDownEvent) return;
final isPaste = event.logicalKey == LogicalKeyboardKey.keyV;
final isModifierPressed = event.isMetaPressed || event.isControlPressed;
if (isPaste && isModifierPressed) {
_handlePaste();
handlePaste();
}
}
void showVisibilityModal() {
showDialog(
context: context,
builder:
(context) => AlertDialog(
title: Text('postVisibility'.tr()),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
leading: Icon(Symbols.public),
title: Text('postVisibilityPublic'.tr()),
onTap: () {
visibility.value = 0;
Navigator.pop(context);
},
selected: visibility.value == 0,
),
ListTile(
leading: Icon(Symbols.group),
title: Text('postVisibilityFriends'.tr()),
onTap: () {
visibility.value = 1;
Navigator.pop(context);
},
selected: visibility.value == 1,
),
ListTile(
leading: Icon(Symbols.link_off),
title: Text('postVisibilityUnlisted'.tr()),
onTap: () {
visibility.value = 2;
Navigator.pop(context);
},
selected: visibility.value == 2,
),
ListTile(
leading: Icon(Symbols.lock),
title: Text('postVisibilityPrivate'.tr()),
onTap: () {
visibility.value = 3;
Navigator.pop(context);
},
selected: visibility.value == 3,
),
],
),
),
);
}
// Helper method to get the appropriate icon for each visibility status
IconData getVisibilityIcon(int visibilityValue) {
switch (visibilityValue) {
case 1: // Friends
return Symbols.group;
case 2: // Unlisted
return Symbols.link_off;
case 3: // Private
return Symbols.lock;
default: // Public (0) or unknown
return Symbols.public;
}
}
// Helper method to get the translation key for each visibility status
String getVisibilityText(int visibilityValue) {
switch (visibilityValue) {
case 1: // Friends
return 'postVisibilityFriends';
case 2: // Unlisted
return 'postVisibilityUnlisted';
case 3: // Private
return 'postVisibilityPrivate';
default: // Public (0) or unknown
return 'postVisibilityPublic';
}
}
@ -320,6 +392,48 @@ class PostComposeScreen extends HookConsumerWidget {
body: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (repliedPost != null)
Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
color: Theme.of(
context,
).colorScheme.surfaceVariant.withOpacity(0.5),
child: Row(
children: [
const Icon(Symbols.reply, size: 16),
const Gap(8),
Expanded(
child: Text(
'${'reply'.tr()}: ${repliedPost!.publisher.nick}',
style: Theme.of(context).textTheme.bodySmall,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
],
),
),
if (forwardedPost != null)
Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
color: Theme.of(
context,
).colorScheme.surfaceVariant.withOpacity(0.5),
child: Row(
children: [
const Icon(Symbols.forward, size: 16),
const Gap(8),
Expanded(
child: Text(
'${'forward'.tr()}: ${forwardedPost!.publisher.nick}',
style: Theme.of(context).textTheme.bodySmall,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
],
),
),
Expanded(
child: Row(
spacing: 12,
@ -327,7 +441,7 @@ class PostComposeScreen extends HookConsumerWidget {
children: [
GestureDetector(
child: ProfilePictureWidget(
fileId: currentPublisher.value?.pictureId,
fileId: currentPublisher.value?.picture?.id,
radius: 20,
fallbackIcon:
currentPublisher.value == null
@ -348,7 +462,52 @@ class PostComposeScreen extends HookConsumerWidget {
child: SingleChildScrollView(
padding: EdgeInsets.symmetric(vertical: 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
OutlinedButton(
onPressed: () {
showVisibilityModal();
},
style: OutlinedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
side: BorderSide(
color: Theme.of(
context,
).colorScheme.primary.withOpacity(0.5),
),
padding: EdgeInsets.symmetric(horizontal: 16),
visualDensity: const VisualDensity(
vertical: -2,
horizontal: -4,
),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
getVisibilityIcon(visibility.value),
size: 16,
color:
Theme.of(context).colorScheme.primary,
),
const SizedBox(width: 6),
Text(
getVisibilityText(visibility.value).tr(),
style: TextStyle(
fontSize: 14,
color:
Theme.of(context).colorScheme.primary,
),
),
],
),
),
],
).padding(bottom: 6),
TextField(
controller: titleController,
decoration: InputDecoration.collapsed(
@ -372,7 +531,7 @@ class PostComposeScreen extends HookConsumerWidget {
const Gap(8),
RawKeyboardListener(
focusNode: FocusNode(),
onKey: _handleKeyPress,
onKey: handleKeyPress,
child: TextField(
controller: contentController,
style: TextStyle(fontSize: 14),
@ -394,49 +553,32 @@ class PostComposeScreen extends HookConsumerWidget {
final isWide = isWideScreen(context);
return isWide
? Wrap(
spacing: 8,
runSpacing: 8,
children: [
for (var idx = 0; idx < attachments.value.length; idx++)
SizedBox(
width: constraints.maxWidth / 2 - 4,
child: AttachmentPreview(
item: attachments.value[idx],
progress: attachmentProgress.value[idx],
onRequestUpload: () => uploadAttachment(idx),
onDelete: () => deleteAttachment(idx),
onMove: (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;
},
),
),
],
)
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 8,
children: [
for (var idx = 0; idx < attachments.value.length; idx++)
AttachmentPreview(
spacing: 8,
runSpacing: 8,
children: [
for (
var idx = 0;
idx < attachments.value.length;
idx++
)
SizedBox(
width: constraints.maxWidth / 2 - 4,
child: AttachmentPreview(
item: attachments.value[idx],
progress: attachmentProgress.value[idx],
onRequestUpload: () => uploadAttachment(idx),
progress:
attachmentProgress.value[idx],
onRequestUpload:
() => uploadAttachment(idx),
onDelete: () => deleteAttachment(idx),
onMove: (delta) {
if (idx + delta < 0 ||
idx + delta >= attachments.value.length) {
idx + delta >=
attachments.value.length) {
return;
}
final clone = List.of(attachments.value);
final clone = List.of(
attachments.value,
);
clone.insert(
idx + delta,
clone.removeAt(idx),
@ -444,8 +586,42 @@ class PostComposeScreen extends HookConsumerWidget {
attachments.value = clone;
},
),
],
);
),
],
)
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 8,
children: [
for (
var idx = 0;
idx < attachments.value.length;
idx++
)
AttachmentPreview(
item: attachments.value[idx],
progress: attachmentProgress.value[idx],
onRequestUpload:
() => uploadAttachment(idx),
onDelete: () => deleteAttachment(idx),
onMove: (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;
},
),
],
);
},
),
],
@ -481,204 +657,3 @@ class PostComposeScreen extends HookConsumerWidget {
);
}
}
class AttachmentPreview extends StatelessWidget {
final UniversalFile item;
final double? progress;
final Function(int)? onMove;
final Function? onDelete;
final Function? onRequestUpload;
const AttachmentPreview({
super.key,
required this.item,
this.progress,
this.onRequestUpload,
this.onMove,
this.onDelete,
});
@override
Widget build(BuildContext context) {
return AspectRatio(
aspectRatio:
(item.isOnCloud ? (item.data.fileMeta?['ratio'] ?? 1) : 1).toDouble(),
child: ClipRRect(
borderRadius: BorderRadius.circular(8),
child: Stack(
fit: StackFit.expand,
children: [
Container(
color: Theme.of(context).colorScheme.surfaceContainerHigh,
child: Builder(
builder: (context) {
if (item.isOnCloud) {
return CloudFileWidget(item: item.data);
} else if (item.data is XFile) {
if (item.type == UniversalFileType.image) {
return Image.file(File(item.data.path));
} else {
return Center(
child: Text(
'Preview is not supported for ${item.type}',
textAlign: TextAlign.center,
),
);
}
} else if (item is List<int> || item is Uint8List) {
if (item.type == UniversalFileType.image) {
return Image.memory(item.data);
} else {
return Center(
child: Text(
'Preview is not supported for ${item.type}',
textAlign: TextAlign.center,
),
);
}
}
return Placeholder();
},
),
),
if (progress != null)
Positioned.fill(
child: Container(
color: Colors.black.withOpacity(0.3),
padding: EdgeInsets.symmetric(horizontal: 40, vertical: 16),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
if (progress != null)
Text(
'${progress!.toStringAsFixed(2)}%',
style: TextStyle(color: Colors.white),
)
else
Text(
'uploading'.tr(),
style: TextStyle(color: Colors.white),
),
Gap(6),
Center(child: LinearProgressIndicator(value: progress)),
],
),
),
),
Positioned(
left: 8,
top: 8,
child: ClipRRect(
borderRadius: BorderRadius.circular(8),
child: Container(
color: Colors.black.withOpacity(0.5),
child: Material(
color: Colors.transparent,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
if (onDelete != null)
InkWell(
borderRadius: BorderRadius.circular(8),
child: const Icon(
Symbols.delete,
size: 14,
color: Colors.white,
).padding(horizontal: 8, vertical: 6),
onTap: () {
onDelete?.call();
},
),
if (onDelete != null && onMove != null)
SizedBox(
height: 26,
child: const VerticalDivider(
width: 0.3,
color: Colors.white,
thickness: 0.3,
),
).padding(horizontal: 2),
if (onMove != null)
InkWell(
borderRadius: BorderRadius.circular(8),
child: const Icon(
Symbols.keyboard_arrow_up,
size: 14,
color: Colors.white,
).padding(horizontal: 8, vertical: 6),
onTap: () {
onMove?.call(-1);
},
),
if (onMove != null)
InkWell(
borderRadius: BorderRadius.circular(8),
child: const Icon(
Symbols.keyboard_arrow_down,
size: 14,
color: Colors.white,
).padding(horizontal: 8, vertical: 6),
onTap: () {
onMove?.call(1);
},
),
],
),
),
),
),
),
if (onRequestUpload != null)
Positioned(
top: 8,
right: 8,
child: InkWell(
borderRadius: BorderRadius.circular(8),
onTap: () => onRequestUpload?.call(),
child: ClipRRect(
borderRadius: BorderRadius.circular(8),
child: Container(
color: Colors.black.withOpacity(0.5),
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
child:
(item.isOnCloud)
? Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Symbols.cloud,
size: 16,
color: Colors.white,
),
const Gap(8),
Text(
'On-cloud',
style: TextStyle(color: Colors.white),
),
],
)
: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Symbols.cloud_off,
size: 16,
color: Colors.white,
),
const Gap(8),
Text(
'On-device',
style: TextStyle(color: Colors.white),
),
],
),
),
),
),
),
],
),
),
);
}
}

View File

@ -1,4 +1,4 @@
import 'package:auto_route/annotations.dart';
import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
@ -108,8 +108,8 @@ class PublisherProfileScreen extends HookConsumerWidget {
leading: PageBackButton(shadows: [iconShadow]),
flexibleSpace: FlexibleSpaceBar(
background:
data.backgroundId != null
? CloudImageWidget(fileId: data.backgroundId!)
data.background?.id != null
? CloudImageWidget(fileId: data.background!.id)
: Container(
color:
Theme.of(context).appBarTheme.backgroundColor,
@ -139,7 +139,7 @@ class PublisherProfileScreen extends HookConsumerWidget {
shadows: [iconShadow],
),
),
error: (_, __) => const SizedBox(),
error: (_, _) => const SizedBox(),
loading:
() => const SizedBox(
width: 48,
@ -163,7 +163,10 @@ class PublisherProfileScreen extends HookConsumerWidget {
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 20,
children: [
ProfilePictureWidget(fileId: data.pictureId!, radius: 32),
ProfilePictureWidget(
fileId: data.picture!.id,
radius: 32,
),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
@ -177,6 +180,26 @@ class PublisherProfileScreen extends HookConsumerWidget {
).fontSize(14).opacity(0.85),
],
),
if (data.type == 0 && data.account != null)
InkWell(
onTap: () {
context.router.pushPath(
'/account/${data.account!.name}',
);
},
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
spacing: 4,
children: [
Text(
'publisherVisitAccountPage'.tr(
args: ['@${data.account!.name}'],
),
).fontSize(14),
Icon(Icons.launch, size: 14),
],
).opacity(0.85),
).padding(bottom: 6),
if (data.type == 0 && data.account != null)
AccountStatusWidget(
uname: data.account!.name,

View File

@ -145,7 +145,7 @@ class _PublisherProviderElement
String get uname => (origin as PublisherProvider).uname;
}
String _$publisherBadgesHash() => r'b26d8804ddc9734c453bdf76af0a9336f166542c';
String _$publisherBadgesHash() => r'a5781deded7e682a781ccd7854418f050438e3f4';
/// See also [publisherBadges].
@ProviderFor(publisherBadges)

View File

@ -15,6 +15,7 @@ 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:riverpod_paging_utils/riverpod_paging_utils.dart';
import 'package:styled_widget/styled_widget.dart';
part 'detail.g.dart';
@ -54,8 +55,8 @@ class RealmDetailScreen extends HookConsumerWidget {
leading: PageBackButton(shadows: [iconShadow]),
flexibleSpace: FlexibleSpaceBar(
background:
realm!.backgroundId != null
? CloudImageWidget(fileId: realm.backgroundId!)
realm!.background?.id != null
? CloudImageWidget(fileId: realm.background!.id)
: Container(
color:
Theme.of(context).appBarTheme.backgroundColor,
@ -118,7 +119,7 @@ class _RealmActionMenu extends HookConsumerWidget {
final isModerator = realmIdentityAsync.when(
data: (identity) => (identity?.role ?? 0) >= 50,
loading: () => false,
error: (_, __) => false,
error: (_, _) => false,
);
return PopupMenuButton(
@ -212,7 +213,7 @@ class _RealmActionMenu extends HookConsumerWidget {
child: Center(child: CircularProgressIndicator()),
),
error:
(_, __) => PopupMenuItem(
(_, _) => PopupMenuItem(
child: Row(
children: [
Icon(
@ -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 =
StateNotifierProvider.family<RealmMemberNotifier, RealmMemberState, String>(
(ref, realmSlug) {
@ -302,13 +339,15 @@ class _RealmMemberListSheet extends HookConsumerWidget {
@override
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 memberNotifier = ref.read(
realmMemberStateProvider(realmSlug).notifier,
);
final realmIdentity = ref.watch(realmIdentityProvider(realmSlug));
useEffect(() {
Future(() {
memberNotifier.loadMore();
@ -329,8 +368,10 @@ class _RealmMemberListSheet extends HookConsumerWidget {
'/realms/invites/$realmSlug',
data: {'related_user_id': result.id, 'role': 0},
);
// Refresh both providers
memberNotifier.reset();
await memberNotifier.loadMore();
ref.invalidate(memberListProvider);
} catch (err) {
showErrorAlert(err);
}
@ -362,8 +403,10 @@ class _RealmMemberListSheet extends HookConsumerWidget {
IconButton(
icon: const Icon(Symbols.refresh),
onPressed: () {
// Refresh both providers
memberNotifier.reset();
memberNotifier.loadMore();
ref.invalidate(memberListProvider);
},
),
IconButton(
@ -376,108 +419,103 @@ class _RealmMemberListSheet extends HookConsumerWidget {
),
const Divider(height: 1),
Expanded(
child:
memberState.error != null
? Center(child: Text(memberState.error!))
: ListView.builder(
itemCount: memberState.members.length + 1,
itemBuilder: (context, index) {
if (index == memberState.members.length) {
if (memberState.isLoading) {
return const Center(
child: Padding(
padding: EdgeInsets.all(16.0),
child: CircularProgressIndicator(),
),
);
}
if (memberState.members.length < memberState.total) {
memberNotifier.loadMore(
offset: memberState.members.length,
);
}
return const SizedBox.shrink();
}
child: PagingHelperView(
provider: memberListProvider,
futureRefreshable: memberListProvider.future,
notifierRefreshable: memberListProvider.notifier,
contentBuilder: (data, widgetCount, endItemView) {
return ListView.builder(
itemCount: widgetCount,
itemBuilder: (context, index) {
if (index == data.items.length) {
return endItemView;
}
final member = memberState.members[index];
return ListTile(
contentPadding: EdgeInsets.only(left: 16, right: 12),
leading: ProfilePictureWidget(
fileId: member.account!.profile.pictureId,
),
title: Row(
spacing: 6,
children: [
Flexible(child: Text(member.account!.nick)),
if (member.joinedAt == null)
const Icon(Symbols.pending_actions, size: 20),
],
),
subtitle: Row(
children: [
Text(
member.role >= 100
? 'permissionOwner'
: member.role >= 50
? 'permissionModerator'
: 'permissionMember',
).tr(),
Text('·').bold().padding(horizontal: 6),
Expanded(child: Text("@${member.account!.name}")),
],
),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
if ((realmIdentity.value?.role ?? 0) >= 50)
IconButton(
icon: const Icon(Symbols.edit),
onPressed: () {
showModalBottomSheet(
isScrollControlled: true,
context: context,
builder:
(context) => _RealmMemberRoleSheet(
realmSlug: realmSlug,
member: member,
),
).then((value) {
if (value != null) {
memberNotifier.reset();
memberNotifier.loadMore();
}
});
},
),
if ((realmIdentity.value?.role ?? 0) >= 50)
IconButton(
icon: const Icon(Symbols.delete),
onPressed: () {
showConfirmAlert(
'removeRealmMemberHint'.tr(),
'removeRealmMember'.tr(),
).then((confirm) async {
if (confirm != true) return;
try {
final apiClient = ref.watch(
apiClientProvider,
);
await apiClient.delete(
'/realms/$realmSlug/members/${member.accountId}',
);
memberNotifier.reset();
memberNotifier.loadMore();
} catch (err) {
showErrorAlert(err);
}
});
},
),
],
),
);
},
),
final member = data.items[index];
return ListTile(
contentPadding: EdgeInsets.only(left: 16, right: 12),
leading: ProfilePictureWidget(
fileId: member.account!.profile.picture?.id,
),
title: Row(
spacing: 6,
children: [
Flexible(child: Text(member.account!.nick)),
if (member.joinedAt == null)
const Icon(Symbols.pending_actions, size: 20),
],
),
subtitle: Row(
children: [
Text(
member.role >= 100
? 'permissionOwner'
: member.role >= 50
? 'permissionModerator'
: 'permissionMember',
).tr(),
Text('·').bold().padding(horizontal: 6),
Expanded(child: Text("@${member.account!.name}")),
],
),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
if ((realmIdentity.value?.role ?? 0) >= 50)
IconButton(
icon: const Icon(Symbols.edit),
onPressed: () {
showModalBottomSheet(
isScrollControlled: true,
context: context,
builder:
(context) => _RealmMemberRoleSheet(
realmSlug: realmSlug,
member: member,
),
).then((value) {
if (value != null) {
// Refresh both providers
memberNotifier.reset();
memberNotifier.loadMore();
ref.invalidate(memberListProvider);
}
});
},
),
if ((realmIdentity.value?.role ?? 0) >= 50)
IconButton(
icon: const Icon(Symbols.delete),
onPressed: () {
showConfirmAlert(
'removeRealmMemberHint'.tr(),
'removeRealmMember'.tr(),
).then((confirm) async {
if (confirm != true) return;
try {
final apiClient = ref.watch(
apiClientProvider,
);
await apiClient.delete(
'/realms/$realmSlug/members/${member.accountId}',
);
// Refresh both providers
memberNotifier.reset();
memberNotifier.loadMore();
ref.invalidate(memberListProvider);
} catch (err) {
showErrorAlert(err);
}
});
},
),
],
),
);
},
);
},
),
),
],
),

View File

@ -148,5 +148,155 @@ class _RealmIdentityProviderElement
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: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package

View File

@ -16,6 +16,7 @@ import 'package:island/services/file.dart';
import 'package:island/widgets/alert.dart';
import 'package:island/widgets/app_scaffold.dart';
import 'package:island/widgets/content/cloud_files.dart';
import 'package:island/widgets/content/sheet.dart';
import 'package:island/widgets/response.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
@ -49,13 +50,13 @@ class RealmListScreen extends HookConsumerWidget {
label: Text(
realmInvites.when(
data: (invites) => invites.length.toString(),
error: (_, __) => '0',
error: (_, _) => '0',
loading: () => '0',
),
),
isLabelVisible: realmInvites.when(
data: (invites) => invites.isNotEmpty,
error: (_, __) => false,
error: (_, _) => false,
loading: () => false,
),
child: const Icon(Symbols.email),
@ -97,7 +98,7 @@ class RealmListScreen extends HookConsumerWidget {
return ListTile(
isThreeLine: true,
leading: ProfilePictureWidget(
fileId: value[item].pictureId,
fileId: value[item].picture?.id,
fallbackIcon: Symbols.group,
),
title: Text(value[item].name),
@ -211,19 +212,15 @@ class EditRealmScreen extends HookConsumerWidget {
submitting.value = true;
try {
final baseUrl = ref.watch(serverUrlProvider);
final atk = await getFreshAtk(
ref.watch(tokenPairProvider),
baseUrl,
onRefreshed: (atk, rtk) {
setTokenPair(ref.watch(sharedPreferencesProvider), atk, rtk);
ref.invalidate(tokenPairProvider);
},
);
if (atk == null) throw ArgumentError('Access token is null');
final token = await getToken(ref.watch(tokenProvider));
if (token == null) throw ArgumentError('Access token is null');
final cloudFile =
await putMediaToCloud(
fileData: result,
atk: atk,
fileData: UniversalFile(
data: result,
type: UniversalFileType.image,
),
atk: token,
baseUrl: baseUrl,
filename: result.name,
mimetype: result.mimeType ?? 'image/jpeg',
@ -401,97 +398,69 @@ class _RealmInviteSheet extends HookConsumerWidget {
}
}
return Container(
constraints: BoxConstraints(
maxHeight: MediaQuery.of(context).size.height * 0.8,
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: EdgeInsets.only(top: 16, left: 20, right: 16, bottom: 12),
child: Row(
children: [
Text(
'invites'.tr(),
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
fontWeight: FontWeight.w600,
letterSpacing: -0.5,
),
),
const Spacer(),
IconButton(
icon: const Icon(Symbols.refresh),
style: IconButton.styleFrom(minimumSize: const Size(36, 36)),
onPressed: () {
ref.invalidate(realmInvitesProvider);
},
),
IconButton(
icon: const Icon(Symbols.close),
onPressed: () => Navigator.pop(context),
style: IconButton.styleFrom(minimumSize: const Size(36, 36)),
),
],
),
),
const Divider(height: 1),
Expanded(
child: invites.when(
data:
(items) =>
items.isEmpty
? Center(
child:
Text(
'invitesEmpty',
textAlign: TextAlign.center,
).tr(),
)
: ListView.builder(
shrinkWrap: true,
itemCount: items.length,
itemBuilder: (context, index) {
final invite = items[index];
return ListTile(
leading: ProfilePictureWidget(
fileId: invite.realm!.pictureId,
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),
),
],
),
);
},
return SheetScaffold(
titleText: 'invites'.tr(),
actions: [
IconButton(
icon: const Icon(Symbols.refresh),
style: IconButton.styleFrom(minimumSize: const Size(36, 36)),
onPressed: () {
ref.invalidate(realmInvitesProvider);
},
),
],
child: invites.when(
data:
(items) =>
items.isEmpty
? Center(
child:
Text(
'invitesEmpty',
textAlign: TextAlign.center,
).tr(),
)
: ListView.builder(
shrinkWrap: true,
itemCount: items.length,
itemBuilder: (context, index) {
final invite = items[index];
return ListTile(
leading: ProfilePictureWidget(
fileId: invite.realm!.picture?.id,
fallbackIcon: Symbols.group,
),
loading: () => const Center(child: CircularProgressIndicator()),
error:
(error, _) => ResponseErrorWidget(
error: error,
onRetry: () => ref.invalidate(realmInvitesProvider),
),
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()),
error:
(error, _) => ResponseErrorWidget(
error: error,
onRetry: () => ref.invalidate(realmInvitesProvider),
),
),
],
),
);
}

View File

@ -7,6 +7,7 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_colorpicker/flutter_colorpicker.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:image_picker/image_picker.dart';
@ -28,7 +29,7 @@ class SettingsScreen extends HookConsumerWidget {
final serverUrl = ref.watch(serverUrlProvider);
final prefs = ref.watch(sharedPreferencesProvider);
final controller = TextEditingController(text: serverUrl);
final settings = ref.watch(appSettingsProvider);
final settings = ref.watch(appSettingsNotifierProvider);
final isDesktop =
!kIsWeb && (Platform.isWindows || Platform.isMacOS || Platform.isLinux);
final isWide = isWideScreen(context);
@ -88,6 +89,110 @@ class SettingsScreen extends HookConsumerWidget {
),
),
// Custom fonts settings
ListTile(
isThreeLine: true,
minLeadingWidth: 48,
title: Text('settingsCustomFonts').tr(),
contentPadding: const EdgeInsets.only(left: 24, right: 17),
leading: const Icon(Symbols.font_download),
subtitle: Padding(
padding: const EdgeInsets.only(top: 6),
child: TextField(
controller: TextEditingController(text: settings.customFonts),
decoration: InputDecoration(
hintText: 'Nunito, Arial, sans-serif',
helperText: 'settingsCustomFontsHelper'.tr(),
suffixIcon: IconButton(
icon: const Icon(Symbols.restart_alt),
onPressed: () {
ref
.read(appSettingsNotifierProvider.notifier)
.setCustomFonts(null);
showSnackBar(context, 'settingsApplied'.tr());
},
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
),
isDense: true,
),
onSubmitted: (value) {
ref
.read(appSettingsNotifierProvider.notifier)
.setCustomFonts(value.isEmpty ? null : value);
showSnackBar(context, 'settingsApplied'.tr());
},
),
),
),
// Color scheme settings
ListTile(
minLeadingWidth: 48,
title: Text('settingsColorScheme').tr(),
contentPadding: const EdgeInsets.only(left: 24, right: 17),
leading: const Icon(Symbols.palette),
trailing: GestureDetector(
onTap: () {
showDialog(
context: context,
builder: (context) {
Color selectedColor =
settings.appColorScheme != null
? Color(settings.appColorScheme!)
: Colors.indigo;
return AlertDialog(
title: Text('settingsColorScheme').tr(),
content: SingleChildScrollView(
child: ColorPicker(
paletteType: PaletteType.rgbWithBlue,
enableAlpha: false,
pickerColor: selectedColor,
onColorChanged: (color) {
selectedColor = color;
},
),
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text('cancel').tr(),
),
TextButton(
onPressed: () {
ref
.read(appSettingsNotifierProvider.notifier)
.setAppColorScheme(selectedColor.value);
Navigator.of(context).pop();
},
child: Text('confirm').tr(),
),
],
);
},
);
},
child: Container(
width: 24,
height: 24,
margin: EdgeInsets.symmetric(horizontal: 2, vertical: 8),
decoration: BoxDecoration(
color:
settings.appColorScheme != null
? Color(settings.appColorScheme!)
: Colors.indigo,
shape: BoxShape.circle,
border: Border.all(
color: Theme.of(context).colorScheme.outline.withOpacity(0.5),
width: 2,
),
),
),
),
),
// Background image settings (only for non-web platforms)
if (!kIsWeb && docBasepath.value != null)
ListTile(
@ -95,18 +200,7 @@ class SettingsScreen extends HookConsumerWidget {
title: Text('settingsBackgroundImage').tr(),
contentPadding: const EdgeInsets.only(left: 24, right: 17),
leading: const Icon(Symbols.image),
trailing: Row(
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),
],
),
trailing: const Icon(Symbols.chevron_right),
onTap: () async {
final imagePicker = ref.read(imagePickerProvider);
final image = await imagePicker.pickImage(
@ -128,13 +222,15 @@ class SettingsScreen extends HookConsumerWidget {
// Clear background image option
if (!kIsWeb && docBasepath.value != null)
FutureBuilder<bool>(
future: File('${docBasepath.value}/app_background_image').exists(),
future:
File('${docBasepath.value}/$kAppBackgroundImagePath').exists(),
builder: (context, snapshot) {
if (!snapshot.hasData || !snapshot.data!) {
return const SizedBox.shrink();
}
return ListTile(
minLeadingWidth: 48,
title: Text('settingsBackgroundImageClear').tr(),
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
leading: const Icon(Symbols.texture),
@ -207,7 +303,9 @@ class SettingsScreen extends HookConsumerWidget {
trailing: Switch(
value: settings.autoTranslate,
onChanged: (value) {
ref.read(appSettingsProvider.notifier).setAutoTranslate(value);
ref
.read(appSettingsNotifierProvider.notifier)
.setAutoTranslate(value);
},
),
),
@ -221,7 +319,9 @@ class SettingsScreen extends HookConsumerWidget {
trailing: Switch(
value: settings.soundEffects,
onChanged: (value) {
ref.read(appSettingsProvider.notifier).setSoundEffects(value);
ref
.read(appSettingsNotifierProvider.notifier)
.setSoundEffects(value);
},
),
),
@ -235,7 +335,9 @@ class SettingsScreen extends HookConsumerWidget {
trailing: Switch(
value: settings.aprilFoolFeatures,
onChanged: (value) {
ref.read(appSettingsProvider.notifier).setAprilFoolFeatures(value);
ref
.read(appSettingsNotifierProvider.notifier)
.setAprilFoolFeatures(value);
},
),
),
@ -253,7 +355,25 @@ class SettingsScreen extends HookConsumerWidget {
trailing: Switch(
value: settings.enterToSend,
onChanged: (value) {
ref.read(appSettingsProvider.notifier).setEnterToSend(value);
ref
.read(appSettingsNotifierProvider.notifier)
.setEnterToSend(value);
},
),
),
// Transparent app bar settings
ListTile(
minLeadingWidth: 48,
title: Text('settingsTransparentAppBar').tr(),
contentPadding: const EdgeInsets.only(left: 24, right: 17),
leading: const Icon(Symbols.blur_on),
trailing: Switch(
value: settings.appBarTransparent,
onChanged: (value) {
ref
.read(appSettingsNotifierProvider.notifier)
.setAppBarTransparent(value);
},
),
),
@ -282,19 +402,19 @@ class SettingsScreen extends HookConsumerWidget {
children: [
_ShortcutRow(
shortcut: 'Ctrl+F',
description: 'Search',
description: 'settingsKeyboardShortcutSearch'.tr(),
),
_ShortcutRow(
shortcut: 'Ctrl+,',
description: 'Settings',
description: 'settingsKeyboardShortcutSettings'.tr(),
),
_ShortcutRow(
shortcut: 'Ctrl+N',
description: 'New Message',
description: 'settingsKeyboardShortcutNewMessage'.tr(),
),
_ShortcutRow(
shortcut: 'Esc',
description: 'Close Dialog',
description: 'settingsKeyboardShortcutCloseDialog'.tr(),
),
// Add more shortcuts as needed
],
@ -303,7 +423,7 @@ class SettingsScreen extends HookConsumerWidget {
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text('Close').tr(),
child: Text('close').tr(),
),
],
),
@ -325,10 +445,10 @@ class SettingsScreen extends HookConsumerWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_SettingsSection(
title: 'Appearance',
title: 'settingsAppearance'.tr(),
children: appearanceSettings,
),
_SettingsSection(title: 'Server', children: serverSettings),
_SettingsSection(title: 'settingsServer'.tr(), children: serverSettings),
],
),
),
@ -337,12 +457,12 @@ class SettingsScreen extends HookConsumerWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_SettingsSection(
title: 'Behavior',
title: 'settingsBehavior'.tr(),
children: behaviorSettings,
),
if (desktopSettings.isNotEmpty)
_SettingsSection(
title: 'Desktop',
title: 'settingsDesktop'.tr(),
children: desktopSettings,
),
],
@ -355,11 +475,11 @@ class SettingsScreen extends HookConsumerWidget {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_SettingsSection(title: 'Appearance', children: appearanceSettings),
_SettingsSection(title: 'Server', children: serverSettings),
_SettingsSection(title: 'Behavior', children: behaviorSettings),
_SettingsSection(title: 'settingsAppearance'.tr(), children: appearanceSettings),
_SettingsSection(title: 'settingsServer'.tr(), children: serverSettings),
_SettingsSection(title: 'settingsBehavior'.tr(), children: behaviorSettings),
if (desktopSettings.isNotEmpty)
_SettingsSection(title: 'Desktop', children: desktopSettings),
_SettingsSection(title: 'settingsDesktop'.tr(), children: desktopSettings),
],
);
}
@ -368,7 +488,7 @@ class SettingsScreen extends HookConsumerWidget {
return AppScaffold(
noBackground: false,
appBar: AppBar(
title: Text('Settings').tr(),
title: Text('settings').tr(),
actions:
isDesktop
? [
@ -385,7 +505,7 @@ class SettingsScreen extends HookConsumerWidget {
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text('Close').tr(),
child: Text('close').tr(),
),
],
),

View File

@ -39,7 +39,7 @@ Future<XFile?> cropImage(
}
Completer<SnCloudFile?> putMediaToCloud({
required dynamic fileData, // Can be XFile or List<int> (Uint8List)
required UniversalFile fileData,
required String atk,
required String baseUrl,
String? filename,
@ -51,21 +51,27 @@ Completer<SnCloudFile?> putMediaToCloud({
String actualMimetype = mimetype ?? '';
Uint8List? byteData;
if (fileData is XFile) {
file = fileData;
actualFilename = filename ?? fileData.name;
actualMimetype = mimetype ?? fileData.mimeType ?? '';
} else if (fileData is List<int> || fileData is Uint8List) {
byteData = fileData is List<int> ? Uint8List.fromList(fileData) : fileData;
// Handle the data based on what's in the UniversalFile
final data = fileData.data;
if (data is XFile) {
file = data;
actualFilename = filename ?? data.name;
actualMimetype = mimetype ?? data.mimeType ?? '';
} else if (data is List<int> || data is Uint8List) {
byteData = data is List<int> ? Uint8List.fromList(data) : data;
actualFilename = filename ?? 'uploaded_file';
actualMimetype = mimetype ?? 'application/octet-stream';
if (mimetype == null) {
throw ArgumentError('Mimetype is required when providing raw bytes.');
}
file = XFile.fromData(byteData!, mimeType: actualMimetype);
} else if (data is SnCloudFile) {
// If the file is already on the cloud, just return it
return Completer<SnCloudFile?>()..complete(data);
} else {
throw ArgumentError(
'Invalid fileData type. Expected XFile or List<int> (Uint8List).',
'Invalid fileData type. Expected data to be XFile, List<int>, Uint8List, or SnCloudFile.',
);
}

View File

@ -81,7 +81,7 @@ class AccountPickerSheet extends HookConsumerWidget {
final account = accounts[index];
return ListTile(
leading: ProfilePictureWidget(
fileId: account.profile.pictureId,
fileId: account.profile.picture?.id,
),
title: Text(account.nick),
subtitle: Text('@${account.name}'),

View File

@ -0,0 +1,246 @@
import 'dart:convert';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/models/auth.dart';
import 'package:island/pods/network.dart';
import 'package:island/services/responsive.dart';
import 'package:island/widgets/alert.dart';
import 'package:island/widgets/content/sheet.dart';
import 'package:island/widgets/response.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:styled_widget/styled_widget.dart';
part 'account_session_sheet.g.dart';
@riverpod
Future<List<SnAuthDevice>> authDevices(Ref ref) async {
final resp = await ref.watch(apiClientProvider).get('/accounts/me/devices');
final sessionId = resp.headers.value('x-auth-session');
final data =
resp.data.map<SnAuthDevice>((e) {
final ele = SnAuthDevice.fromJson(e);
return ele.copyWith(isCurrent: ele.sessions.first.id == sessionId);
}).toList();
return data;
}
class _DeviceListTile extends StatelessWidget {
final SnAuthDevice device;
final Function(String) updateDeviceLabel;
final Function(String) logoutDevice;
const _DeviceListTile({
required this.device,
required this.updateDeviceLabel,
required this.logoutDevice,
});
@override
Widget build(BuildContext context) {
return ListTile(
isThreeLine: true,
contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
leading: Icon(switch (device.platform) {
0 => Icons.device_unknown, // Unidentified
1 => Icons.web, // Web
2 => Icons.phone_iphone, // iOS
3 => Icons.phone_android, // Android
4 => Icons.laptop_mac, // macOS
5 => Icons.window, // Windows
6 => Icons.computer, // Linux
_ => Icons.device_unknown, // fallback
}).padding(top: 4),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text('authSessionsCount'.plural(device.sessions.length)),
Text(
'lastActiveAt'.tr(
args: [
DateFormat().format(
device.sessions.first.lastGrantedAt.toLocal(),
),
],
),
),
Text(device.sessions.first.challenge.ipAddress),
if (device.isCurrent)
Row(
children: [
Badge(
backgroundColor: Theme.of(context).colorScheme.primary,
label: Text(
'authDeviceCurrent'.tr(),
style: TextStyle(
color: Theme.of(context).colorScheme.onPrimary,
),
),
),
],
).padding(top: 4),
],
),
title: Text(device.label ?? device.sessions.first.challenge.userAgent),
trailing:
isWideScreen(context)
? Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: Icon(Icons.edit),
tooltip: 'authDeviceEditLabel'.tr(),
onPressed:
() => updateDeviceLabel(device.sessions.first.id),
),
if (!device.isCurrent)
IconButton(
icon: Icon(Icons.logout),
tooltip: 'authDeviceLogout'.tr(),
onPressed: () => logoutDevice(device.sessions.first.id),
),
],
)
: null,
);
}
}
class AccountSessionSheet extends HookConsumerWidget {
const AccountSessionSheet({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final authDevices = ref.watch(authDevicesProvider);
void logoutDevice(String sessionId) async {
final confirm = await showConfirmAlert(
'authDeviceLogoutHint'.tr(),
'authDeviceLogout'.tr(),
);
if (!confirm || !context.mounted) return;
try {
final apiClient = ref.watch(apiClientProvider);
await apiClient.delete('/accounts/me/sessions/$sessionId');
ref.invalidate(authDevicesProvider);
} catch (err) {
showErrorAlert(err);
}
}
void updateDeviceLabel(String sessionId) async {
final controller = TextEditingController();
final label = await showDialog<String>(
context: context,
builder:
(context) => AlertDialog(
title: Text('authDeviceLabelTitle'.tr()),
content: TextField(
controller: controller,
decoration: InputDecoration(
isDense: true,
border: const OutlineInputBorder(),
hintText: 'authDeviceLabelHint'.tr(),
),
autofocus: true,
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text('cancel'.tr()),
),
TextButton(
onPressed: () => Navigator.pop(context, controller.text),
child: Text('confirm'.tr()),
),
],
),
);
if (label == null || label.isEmpty || !context.mounted) return;
try {
final apiClient = ref.watch(apiClientProvider);
await apiClient.patch(
'/accounts/me/sessions/$sessionId/label',
data: jsonEncode(label),
);
ref.invalidate(authDevicesProvider);
} catch (err) {
showErrorAlert(err);
}
}
final wideScreen = isWideScreen(context);
return SheetScaffold(
titleText: 'authSessions'.tr(),
child: authDevices.when(
data:
(data) => RefreshIndicator(
onRefresh:
() => Future.sync(() => ref.invalidate(authDevicesProvider)),
child: ListView.builder(
padding: EdgeInsets.zero,
itemCount: data.length,
itemBuilder: (context, index) {
final device = data[index];
if (wideScreen) {
return _DeviceListTile(
device: device,
updateDeviceLabel: updateDeviceLabel,
logoutDevice: logoutDevice,
);
} else {
return Dismissible(
key: Key('device-${device.sessions.first.id}'),
direction:
device.isCurrent
? DismissDirection.startToEnd
: DismissDirection.horizontal,
background: Container(
color: Colors.blue,
alignment: Alignment.centerLeft,
padding: EdgeInsets.symmetric(horizontal: 20),
child: Icon(Icons.edit, color: Colors.white),
),
secondaryBackground: Container(
color: Colors.red,
alignment: Alignment.centerRight,
padding: EdgeInsets.symmetric(horizontal: 20),
child: Icon(Icons.logout, color: Colors.white),
),
confirmDismiss: (direction) async {
if (direction == DismissDirection.startToEnd) {
updateDeviceLabel(device.sessions.first.id);
return false;
} else {
final confirm = await showConfirmAlert(
'authDeviceLogoutHint'.tr(),
'authDeviceLogout'.tr(),
);
if (confirm && context.mounted) {
logoutDevice(device.sessions.first.id);
}
return false; // Don't dismiss
}
},
child: _DeviceListTile(
device: device,
updateDeviceLabel: updateDeviceLabel,
logoutDevice: logoutDevice,
),
);
}
},
),
),
error:
(err, _) => ResponseErrorWidget(
error: err,
onRetry: () => ref.invalidate(authDevicesProvider),
),
loading: () => ResponseLoadingWidget(),
),
);
}
}

View File

@ -0,0 +1,29 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'account_session_sheet.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$authDevicesHash() => r'19807110962206a9637075d03cd372233cae2f49';
/// See also [authDevices].
@ProviderFor(authDevices)
final authDevicesProvider =
AutoDisposeFutureProvider<List<SnAuthDevice>>.internal(
authDevices,
name: r'authDevicesProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$authDevicesHash,
dependencies: null,
allTransitiveDependencies: null,
);
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
typedef AuthDevicesRef = AutoDisposeFutureProviderRef<List<SnAuthDevice>>;
// 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

View File

@ -133,54 +133,3 @@ class AccountStatusWidget extends HookConsumerWidget {
).opacity((userStatus.value?.isCustomized ?? false) ? 1 : 0.85);
}
}
class StatusActivityWidget extends StatelessWidget {
final SnActivity item;
const StatusActivityWidget({super.key, required this.item});
@override
Widget build(BuildContext context) {
final result = SnAccountStatus.fromJson(item.data);
return Row(
spacing: 12,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ProfilePictureWidget(
fileId: item.account.profile.pictureId,
radius: 12,
),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Icon(Symbols.circle, size: 12).padding(top: 1, left: 2),
const Gap(4),
Text('status').fontSize(11).tr(),
],
).opacity(0.85),
Text(
result.clearedAt == null
? 'statusActivityTitle'
: 'statusActivityEndedTitle',
)
.tr(
args: [
item.account.nick,
result.label,
RelativeTime(context).format(result.createdAt),
if (result.clearedAt != null)
RelativeTime(context).format(result.clearedAt!),
],
)
.fontSize(13)
.padding(left: 2),
],
),
),
],
).padding(horizontal: 16, vertical: 12);
}
}

View File

@ -235,7 +235,7 @@ class AppBackground extends ConsumerWidget {
},
loading: () => const SizedBox(),
error:
(_, __) => Material(
(_, _) => Material(
color: Theme.of(context).colorScheme.surface,
child: child,
),
@ -290,7 +290,9 @@ class _WebSocketIndicator extends HookConsumerWidget {
return AnimatedPositioned(
duration: Duration(milliseconds: 1850),
top:
!user.hasValue || websocketState == WebSocketState.connected()
!user.hasValue ||
user.value == null ||
websocketState == WebSocketState.connected()
? -indicatorHeight
: 0,
curve: Curves.fastLinearToSlowEaseIn,

View File

@ -14,6 +14,7 @@ part 'call_button.g.dart';
@riverpod
Future<SnRealtimeCall?> ongoingCall(Ref ref, String roomId) async {
if (roomId.isEmpty) return null;
try {
final apiClient = ref.watch(apiClientProvider);
final resp = await apiClient.get('/chat/realtime/$roomId');

View File

@ -6,7 +6,7 @@ part of 'call_button.dart';
// RiverpodGenerator
// **************************************************************************
String _$ongoingCallHash() => r'd8a942e6695a7da702daeaa452464c16761ef6e7';
String _$ongoingCallHash() => r'ab7337bcd4d766897bd6d6a38f418c6bdd15eb94';
/// Copied from Dart SDK
class _SystemHash {

View File

@ -45,7 +45,7 @@ class CallControlsBar extends HookConsumerWidget {
callNotifier.localParticipant!.isSpeaking,
audioLevel:
callNotifier.localParticipant!.audioLevel,
pictureId: userInfo.value?.profile.pictureId,
pictureId: userInfo.value?.profile.picture?.id,
size: 36,
).center(),
);
@ -158,7 +158,8 @@ class CallOverlayBar extends HookConsumerWidget {
.profile
?.account
.profile
.pictureId,
.picture
?.id,
size: 36,
).center(),
);

View File

@ -106,7 +106,7 @@ class CallParticipantTile extends StatelessWidget {
return SpeakingRippleAvatar(
isSpeaking: live.isSpeaking,
audioLevel: audioLevel,
pictureId: live.participant.profile?.account.profile.pictureId,
pictureId: live.participant.profile?.account.profile.picture?.id,
size: 84,
);
}

View File

@ -1,5 +1,9 @@
import 'dart:io';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/database/message.dart';
@ -27,6 +31,7 @@ class MessageItem extends HookConsumerWidget {
final Function(String action)? onAction;
final Map<int, double>? progress;
final bool showAvatar;
final Function(String messageId) onJump;
const MessageItem({
super.key,
@ -35,6 +40,7 @@ class MessageItem extends HookConsumerWidget {
required this.onAction,
required this.progress,
required this.showAvatar,
required this.onJump,
});
@override
@ -54,6 +60,8 @@ class MessageItem extends HookConsumerWidget {
final remoteMessage = message.toRemoteMessage();
final sender = remoteMessage.sender;
final isMobile = !kIsWeb && (Platform.isAndroid || Platform.isIOS);
return ContextMenuWidget(
menuProvider: (_) {
if (onAction == null) return Menu(children: []);
@ -90,6 +98,17 @@ class MessageItem extends HookConsumerWidget {
onAction!.call(MessageItemAction.forward);
},
),
if (isMobile) MenuSeparator(),
if (isMobile)
MenuAction(
title: 'copyMessage'.tr(),
image: MenuImage.icon(Symbols.copy_all),
callback: () {
Clipboard.setData(
ClipboardData(text: remoteMessage.content ?? ''),
);
},
),
],
);
},
@ -111,7 +130,7 @@ class MessageItem extends HookConsumerWidget {
mainAxisSize: MainAxisSize.min,
children: [
ProfilePictureWidget(
fileId: sender.account.profile.pictureId,
fileId: sender.account.profile.picture?.id,
radius: 16,
),
Column(
@ -355,39 +374,67 @@ class MessageQuoteWidget extends HookConsumerWidget {
if (remoteMessage != null) {
return ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(8)),
child: Container(
padding: EdgeInsets.symmetric(vertical: 4, horizontal: 6),
color: Theme.of(
context,
).colorScheme.primaryFixedDim.withOpacity(0.4),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (isReply)
Row(
mainAxisSize: MainAxisSize.min,
spacing: 4,
children: [
Icon(Symbols.reply, size: 16, color: textColor),
Text(
'Replying to ${remoteMessage.sender.account.nick}',
).textColor(textColor).bold(),
],
).padding(right: 8)
else
Row(
mainAxisSize: MainAxisSize.min,
spacing: 4,
children: [
Icon(Symbols.forward, size: 16, color: textColor),
Text(
'Forwarded from ${remoteMessage.sender.account.nick}',
).textColor(textColor).bold(),
],
).padding(right: 8),
if (_MessageItemContent.hasContent(remoteMessage))
_MessageItemContent(item: remoteMessage),
],
child: GestureDetector(
onTap: () {
final messageId =
isReply
? message.toRemoteMessage().repliedMessageId!
: message.toRemoteMessage().forwardedMessageId!;
// Find the nearest MessageItem ancestor and call its onJump method
final MessageItem? ancestor =
context.findAncestorWidgetOfExactType<MessageItem>();
if (ancestor != null) {
ancestor.onJump(messageId);
}
},
child: Container(
padding: EdgeInsets.symmetric(vertical: 4, horizontal: 6),
color: Theme.of(
context,
).colorScheme.primaryFixedDim.withOpacity(0.4),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (isReply)
Row(
mainAxisSize: MainAxisSize.min,
spacing: 4,
children: [
Icon(Symbols.reply, size: 16, color: textColor),
Text(
'${'repliedTo'.tr()} ${remoteMessage.sender.account.nick}',
).textColor(textColor).bold(),
],
).padding(right: 8)
else
Row(
mainAxisSize: MainAxisSize.min,
spacing: 4,
children: [
Icon(Symbols.forward, size: 16, color: textColor),
Text(
'${'forwarded'.tr()} ${remoteMessage.sender.account.nick}',
).textColor(textColor).bold(),
],
).padding(right: 8),
if (_MessageItemContent.hasContent(remoteMessage))
_MessageItemContent(item: remoteMessage),
if (remoteMessage.attachments.isNotEmpty)
Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Symbols.attach_file, size: 12, color: textColor),
const SizedBox(width: 4),
Text(
'hasAttachments'.plural(
remoteMessage.attachments.length,
),
style: TextStyle(color: textColor, fontSize: 12),
),
],
).padding(vertical: 2),
],
),
),
),
).padding(bottom: 4);
@ -414,7 +461,7 @@ class _MessageItemContent extends StatelessWidget {
);
case 'text':
default:
return MarkdownTextContent(content: item.content!);
return MarkdownTextContent(content: item.content!, isSelectable: true);
}
}

Some files were not shown because too many files have changed in this diff Show More