Compare commits

...

71 Commits

Author SHA1 Message Date
LittleSheep
e5f2e2d146 🐛 Fix notification page cannot return 2025-08-18 11:43:12 +08:00
LittleSheep
11368d064f 🐛 Fix explore page 2025-08-18 11:42:42 +08:00
LittleSheep
246b163aec 🍱 Update notification icon 2025-08-18 02:36:29 +08:00
LittleSheep
10e0d2fe5f 🚀 Launch 3.2.0+126 2025-08-18 02:21:39 +08:00
LittleSheep
99e10cb612 Chat, realm member list with status 2025-08-18 02:01:13 +08:00
LittleSheep
1db6941431 💄 Optimize message styling 2025-08-17 23:13:59 +08:00
LittleSheep
8370da4fe3 Realm page redesgiened 2025-08-17 19:44:43 +08:00
LittleSheep
2bdf7029e9 💄 Optimize chat list tile 2025-08-17 13:51:02 +08:00
LittleSheep
86682a3a9a 💄 Optimize realm discovery list 2025-08-17 13:48:02 +08:00
LittleSheep
c3925e81b5 :drunk: Media offline error builder 2025-08-17 13:40:35 +08:00
LittleSheep
6f1f488490 💄 Limit realm card max description line 2025-08-17 13:36:02 +08:00
LittleSheep
31b2de2e46 💄 Update styling of realm list 2025-08-17 13:35:03 +08:00
LittleSheep
412dcfa62a 💄 Move the publisher invite icon to the list 2025-08-17 13:26:17 +08:00
LittleSheep
ffdc7e81ae 🐛 Make video being contained to prevent some issue 2025-08-17 13:15:55 +08:00
LittleSheep
1d3357803d 🐛 Fix post slug 2025-08-17 13:11:13 +08:00
LittleSheep
6c48aa2356 💄 Optimize attachment preview 2025-08-17 13:08:08 +08:00
LittleSheep
466e354679 💄 Optimize attachment in article 2025-08-17 13:02:52 +08:00
LittleSheep
5d4b896f70 Post slug 2025-08-17 12:47:00 +08:00
LittleSheep
a04dffdfe8 🐛 Fix missing sharePositionOrigin in share 2025-08-17 11:56:34 +08:00
LittleSheep
ff871943cf Show realm post indicator 2025-08-17 02:56:12 +08:00
LittleSheep
1a892ab227 Realm post, and post publisher is org is rounded rect 2025-08-17 02:37:45 +08:00
LittleSheep
af1b303211 Prevent user from setting empty links 2025-08-17 00:55:22 +08:00
LittleSheep
6fd702eba8 🐛 Fix profile update 2025-08-17 00:47:10 +08:00
LittleSheep
d220d43cd2 💄 Optimize chat room 2025-08-17 00:11:28 +08:00
LittleSheep
6892afb974 🔊 Add more logging and optimzation 2025-08-16 23:39:41 +08:00
LittleSheep
007b46b080 ♻️ Refactored the message repository logic 2025-08-16 23:07:21 +08:00
LittleSheep
67d130dc34 🔨 Sync the CMakeLists in linux to update date with the v2 modified version 2025-08-16 18:09:45 +08:00
LittleSheep
7e923c77fe 💄 Change explore screen wide mode breakpoint 2025-08-16 18:03:23 +08:00
LittleSheep
a593b52812 ♻️ Adjust the firebase analytics observer guard 2025-08-16 17:20:03 +08:00
LittleSheep
520dc80303 🔀 Merge pull request #169 from Texas0295/v3
🐛 linux: guard FirebaseAnalyticsObserver when Firebase is not initialized
2025-08-16 17:18:20 +08:00
LittleSheep
001897bbcd Notification indicator 2025-08-16 17:14:26 +08:00
Texas0295
bab29c23e3 🐛 linux: guard FirebaseAnalyticsObserver when Firebase is not initialized 2025-08-16 16:58:12 +08:00
LittleSheep
76b39f2df3 Mark all as read 2025-08-16 11:47:29 +08:00
LittleSheep
509b3e145b 🐛 Fix category selection render error 2025-08-16 02:39:00 +08:00
LittleSheep
2b80ebc2d0 🐛 Fix markdown image in chat close #167 2025-08-16 02:14:44 +08:00
LittleSheep
0ab908dd2a Auto collapse featured post if read 2025-08-16 02:02:06 +08:00
LittleSheep
6007467e7a 🐛 Fixes due to changes in backend 2025-08-15 03:33:20 +08:00
LittleSheep
3745157c42 💄 Optimize snackbars 2025-08-15 00:09:05 +08:00
LittleSheep
94481ec7bd 💫 Chnaged tab page animations 2025-08-14 14:21:57 +08:00
LittleSheep
fbfe8cbdee 🚀 Launch 3.2.0+125 2025-08-14 02:34:05 +08:00
LittleSheep
fbbab0a981 Send delete session requset when logout 2025-08-14 02:29:32 +08:00
LittleSheep
ae2fb3b303 👽 Support new authorized device 2025-08-14 02:10:21 +08:00
LittleSheep
3d7a4666ed 👔 Skip the debug mode crashlytics setup 2025-08-13 15:32:34 +08:00
LittleSheep
5d3e0fb800 🐛 Fix gha artificats has same name 2025-08-13 15:26:00 +08:00
LittleSheep
85ff52a661 🔨 Update windows setup build script and use gha to do so 2025-08-13 13:55:11 +08:00
LittleSheep
da7fd64a43 Merge branch 'v3' of https://github.com/Solsynth/Solian into v3 2025-08-13 13:40:58 +08:00
LittleSheep
3902633217 🔨 Update windows setup builder script 2025-08-13 13:40:53 +08:00
LittleSheep
f478ea8b84 🐛 Serval bug fixes 2025-08-13 13:06:20 +08:00
LittleSheep
0f481aff5b ♻️ Extract the poll related words 2025-08-13 00:35:44 +08:00
LittleSheep
7a31663310 🍱 Update translation 2025-08-12 22:55:20 +08:00
LittleSheep
0239c53c04 💄 Optimize poll submitted view 2025-08-12 22:52:52 +08:00
LittleSheep
16987c758e 💄 Optimize poll 2025-08-12 22:52:05 +08:00
LittleSheep
3a36915140 Setup windows installer 2025-08-12 15:25:41 +08:00
LittleSheep
4bde708878 🐛 Fix windows 2025-08-12 13:04:15 +08:00
LittleSheep
2f0cf560f8 🐛 Hide share via screenshot on web 2025-08-11 22:23:21 +08:00
LittleSheep
cf355a95fd Post share card 2025-08-11 22:18:35 +08:00
LittleSheep
2f43073172 🐛 Hide actions on user himself's profile page 2025-08-11 22:02:50 +08:00
LittleSheep
8236d31ecc ♻️ Refactor the two types of post item 2025-08-11 21:33:10 +08:00
LittleSheep
459a7dade0 🚀 Launch 3.2.0+124 2025-08-11 18:56:32 +08:00
LittleSheep
e6000a660a 📈 Add firebase analytics 2025-08-11 17:59:05 +08:00
LittleSheep
75abaac205 📈 Setup firebase crash handler 2025-08-11 17:25:31 +08:00
LittleSheep
603d5c3f73 Remove keyboard nav 2025-08-11 16:46:43 +08:00
LittleSheep
4e4bd99598 🚀 Launch 3.1.0+123 2025-08-11 02:06:29 +08:00
LittleSheep
d1fbe5f15e 🐛 Dozens of bug fixes 2025-08-11 01:56:19 +08:00
LittleSheep
c061ef2132 🐛 Bug fixes 2025-08-11 01:44:18 +08:00
LittleSheep
c378309bdd 📝 Update localization 2025-08-11 01:44:12 +08:00
LittleSheep
b2c5d64fc5 Keyboard navigation basis 2025-08-10 16:57:11 +08:00
LittleSheep
5371637b16 🔀 Merge pull request #161 from Texas0295/v3
🐛 linux: guard FirebaseMessaging on unsupported platforms
2025-08-10 14:05:48 +08:00
LittleSheep
c5cbf0af37 ⬆️ Upgrade android native project 2025-08-10 13:51:19 +08:00
LittleSheep
1a31e22450 🐛 Fix stickers pack unable to create 2025-08-10 13:23:15 +08:00
Texas0295
49db54529d 🐛 linux: guard FirebaseMessaging on unsupported platforms 2025-08-10 13:17:48 +08:00
120 changed files with 6584 additions and 3704 deletions

View File

@@ -41,6 +41,15 @@ jobs:
with:
name: build-output-windows
path: build/windows/x64/runner/Release
- name: Compile Installer
uses: Minionguyjpro/Inno-Setup-Action@v1.2.2
with:
path: setup.iss
- name: Archive installer artifacts
uses: actions/upload-artifact@v4
with:
name: build-output-windows-installer
path: Installer/windows-x86_64-setup.exe
build-linux:
runs-on: ubuntu-latest
steps:

3
.gitignore vendored
View File

@@ -12,6 +12,9 @@
.swiftpm/
migrate_working_dir/
# Inno Setup
Installer/
# IntelliJ related
*.iml
*.ipr

View File

@@ -5,6 +5,7 @@ plugins {
id("com.android.application")
// START: FlutterFire Configuration
id("com.google.gms.google-services")
id("com.google.firebase.crashlytics")
// END: FlutterFire Configuration
id("kotlin-android")
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
@@ -51,6 +52,12 @@ android {
buildTypes {
release {
signingConfig = signingConfigs.getByName("release")
isMinifyEnabled = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
}
@@ -58,7 +65,7 @@ android {
dependencies {
implementation("com.google.android.material:material:1.12.0")
implementation("com.github.bumptech.glide:glide:4.16.0")
implementation("com.squareup.okhttp3:okhttp:4.12.0")
implementation("com.squareup.okhttp3:okhttp:5.1.0")
}
flutter {

5
android/app/proguard-rules.pro vendored Normal file
View File

@@ -0,0 +1,5 @@
# JNI Zero initialization (required for WebRTC native method registration)
-keep class livekit.org.jni_zero.JniInit {
# Keep the init method un-obfuscated for native code callback
private static java.lang.Object[] init();
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

View File

@@ -0,0 +1,41 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="192dp"
android:height="192dp"
android:viewportWidth="192"
android:viewportHeight="192">
<path
android:pathData="M54,147h86"
android:strokeLineJoin="round"
android:strokeWidth="12"
android:fillColor="#00000000"
android:strokeColor="#000"
android:strokeLineCap="round"/>
<path
android:pathData="M57,111s-2,-4.5 -2,-10m22,22s-4,7 -11,4m9,-22s-2,-4.5 -2,-10"
android:strokeLineJoin="round"
android:strokeWidth="10"
android:fillColor="#00000000"
android:strokeColor="#000"
android:strokeLineCap="round"/>
<path
android:pathData="M54,147a32,32 0,0 1,-12 -61.67A39,39 0,0 1,81 46m59,101a30,30 0,0 0,29.93 -28"
android:strokeLineJoin="round"
android:strokeWidth="12"
android:fillColor="#00000000"
android:strokeColor="#000"
android:strokeLineCap="round"/>
<path
android:pathData="M132,75m-4,0a4,4 0,1 1,8 0a4,4 0,1 1,-8 0"
android:strokeLineJoin="round"
android:strokeWidth="8"
android:fillColor="#00000000"
android:strokeColor="#000"
android:strokeLineCap="round"/>
<path
android:pathData="M112.5,41.22C100.84,47.96 93,60.56 93,75c0,6.38 1.53,12.39 4.24,17.71m69.51,-35.42A38.84,38.84 0,0 1,171 75c0,14.43 -7.84,27.03 -19.49,33.78m-0.79,-43.32A20.9,20.9 0,0 1,153 75c0,7.77 -4.22,14.56 -10.49,18.19m-21,-36.38C115.22,60.44 111,67.23 111,75a20.9,20.9 0,0 0,2.28 9.53"
android:strokeLineJoin="round"
android:strokeWidth="10"
android:fillColor="#00000000"
android:strokeColor="#000"
android:strokeLineCap="round"/>
</vector>

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.11.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-all.zip

View File

@@ -18,11 +18,12 @@ pluginManagement {
plugins {
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
id("com.android.application") version "8.10.1" apply false
id("com.android.application") version "8.12.0" apply false
// START: FlutterFire Configuration
id("com.google.gms.google-services") version("4.3.15") apply false
id("com.google.firebase.crashlytics") version("2.8.1") apply false
// END: FlutterFire Configuration
id("org.jetbrains.kotlin.android") version "1.8.22" apply false
id("org.jetbrains.kotlin.android") version("2.2.0") apply false
}
include(":app")

View File

@@ -334,6 +334,7 @@
"walletCreate": "Create a Wallet",
"settingsServerUrl": "Server URL",
"settingsApplied": "The settings has been applied.",
"settingsCustomFontsHelper": "Use comma to seprate.",
"notifications": "Notifications",
"posts": "Posts",
"settingsBackgroundImage": "Background Image",
@@ -573,6 +574,7 @@
"keyboardShortcuts": "Keyboard Shortcuts",
"share": "Share",
"sharePost": "Share Post",
"sharePostPhoto": "Share Post as Photo",
"quickActions": "Quick Actions",
"post": "Post",
"copy": "Copy",
@@ -760,6 +762,7 @@
"pollsRecent": "Recent Polls",
"pollCreateNew": "Create New",
"pollCreateNewHint": "Create a new poll for your post. Pick a publisher and continue.",
"pollQuestions": "Questions",
"publisher": "Publisher",
"publisherHint": "Enter the publisher name",
"publisherCannotBeEmpty": "Publisher cannot be empty",
@@ -789,5 +792,63 @@
"linkKey": "Link Name",
"linkValue": "URL",
"debugOptions": "Debug Options",
"joinedAt": "Joined at {}"
"joinedAt": "Joined at {}",
"searchAccounts": "Search accounts...",
"webFeeds": "Web Feeds",
"polls": "Polls",
"sharePostSlogan": "Explore more on the Solar Network",
"filesListAdditional": {
"one": "+{} file remaining",
"other": "+{} files remaining"
},
"pollAnswerSubmitted": "Poll answer has been submitted.",
"modifyAnswers": "Modify Answers",
"back": "Back",
"submit": "Submit",
"pollOptionDefaultLabel": "Option 1",
"pollUpdated": "Poll updated.",
"pollCreated": "Poll created.",
"pollCreate": "Create Poll",
"pollEdit": "Edit Poll",
"pollPreviewJsonDebug": "Debug Preview",
"pollTitleRequired": "Title is required",
"pollEndDateOptional": "End date & time (optional)",
"notSet": "Not set",
"pick": "Pick",
"clear": "Clear",
"questions": "Questions",
"pollAddQuestion": "Add question",
"pollQuestionTypeSingleChoice": "Single choice",
"pollQuestionTypeMultipleChoice": "Multiple choice",
"pollQuestionTypeFreeText": "Free text",
"pollQuestionTypeYesNo": "Yes / No",
"pollQuestionTypeRating": "Rating",
"pollNoQuestionsYet": "No questions yet",
"pollNoQuestionsHint": "Use \"Add question\" to start building your poll.",
"pollDebugPreview": "Debug Preview",
"pollUntitledQuestion": "Untitled question",
"moveUp": "Move up",
"moveDown": "Move down",
"required": "Required",
"pollQuestionTitle": "Question title",
"pollQuestionTitleRequired": "Question title is required",
"pollQuestionDescriptionOptional": "Question description (optional)",
"options": "Options",
"pollAddOption": "Add option",
"pollOptionLabel": "Option label",
"pollLongTextAnswerPreview": "Long text answer (preview)",
"pollShortTextAnswerPreview": "Short text answer (preview)",
"messageJumpNotLoaded": "The referenced message was not loaded, unable to jump to it.",
"postUnlinkRealm": "No linked realm",
"postSlug": "Slug",
"postSlugHint": "The slug can be used to access your post via URL in the webpage, it should be publisher-wide unique.",
"attachmentOnDevice": "On-device",
"attachmentOnCloud": "On-cloud",
"attachments": "Attachments",
"publisherCollabInvitation": "Collabration invitations",
"publisherCollabInvitationCount": {
"zero": "No invitation",
"one": "{} available invitation",
"other": "{} available invitations"
}
}

View File

@@ -46,7 +46,6 @@
"delete": "删除",
"deletePublisher": "删除发布者",
"deletePublisherHint": "确定要删除此发布者吗?这也会删除此发布者下的所有帖子和收藏。",
"somethingWentWrong": "发生了一些错误",
"deletePost": "删除帖子",
"deletePostHint": "确定要删除这篇帖子吗?",
"copyLink": "复制链接",
@@ -120,14 +119,9 @@
"other": "{}个附件"
},
"edited": "已编辑",
"editedAt": "编辑于 {}",
"addVideo": "添加视频",
"addPhoto": "添加照片",
"addFile": "添加文件",
"addAttachmentById": "通过 ID 添加附件",
"enterFileId": "输入文件 ID",
"fileIdCannotBeEmpty": "文件 ID 不能为空",
"failedToFetchFile": "获取文件失败: {}",
"createDirectMessage": "创建新私人消息",
"gotoDirectMessage": "前往私信",
"react": "反应",
@@ -306,6 +300,7 @@
"walletCreate": "创建钱包",
"settingsServerUrl": "服务器 URL",
"settingsApplied": "设置已应用。",
"settingsCustomFontsHelper": "用逗号分隔。",
"notifications": "通知",
"posts": "帖子",
"settingsBackgroundImage": "背景图片",
@@ -350,11 +345,10 @@
"accountSettingsHelpContent": "此页面允许您管理您的帐户安全性、隐私和其他设置。如果您需要帮助,请联系管理员。",
"unauthorized": "未授权",
"unauthorizedHint": "您未登录或会话已过期,请重新登录。",
"publisherBelongsTo": "属于 {}",
"publisherBelongsTo": "属于",
"postContent": "内容",
"postSettings": "设置",
"postPublisherUnselected": "未指定发布者",
"postVisibility": "可见性",
"postVisibilityPublic": "公开",
"postVisibilityFriends": "仅好友可见",
"postVisibilityUnlisted": "不公开",
@@ -495,20 +489,26 @@
"paymentError": "付款失败: {error}",
"usePinInstead": "使用 PIN 码",
"levelProgress": "等级进度",
"unlockedFeatures": "已解锁的功能",
"unlockedFeaturesDescription": "在您当前级别上解锁的功能将显示在这里。",
"stellarMembership": "恒星计划",
"upgradeYourPlan": "升级您的计划",
"chooseYourPlan": "选择你的方案",
"currentMembership": "当前:{}",
"currentMembershipMember": "恒星计划「{}」级会员",
"membershipExpires": "过期于:{}",
"membershipTierStellar": "恒星",
"membershipTierNova": "新星",
"membershipTierSupernova": "超新星",
"membershipTierUnknown": "未知",
"membershipPriceStellar": "每月 1200 源点,至少需要 3 级",
"membershipPriceNova": "每月 2400 源点,至少需要 6 级",
"membershipPriceSupernova": "每月 3600 源点,至少需要 9 级",
"membershipFeatureBasic": "基础功能",
"membershipFeaturePrioritySupport": "优先支持",
"membershipFeatureAdFree": "无广告",
"membershipFeatureAllPrimary": "所有主要功能",
"membershipFeatureAdvancedCustomization": "高级自定义",
"membershipFeatureEarlyAccess": "抢先体验",
"membershipFeatureAllNova": "所有「新星」功能",
"membershipFeatureExclusiveContent": "限定内容",
"membershipFeatureVipSupport": "VIP 支持",
"membershipCurrentBadge": "当前",
"restorePurchase": "恢复购买",
"restorePurchaseDescription": "输入您付款的提供商和订单 ID 以恢复您的购买。",
@@ -518,11 +518,186 @@
"enterOrderId": "输入您的订单 ID",
"restore": "恢复",
"keyboardShortcuts": "键盘快捷键",
"safetyReport": "举报",
"safetyReportTitle": "举报",
"safetyReportDescription": "通过举报不合适的内容和行为来维护我们社区的稳定。",
"safetyReportType": "举报类型",
"safetyReportReason": "更多证据",
"safetyReportReasonHint": "请提供更多证据……",
"safetyReportSubmit": "提交举报",
"safetyReportSubmitting": "提交中……",
"safetyReportSuccess": "举报成功,感谢您参与维护社区健康发展。",
"safetyReportError": "举报失败,请稍后重试。",
"safetyReportReasonRequired": "请提供举报证据",
"safetyReportTypeSpam": "垃圾或导向错误",
"safetyReportTypeHarassment": "骚扰或暴力行为",
"safetyReportTypeHateSpeech": "歧视言论",
"safetyReportTypeViolence": "威胁或暴力内容",
"safetyReportTypeAdultContent": "成人内容",
"safetyReportTypeIntellectualProperty": "抄袭",
"safetyReportTypeOther": "其它",
"safetyReportTypeInappropriate": "不良内容",
"safetyReportTypeCopyright": "版权侵害",
"safetyReportSuccessTitle": "举报成功",
"safetyReportErrorTitle": "错误",
"discover": "发现",
"joinRealm": "加入领域",
"removePublisherMember": "移除发布者",
"removePublisherMemberHint": "你确定要将这个成员从发布者中移除?",
"drafts": "草稿箱",
"noDrafts": "无草稿",
"articleDrafts": "文章草稿",
"postDrafts": "帖子草稿",
"saveDraft": "保存草稿",
"draftSaved": "草稿已保存",
"draftSaveFailed": "保存草稿失败",
"clearAllDrafts": "清除全部草稿",
"clearAllDraftsConfirm": "你确定要清除全部草稿?这一操作无法撤销。",
"clearAll": "清除所有",
"untitled": "未命名",
"noContent": "内容为空",
"justNow": "刚刚",
"minutesAgo": "{} 分钟以前",
"hoursAgo": "{} 小时以前",
"daysAgo": "{} 天以前",
"public": "公开的",
"unlisted": "不列出",
"friends": "朋友",
"selected": "选择的",
"private": "私密的",
"postContentEmpty": "发布的内容不能为空",
"share": "分享",
"sharePost": "分享帖子",
"quickActions": "快捷操作",
"post": "发帖",
"copy": "复制",
"sendToChat": "发送到聊天",
"failedToShareToPost": "分享到帖子失败:{}",
"shareToChatComingSoon": "分享到聊天功能即将推出",
"failedToShareToChat": "分享到聊天失败:{}",
"shareToSpecificChatComingSoon": "分享到 {} 功能即将推出",
"directChat": "私信",
"systemShareComingSoon": "系统分享功能即将推出",
"failedToShareToSystem": "分享到系统失败:{}",
"failedToCopy": "复制失败:{}",
"noChatRoomsAvailable": "无可用聊天室",
"failedToLoadChats": "加载聊天失败",
"contentToShare": "分享内容:",
"unknownChat": "未知聊天",
"addAdditionalMessage": "添加附加消息……",
"uploadingFiles": "上传文件中……",
"sharedSuccessfully": "分享成功!",
"shareSuccess": "分享成功!",
"shareToSpecificChatSuccess": "成功分享至 {}",
"wouldYouLikeToGoToChat": "是否前往该聊天?",
"no": "否",
"yes": "是",
"navigateToChat": "前往聊天",
"abuseReport": "举报",
"abuseReportTitle": "举报内容",
"abuseReportDescription": "举报不当内容或行为,协助维护社区安全。",
"abuseReportType": "举报类型",
"abuseReportReason": "补充详情",
"abuseReportReasonHint": "请提供更多详情……",
"abuseReportSubmit": "提交举报",
"abuseReportSuccess": "举报提交成功,感谢你为社区维护作出贡献。",
"abuseReportError": "无法提交举报,请稍后再试。",
"abuseReportReasonRequired": "请提供关于此事件的细节",
"abuseReportSuccessTitle": "举报已提交",
"abuseReportErrorTitle": "错误",
"abuseReportTypeSpam": "垃圾或错误信息",
"abuseReportTypeHarassment": "骚扰或滥用",
"abuseReportTypeInappropriate": "不合适的内容",
"abuseReportTypeViolence": "暴力或人身威胁",
"abuseReportTypeCopyright": "版权侵犯",
"abuseReportTypeImpersonation": "冒充",
"abuseReportTypeOffensiveContent": "冒犯性内容",
"abuseReportTypePrivacyViolation": "隐私侵犯",
"abuseReportTypeIllegalContent": "违法内容",
"abuseReportTypeOther": "其他",
"tags": "标签",
"tagsHint": "输入标签,用英文逗号分隔",
"categories": "分类",
"categoriesHint": "输入分类,由逗号隔开",
"chatNotJoined": "你还没有加入这个聊天。",
"chatUnableJoin": "由于该聊天的访问设置使你无法加入。",
"chatJoin": "加入聊天",
"realmJoin": "加入领域",
"realmJoinSuccess": "成功加入领域。",
"search": "搜索",
"publisherMembers": "合作者",
"developerHub": "开发者中心",
"developerHubUnselectedHint": "选择一名开发者查看总结数据或成为一名。",
"enrollDeveloper": "成为一名开发者",
"enrollDeveloperHint": "让你的一个发布者成为开发者。",
"noPublishersToEnroll": "你没有可以成为开发者的发布者。",
"totalCustomApps": "所有应用套件",
"customApps": "应用套件",
"noCustomApps": "还没有应用套件。",
"createCustomApp": "创建应用套件",
"editCustomApp": "编辑应用套件",
"deleteCustomApp": "删除应用套件",
"deleteCustomAppHint": "你确定要删除这个应用套件吗?这一步无法撤销。",
"publicRealm": "公开领域",
"publicRealmDescription": "所有人都可以预览这个领域的内容。",
"communityRealm": "领域",
"communityRealmDescription": "所有人都可以加入该领域并参与讨论,并将在发现和反馈页面显示。",
"publicChat": "公开聊天",
"publicChatDescription": "任何人都可以预览此聊天的内容。包括未加入的机器人。",
"communityChat": "社区聊天",
"communityChatDescription": "所有人都可以加入该聊天并参与参与讨论。",
"appLinks": "应用链接",
"homePageUrl": "主页链接",
"privacyPolicyUrl": "隐私政策链接",
"termsOfServiceUrl": "用户协议链接",
"oauthConfig": "OAuth 配置",
"clientUri": "客户端 URI",
"redirectUris": "重定向 URIs",
"addRedirectUri": "添加重定向 URI",
"allowedScopes": "允许的范围",
"requirePkce": "需要 PKCE",
"allowOfflineAccess": "允许离线访问",
"redirectUri": "重定向 URI",
"redirectUriHint": "重定向 URI 用于 OAuth 认证,但您的项目状态转为线上时我们会验证请求中的重定向 URI 是否符合此配置。",
"uriRequired": "这个 URI 是必须填写的。",
"uriInvalid": "无效 URI。",
"add": "添加",
"addScope": "添加范围",
"scope": "范围",
"publisherFeatures": "功能",
"publisherFeatureDevelop": "开发者计划",
"publisherFeatureDevelopDescription": "为你的开发者解锁包括应用套件API 及更多开发功能。",
"publisherFeatureDevelopHint": "目前该功能还在开发中,你需要邀请才可解锁。",
"learnMore": "了解更多",
"discoverWebArticles": "来自站外的文章",
"webArticlesStand": "文章亭",
"about": "关于",
"somethingWentWrong": "发生了一些错误",
"editedAt": "编辑于 {}",
"addAudio": "添加音频",
"recordAudio": "录制音频",
"linkAttachment": "链接附件",
"fileIdCannotBeEmpty": "文件 ID 不能为空",
"fileIdLinkHint": "还没有上传到 Solar Network点击此处打开 Solar Network Drive自定义您的上传内容。",
"failedToFetchFile": "获取文件失败:{}",
"callLeave": "离开",
"callEnd": "挂断通话",
"postType": "帖子类型",
"articleAttachmentHint": "附件必须上传并插入到文章主体中才能显示出来。",
"postVisibility": "可见性",
"currentMembershipMember": "恒星计划成员 · {}",
"membershipPriceStellar": "需要用户等级 3+,每月价格 1200 NSP",
"membershipPriceNova": "需要用户等级 6+,每月价格 2400 NSP",
"membershipPriceSupernova": "需要用户等级 9+,每月价格 3600 NSP",
"sharePostPhoto": "通过图片分享帖子",
"wouldYouLikeToNavigateToChat": "你想要前往聊天页面吗?",
"abuseReports": "举报",
"discoverRealms": "发现领域",
"discoverPublishers": "发现发布者",
"membershipCancel": "取消会员订阅",
"membershipCancelConfirm": "确定要取消您的会员订阅?",
"membershipCancelHint": "确定要取消您的会员订阅吗?将不会再被收费。的会员资格将在当前计费周期结束前保持有效。并且您在当前订阅结束之前无法重新订阅。",
"membershipCancelSuccess": "的会员订阅已成功取消。",
"membershipCancelConfirm": "确定要取消会员订阅",
"membershipCancelHint": "确定要取消会员订阅吗?将不会再次被扣费。的会员资格将在当前计费周期结束前保持有效。并且你将无法重新订阅,直到当前订阅结束。",
"membershipCancelSuccess": "的会员订阅已成功取消。",
"aboutScreenTitle": "关于",
"aboutScreenVersionInfo": "版本 {} ({})",
"aboutScreenAppInfoSectionTitle": "应用信息",
@@ -532,18 +707,122 @@
"aboutScreenLinksSectionTitle": "链接",
"aboutScreenPrivacyPolicyTitle": "隐私政策",
"aboutScreenTermsOfServiceTitle": "服务条款",
"aboutScreenOpenSourceLicensesTitle": "开源许可",
"aboutScreenOpenSourceLicensesTitle": "开源许可",
"aboutScreenDeveloperSectionTitle": "开发者",
"aboutScreenContactUsTitle": "联系我们",
"aboutScreenLicenseTitle": "许可",
"aboutScreenLicenseContent": "GNU Affero General Public License v3.0",
"aboutScreenCopyright": "版权所有 © 索尔辛茨 {}",
"aboutScreenMadeWith": "由 Solar Network Team 用 ❤︎️ 制作",
"aboutScreenFailedToLoadPackageInfo": "加载包信息失败{error}",
"aboutScreenLicenseTitle": "许可",
"aboutScreenLicenseContent": "无法翻译",
"aboutScreenCopyright": "版权所有 © Solsynth {}",
"aboutScreenMadeWith": "由 Solar Network 团队用 ❤︎️ 制作",
"aboutScreenFailedToLoadPackageInfo": "无法加载包信息:{error}",
"copiedToClipboard": "已复制到剪贴板",
"copyToClipboardTooltip": "复制到剪贴板",
"postForwardingTo": "转发",
"postReplyingTo": "回复",
"postEditing": "您正在编辑现有帖子",
"postArticle": "文章"
"postForwardingTo": "正在转发",
"postReplyingTo": "正在回复",
"postReplyPlaceholder": "发表你的回复",
"postEditing": "你正在编辑一个现有的帖子",
"postArticle": "文章",
"aboutDeviceName": "设备名称",
"aboutDeviceIdentifier": "设备标识符",
"donate": "捐赠",
"donateDescription": "支持我们继续开发 Solar Network并维持服务器运行。",
"fileId": "文件 ID",
"fileIdHint": "文件 ID 是你通过 Solar Network Drive 上传文件后获得的 ID。",
"translate": "翻译",
"translating": "正在翻译",
"translated": "已翻译",
"reactionThumbUp": "赞",
"reactionThumbDown": "踩",
"reactionJustOkay": "还行",
"reactionCry": "哭",
"reactionConfuse": "困惑",
"reactionClap": "鼓掌",
"reactionLaugh": "笑",
"reactionAngry": "生气",
"reactionParty": "派对",
"reactionPray": "祈祷",
"reactionHeart": "爱心",
"selectMicrophone": "选择麦克风",
"selectCamera": "选择摄像头",
"switchedTo": "已切换到 {}",
"connecting": "正在连接",
"reconnecting": "正在重新连接",
"disconnected": "已断开连接",
"connected": "已连接",
"repliesLoadMore": "加载更多回复",
"attachmentsRecentUploads": "最近上传",
"attachmentsManualInput": "手动输入",
"crop": "裁剪",
"rename": "重命名",
"markAsSensitive": "标记为敏感",
"fileName": "文件名",
"sensitiveCategories": {
"language": "语言",
"sexualContent": "色情内容",
"violence": "暴力",
"profanity": "亵渎",
"hateSpeech": "仇恨言论",
"racism": "种族主义",
"adultContent": "成人内容",
"drugAbuse": "药物滥用",
"alcoholAbuse": "酗酒",
"gambling": "赌博",
"selfHarm": "自残",
"childAbuse": "虐待儿童",
"other": "其他"
},
"poll": "投票",
"pollsRecent": "最近投票",
"pollCreateNew": "创建新投票",
"pollCreateNewHint": "为你的帖子创建一个新投票。选择一个发布者然后继续。",
"publisher": "发布者",
"publisherHint": "输入发布者名称",
"publisherCannotBeEmpty": "发布者不能为空",
"operationFailed": "操作失败:{}",
"stickerMarketplace": "贴纸市场",
"stickerPackAdded": "贴纸包已添加到你的收藏",
"stickerPackRemoved": "贴纸包已从你的收藏中移除",
"addPack": "添加贴纸包",
"removePack": "移除贴纸包",
"browseAndAddStickers": "浏览并添加贴纸包",
"stickerPack": "贴纸包",
"postCategoryTechnology": "科技",
"postCategoryTravel": "旅行",
"postCategoryFood": "美食",
"postCategoryHealth": "健康",
"postCategoryScience": "科学",
"postCategorySports": "体育",
"postCategoryFinance": "金融",
"postCategoryLife": "生活",
"postCategoryArt": "艺术",
"postCategoryStudy": "学习",
"postCategoryGaming": "游戏",
"postCategoryProgramming": "编程",
"postCategoryMusic": "音乐",
"links": "链接",
"addLink": "添加链接",
"linkKey": "链接名称",
"linkValue": "链接",
"debugOptions": "调试选项",
"joinedAt": "加入于 {}",
"searchAccounts": "搜索帐号……",
"webFeeds": "订阅源",
"polls": "投票",
"sharePostSlogan": "加入 Solar Network 以便探索更多",
"filesListAdditional": {
"one": "+{} 个文件被折叠",
"other": "+{} 个文件被折叠"
},
"messageJumpNotLoaded": "引用的消息没有被加载,无法跳转。",
"postUnlinkRealm": "不关联领域",
"postSlug": "别名",
"postSlugHint": "这个别名可以用于在网页通过 URL 浏览到你的帖子,它应该在同一发布者中是唯一。",
"attachmentOnDevice": "离线",
"attachmentOnCloud": "在线",
"publisherCollabInvitation": "协作邀请",
"publisherCollabInvitationCount": {
"zero": "无邀请",
"one": "{} 个可用邀请",
"other": "{} 个可用邀请"
}
}

File diff suppressed because it is too large Load Diff

BIN
assets/icons/icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 307 KiB

View File

@@ -42,22 +42,62 @@ PODS:
- Flutter
- Firebase/CoreOnly (12.0.0):
- FirebaseCore (~> 12.0.0)
- Firebase/Crashlytics (12.0.0):
- Firebase/CoreOnly
- FirebaseCrashlytics (~> 12.0.0)
- Firebase/Messaging (12.0.0):
- Firebase/CoreOnly
- FirebaseMessaging (~> 12.0.0)
- firebase_analytics (12.0.0):
- firebase_core
- FirebaseAnalytics (= 12.0.0)
- Flutter
- firebase_core (4.0.0):
- Firebase/CoreOnly (= 12.0.0)
- Flutter
- firebase_crashlytics (5.0.0):
- Firebase/Crashlytics (= 12.0.0)
- firebase_core
- Flutter
- firebase_messaging (16.0.0):
- Firebase/Messaging (= 12.0.0)
- firebase_core
- Flutter
- FirebaseAnalytics (12.0.0):
- FirebaseAnalytics/Default (= 12.0.0)
- FirebaseCore (~> 12.0.0)
- FirebaseInstallations (~> 12.0.0)
- GoogleUtilities/AppDelegateSwizzler (~> 8.1)
- GoogleUtilities/MethodSwizzler (~> 8.1)
- GoogleUtilities/Network (~> 8.1)
- "GoogleUtilities/NSData+zlib (~> 8.1)"
- nanopb (~> 3.30910.0)
- FirebaseAnalytics/Default (12.0.0):
- FirebaseCore (~> 12.0.0)
- FirebaseInstallations (~> 12.0.0)
- GoogleAppMeasurement/Default (= 12.0.0)
- GoogleUtilities/AppDelegateSwizzler (~> 8.1)
- GoogleUtilities/MethodSwizzler (~> 8.1)
- GoogleUtilities/Network (~> 8.1)
- "GoogleUtilities/NSData+zlib (~> 8.1)"
- nanopb (~> 3.30910.0)
- FirebaseCore (12.0.0):
- FirebaseCoreInternal (~> 12.0.0)
- GoogleUtilities/Environment (~> 8.1)
- GoogleUtilities/Logger (~> 8.1)
- FirebaseCoreExtension (12.0.0):
- FirebaseCore (~> 12.0.0)
- FirebaseCoreInternal (12.0.0):
- "GoogleUtilities/NSData+zlib (~> 8.1)"
- FirebaseCrashlytics (12.0.0):
- FirebaseCore (~> 12.0.0)
- FirebaseInstallations (~> 12.0.0)
- FirebaseRemoteConfigInterop (~> 12.0.0)
- FirebaseSessions (~> 12.0.0)
- GoogleDataTransport (~> 10.1)
- GoogleUtilities/Environment (~> 8.1)
- nanopb (~> 3.30910.0)
- PromisesObjC (~> 2.4)
- FirebaseInstallations (12.0.0):
- FirebaseCore (~> 12.0.0)
- GoogleUtilities/Environment (~> 8.1)
@@ -72,6 +112,16 @@ PODS:
- GoogleUtilities/Reachability (~> 8.1)
- GoogleUtilities/UserDefaults (~> 8.1)
- nanopb (~> 3.30910.0)
- FirebaseRemoteConfigInterop (12.0.0)
- FirebaseSessions (12.0.0):
- FirebaseCore (~> 12.0.0)
- FirebaseCoreExtension (~> 12.0.0)
- FirebaseInstallations (~> 12.0.0)
- GoogleDataTransport (~> 10.1)
- GoogleUtilities/Environment (~> 8.1)
- GoogleUtilities/UserDefaults (~> 8.1)
- nanopb (~> 3.30910.0)
- PromisesSwift (~> 2.1)
- Flutter (1.0.0)
- flutter_app_update (0.0.1):
- Flutter
@@ -101,6 +151,32 @@ PODS:
- gal (1.0.0):
- Flutter
- FlutterMacOS
- GoogleAdsOnDeviceConversion (2.1.0):
- GoogleUtilities/Logger (~> 8.1)
- GoogleUtilities/Network (~> 8.1)
- nanopb (~> 3.30910.0)
- GoogleAppMeasurement/Core (12.0.0):
- GoogleUtilities/AppDelegateSwizzler (~> 8.1)
- GoogleUtilities/MethodSwizzler (~> 8.1)
- GoogleUtilities/Network (~> 8.1)
- "GoogleUtilities/NSData+zlib (~> 8.1)"
- nanopb (~> 3.30910.0)
- GoogleAppMeasurement/Default (12.0.0):
- GoogleAdsOnDeviceConversion (= 2.1.0)
- GoogleAppMeasurement/Core (= 12.0.0)
- GoogleAppMeasurement/IdentitySupport (= 12.0.0)
- GoogleUtilities/AppDelegateSwizzler (~> 8.1)
- GoogleUtilities/MethodSwizzler (~> 8.1)
- GoogleUtilities/Network (~> 8.1)
- "GoogleUtilities/NSData+zlib (~> 8.1)"
- nanopb (~> 3.30910.0)
- GoogleAppMeasurement/IdentitySupport (12.0.0):
- GoogleAppMeasurement/Core (= 12.0.0)
- GoogleUtilities/AppDelegateSwizzler (~> 8.1)
- GoogleUtilities/MethodSwizzler (~> 8.1)
- GoogleUtilities/Network (~> 8.1)
- "GoogleUtilities/NSData+zlib (~> 8.1)"
- nanopb (~> 3.30910.0)
- GoogleDataTransport (10.1.0):
- nanopb (~> 3.30910.0)
- PromisesObjC (~> 2.4)
@@ -114,6 +190,9 @@ PODS:
- GoogleUtilities/Logger (8.1.0):
- GoogleUtilities/Environment
- GoogleUtilities/Privacy
- GoogleUtilities/MethodSwizzler (8.1.0):
- GoogleUtilities/Logger
- GoogleUtilities/Privacy
- GoogleUtilities/Network (8.1.0):
- GoogleUtilities/Logger
- "GoogleUtilities/NSData+zlib"
@@ -162,9 +241,11 @@ PODS:
- pointer_interceptor_ios (0.0.1):
- Flutter
- PromisesObjC (2.4.0)
- PromisesSwift (2.4.0):
- PromisesObjC (= 2.4.0)
- receive_sharing_intent (1.8.1):
- Flutter
- record_ios (1.0.0):
- record_ios (1.1.0):
- Flutter
- SAMKeychain (1.5.3)
- SDWebImage (5.21.1):
@@ -222,7 +303,9 @@ DEPENDENCIES:
- croppy (from `.symlinks/plugins/croppy/ios`)
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
- file_picker (from `.symlinks/plugins/file_picker/ios`)
- firebase_analytics (from `.symlinks/plugins/firebase_analytics/ios`)
- firebase_core (from `.symlinks/plugins/firebase_core/ios`)
- firebase_crashlytics (from `.symlinks/plugins/firebase_crashlytics/ios`)
- firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`)
- Flutter (from `Flutter`)
- flutter_app_update (from `.symlinks/plugins/flutter_app_update/ios`)
@@ -265,16 +348,24 @@ SPEC REPOS:
- DKImagePickerController
- DKPhotoGallery
- Firebase
- FirebaseAnalytics
- FirebaseCore
- FirebaseCoreExtension
- FirebaseCoreInternal
- FirebaseCrashlytics
- FirebaseInstallations
- FirebaseMessaging
- FirebaseRemoteConfigInterop
- FirebaseSessions
- GoogleAdsOnDeviceConversion
- GoogleAppMeasurement
- GoogleDataTransport
- GoogleUtilities
- Kingfisher
- nanopb
- OrderedSet
- PromisesObjC
- PromisesSwift
- SAMKeychain
- SDWebImage
- sqlite3
@@ -290,8 +381,12 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/device_info_plus/ios"
file_picker:
:path: ".symlinks/plugins/file_picker/ios"
firebase_analytics:
:path: ".symlinks/plugins/firebase_analytics/ios"
firebase_core:
:path: ".symlinks/plugins/firebase_core/ios"
firebase_crashlytics:
:path: ".symlinks/plugins/firebase_crashlytics/ios"
firebase_messaging:
:path: ".symlinks/plugins/firebase_messaging/ios"
Flutter:
@@ -370,12 +465,19 @@ SPEC CHECKSUMS:
DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be
Firebase: 800d487043c0557d9faed71477a38d9aafb08a41
firebase_analytics: cd56fc56f75c1df30a6ff5290cd56e230996a76d
firebase_core: 633e1851ffe1b9ab875f6467a4f574c79cef02e4
firebase_crashlytics: 2c6c1a17900a38081d938330e9f48e60ec5b255d
firebase_messaging: d17feef781edc84ebefe62624fb384358ad96361
FirebaseAnalytics: 6d790cd1b159b4eb61a99948df0934ce505a34f7
FirebaseCore: 055f4ab117d5964158c833f3d5e7ec6d91648d4a
FirebaseCoreExtension: 639afb3de6abd611952be78a794c54a47fa0f361
FirebaseCoreInternal: dedc28e569a4be85f38f3d6af1070a2e12018d55
FirebaseCrashlytics: db75aa0cab8d00f68406fa247c32fe17ade884d7
FirebaseInstallations: d4c7c958f99c8860d7fcece786314ae790e2f988
FirebaseMessaging: af49f8d7c0a3d2a017d9302c80946f45a7777dde
FirebaseRemoteConfigInterop: bfa0ea72ba3dc5af739777296424e46bd6f42613
FirebaseSessions: 4e784acda213108aafef536535cdfc03504acc42
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
flutter_app_update: 816fdb2e30e4832a7c45e3f108d391c42ef040a9
flutter_inappwebview_ios: b89ba3482b96fb25e00c967aae065701b66e9b99
@@ -387,6 +489,8 @@ SPEC CHECKSUMS:
flutter_udid: f7c3884e6ec2951efe4f9de082257fc77c4d15e9
flutter_webrtc: 6f7da106613d52ade777d5b4875a43f48c28b457
gal: baecd024ebfd13c441269ca7404792a7152fde89
GoogleAdsOnDeviceConversion: 2be6297a4f048459e0ae17fad9bfd2844e10cf64
GoogleAppMeasurement: 8f6ab04ad6ae493b53fcf56bd26323fb2f1384f3
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a
@@ -404,8 +508,9 @@ SPEC CHECKSUMS:
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
pointer_interceptor_ios: ec847ef8b0915778bed2b2cef636f4d177fa8eed
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
PromisesSwift: 9d77319bbe72ebf6d872900551f7eeba9bce2851
receive_sharing_intent: 222384f00ffe7e952bbfabaa9e3967cb87e5fe00
record_ios: fee1c924aa4879b882ebca2b4bce6011bcfc3d8b
record_ios: f75fa1d57f840012775c0e93a38a7f3ceea1a374
SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c
SDWebImage: f29024626962457f3470184232766516dee8dfea
share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a

View File

@@ -439,6 +439,7 @@
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
8C0351B03869BBF493808288 /* [CP] Embed Pods Frameworks */,
5E7D6EF29B671AC7EDBA5649 /* [CP] Copy Pods Resources */,
E86CDE9D6464F4F52B910856 /* FlutterFire: "flutterfire upload-crashlytics-symbols" */,
);
buildRules = (
);
@@ -682,6 +683,24 @@
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
E86CDE9D6464F4F52B910856 /* FlutterFire: "flutterfire upload-crashlytics-symbols" */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
name = "FlutterFire: \"flutterfire upload-crashlytics-symbols\"";
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\n#!/bin/bash\nPATH=\"${PATH}:$FLUTTER_ROOT/bin:${PUB_CACHE}/bin:$HOME/.pub-cache/bin\"\n\nif [ -z \"$PODS_ROOT\" ] || [ ! -d \"$PODS_ROOT/FirebaseCrashlytics\" ]; then\n # Cannot use \"BUILD_DIR%/Build/*\" as per Firebase documentation, it points to \"flutter-project/build/ios/*\" path which doesn't have run script\n DERIVED_DATA_PATH=$(echo \"$BUILD_ROOT\" | sed -E 's|(.*DerivedData/[^/]+).*|\\1|')\n PATH_TO_CRASHLYTICS_UPLOAD_SCRIPT=\"${DERIVED_DATA_PATH}/SourcePackages/checkouts/firebase-ios-sdk/Crashlytics/run\"\nelse\n PATH_TO_CRASHLYTICS_UPLOAD_SCRIPT=\"$PODS_ROOT/FirebaseCrashlytics/run\"\nfi\n\n# Command to upload symbols script used to upload symbols to Firebase server\nflutterfire upload-crashlytics-symbols --upload-symbols-script-path=\"$PATH_TO_CRASHLYTICS_UPLOAD_SCRIPT\" --platform=ios --apple-project-path=\"${SRCROOT}\" --env-platform-name=\"${PLATFORM_NAME}\" --env-configuration=\"${CONFIGURATION}\" --env-project-dir=\"${PROJECT_DIR}\" --env-built-products-dir=\"${BUILT_PRODUCTS_DIR}\" --env-dwarf-dsym-folder-path=\"${DWARF_DSYM_FOLDER_PATH}\" --env-dwarf-dsym-file-name=\"${DWARF_DSYM_FILE_NAME}\" --env-infoplist-path=\"${INFOPLIST_PATH}\" --default-config=default\n";
};
E947029FCA058878F9B63890 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;

View File

@@ -1,484 +0,0 @@
import 'package:dio/dio.dart';
import 'package:island/database/drift_db.dart';
import 'package:island/database/message.dart';
import 'package:island/models/chat.dart';
import 'package:island/models/file.dart';
import 'package:island/services/file.dart';
import 'package:island/widgets/alert.dart';
import 'package:uuid/uuid.dart';
class MessageRepository {
final SnChatRoom room;
final SnChatMember identity;
final Dio _apiClient;
final AppDatabase _database;
final Map<String, LocalChatMessage> pendingMessages = {};
final Map<String, Map<int, double>> fileUploadProgress = {};
int? _totalCount;
MessageRepository(this.room, this.identity, this._apiClient, this._database);
Future<LocalChatMessage?> getLastMessages() async {
final dbMessages = await _database.getMessagesForRoom(
room.id,
offset: 0,
limit: 1,
);
if (dbMessages.isEmpty) {
return null;
}
return _database.companionToMessage(dbMessages.first);
}
Future<bool> syncMessages() async {
final lastMessage = await getLastMessages();
if (lastMessage == null) return false;
try {
final resp = await _apiClient.post(
'/sphere/chat/${room.id}/sync',
data: {
'last_sync_timestamp':
lastMessage.toRemoteMessage().updatedAt.millisecondsSinceEpoch,
},
);
final response = MessageSyncResponse.fromJson(resp.data);
for (final change in response.changes) {
switch (change.action) {
case MessageChangeAction.create:
await receiveMessage(change.message!);
break;
case MessageChangeAction.update:
await receiveMessageUpdate(change.message!);
break;
case MessageChangeAction.delete:
await receiveMessageDeletion(change.messageId.toString());
break;
}
}
} catch (err) {
showErrorAlert(err);
}
return true;
}
Future<List<LocalChatMessage>> listMessages({
int offset = 0,
int take = 20,
bool synced = false,
}) async {
try {
// For initial load, fetch latest messages in the background to sync.
if (offset == 0 && !synced) {
// Not awaiting this is intentional, for a quicker UI response.
// The UI should rely on a stream from the database to get updates.
_fetchAndCacheMessages(room.id, offset: 0, take: take).catchError((_) {
// Best effort, errors will be handled by later fetches.
return <LocalChatMessage>[];
});
}
final localMessages = await _getCachedMessages(
room.id,
offset: offset,
take: take,
);
// If local cache has messages, return them. This is the common case for scrolling up.
if (localMessages.isNotEmpty) {
return localMessages;
}
// If local cache is empty, we've probably reached the end of cached history.
// Fetch from remote. This will also be hit on first load if cache is empty.
return await _fetchAndCacheMessages(room.id, offset: offset, take: take);
} catch (e) {
// Final fallback to cache in case of network errors during fetch.
final localMessages = await _getCachedMessages(
room.id,
offset: offset,
take: take,
);
if (localMessages.isNotEmpty) {
return localMessages;
}
rethrow;
}
}
Future<List<LocalChatMessage>> _getCachedMessages(
String roomId, {
int offset = 0,
int take = 20,
}) async {
// Get messages from local database
final dbMessages = await _database.getMessagesForRoom(
roomId,
offset: offset,
limit: take,
);
final dbLocalMessages =
dbMessages.map(_database.companionToMessage).toList();
// Combine with pending messages for the first page
if (offset == 0) {
final pendingForRoom =
pendingMessages.values.where((msg) => msg.roomId == roomId).toList();
final allMessages = [...pendingForRoom, ...dbLocalMessages];
allMessages.sort((a, b) => b.createdAt.compareTo(a.createdAt));
// Remove duplicates by ID, preserving the order
final uniqueMessages = <LocalChatMessage>[];
final seenIds = <String>{};
for (final message in allMessages) {
if (seenIds.add(message.id)) {
uniqueMessages.add(message);
}
}
return uniqueMessages;
}
return dbLocalMessages;
}
Future<List<LocalChatMessage>> _fetchAndCacheMessages(
String roomId, {
int offset = 0,
int take = 20,
}) async {
// Use cached total count if available, otherwise fetch it
if (_totalCount == null) {
final response = await _apiClient.get(
'/sphere/chat/$roomId/messages',
queryParameters: {'offset': 0, 'take': 1},
);
_totalCount = int.parse(response.headers['x-total']?.firstOrNull ?? '0');
}
if (offset >= _totalCount!) {
return [];
}
final response = await _apiClient.get(
'/sphere/chat/$roomId/messages',
queryParameters: {'offset': offset, 'take': take},
);
final List<dynamic> data = response.data;
// Update total count from response headers
_totalCount = int.parse(response.headers['x-total']?.firstOrNull ?? '0');
final messages =
data.map((json) {
final remoteMessage = SnChatMessage.fromJson(json);
return LocalChatMessage.fromRemoteMessage(
remoteMessage,
MessageStatus.sent,
);
}).toList();
for (final message in messages) {
await _database.saveMessage(_database.messageToCompanion(message));
if (message.nonce != null) {
pendingMessages.removeWhere(
(_, pendingMsg) => pendingMsg.nonce == message.nonce,
);
}
}
return messages;
}
Future<LocalChatMessage> sendMessage(
String token,
String baseUrl,
String roomId,
String content,
String nonce, {
required List<UniversalFile> attachments,
Map<String, dynamic>? meta,
SnChatMessage? replyingTo,
SnChatMessage? forwardingTo,
SnChatMessage? editingTo,
Function(LocalChatMessage)? onPending,
Function(String, Map<int, double>)? onProgress,
}) async {
// Generate a unique nonce for this message
final nonce = const Uuid().v4();
// Create a local message with pending status
final mockMessage = SnChatMessage(
id: 'pending_$nonce',
chatRoomId: roomId,
senderId: identity.id,
content: content,
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
nonce: nonce,
sender: identity,
);
final localMessage = LocalChatMessage.fromRemoteMessage(
mockMessage,
MessageStatus.pending,
);
// Store in memory and database
pendingMessages[localMessage.id] = localMessage;
fileUploadProgress[localMessage.id] = {};
await _database.saveMessage(_database.messageToCompanion(localMessage));
onPending?.call(localMessage);
try {
var cloudAttachments = List.empty(growable: true);
// Upload files
for (var idx = 0; idx < attachments.length; idx++) {
final cloudFile =
await putMediaToCloud(
fileData: attachments[idx],
atk: token,
baseUrl: baseUrl,
filename: attachments[idx].data.name ?? 'Post media',
mimetype:
attachments[idx].data.mimeType ??
switch (attachments[idx].type) {
UniversalFileType.image => 'image/unknown',
UniversalFileType.video => 'video/unknown',
UniversalFileType.audio => 'audio/unknown',
UniversalFileType.file => 'application/octet-stream',
},
onProgress: (progress, _) {
fileUploadProgress[localMessage.id]?[idx] = progress;
onProgress?.call(
localMessage.id,
fileUploadProgress[localMessage.id] ?? {},
);
},
).future;
if (cloudFile == null) {
throw ArgumentError('Failed to upload the file...');
}
cloudAttachments.add(cloudFile);
}
// Send to server
final response = await _apiClient.request(
editingTo == null
? '/sphere/chat/$roomId/messages'
: '/sphere/chat/$roomId/messages/${editingTo.id}',
data: {
'content': content,
'attachments_id': cloudAttachments.map((e) => e.id).toList(),
'replied_message_id': replyingTo?.id,
'forwarded_message_id': forwardingTo?.id,
'meta': meta,
'nonce': nonce,
},
options: Options(method: editingTo == null ? 'POST' : 'PATCH'),
);
// Update with server response
final remoteMessage = SnChatMessage.fromJson(response.data);
final updatedMessage = LocalChatMessage.fromRemoteMessage(
remoteMessage,
MessageStatus.sent,
);
// Remove from pending and update in database
pendingMessages.remove(localMessage.id);
await _database.deleteMessage(localMessage.id);
await _database.saveMessage(_database.messageToCompanion(updatedMessage));
return updatedMessage;
} catch (e) {
// Update status to failed
localMessage.status = MessageStatus.failed;
pendingMessages[localMessage.id] = localMessage;
await _database.updateMessageStatus(
localMessage.id,
MessageStatus.failed,
);
rethrow;
}
}
Future<LocalChatMessage> retryMessage(String pendingMessageId) async {
final message = await getMessageById(pendingMessageId);
if (message == null) {
throw Exception('Message not found');
}
// Update status back to pending
message.status = MessageStatus.pending;
pendingMessages[pendingMessageId] = message;
await _database.updateMessageStatus(
pendingMessageId,
MessageStatus.pending,
);
try {
// Send to server
var remoteMessage = message.toRemoteMessage();
final response = await _apiClient.post(
'/sphere/chat/${message.roomId}/messages',
data: {
'content': remoteMessage.content,
'attachments_id': remoteMessage.attachments,
'meta': remoteMessage.meta,
'nonce': message.nonce,
},
);
// Update with server response
remoteMessage = SnChatMessage.fromJson(response.data);
final updatedMessage = LocalChatMessage.fromRemoteMessage(
remoteMessage,
MessageStatus.sent,
);
// Remove from pending and update in database
pendingMessages.remove(pendingMessageId);
await _database.deleteMessage(pendingMessageId);
await _database.saveMessage(_database.messageToCompanion(updatedMessage));
return updatedMessage;
} catch (e) {
// Update status to failed
message.status = MessageStatus.failed;
pendingMessages[pendingMessageId] = message;
await _database.updateMessageStatus(
pendingMessageId,
MessageStatus.failed,
);
rethrow;
}
}
Future<LocalChatMessage> receiveMessage(SnChatMessage remoteMessage) async {
final localMessage = LocalChatMessage.fromRemoteMessage(
remoteMessage,
MessageStatus.sent,
);
if (remoteMessage.nonce != null) {
pendingMessages.removeWhere(
(_, pendingMsg) => pendingMsg.nonce == remoteMessage.nonce,
);
}
await _database.saveMessage(_database.messageToCompanion(localMessage));
return localMessage;
}
Future<LocalChatMessage> receiveMessageUpdate(
SnChatMessage remoteMessage,
) async {
final localMessage = LocalChatMessage.fromRemoteMessage(
remoteMessage,
MessageStatus.sent,
);
await _database.updateMessage(_database.messageToCompanion(localMessage));
return localMessage;
}
Future<void> receiveMessageDeletion(String messageId) async {
// Remove from pending messages if exists
pendingMessages.remove(messageId);
// Delete from local database
await _database.deleteMessage(messageId);
}
Future<LocalChatMessage> updateMessage(
String messageId,
String content, {
List<SnCloudFile>? attachments,
Map<String, dynamic>? meta,
}) async {
final message = pendingMessages[messageId];
if (message != null) {
// Update pending message
final rmMessage = message.toRemoteMessage();
final updatedRemoteMessage = rmMessage.copyWith(
content: content,
meta: meta ?? rmMessage.meta,
);
final updatedLocalMessage = LocalChatMessage.fromRemoteMessage(
updatedRemoteMessage,
MessageStatus.pending,
);
pendingMessages[messageId] = updatedLocalMessage;
await _database.updateMessage(
_database.messageToCompanion(updatedLocalMessage),
);
return message;
}
try {
// Update on server
final response = await _apiClient.put(
'/sphere/chat/${room.id}/messages/$messageId',
data: {'content': content, 'attachments': attachments, 'meta': meta},
);
// Update local copy
final remoteMessage = SnChatMessage.fromJson(response.data);
final updatedMessage = LocalChatMessage.fromRemoteMessage(
remoteMessage,
MessageStatus.sent,
);
await _database.updateMessage(
_database.messageToCompanion(updatedMessage),
);
return updatedMessage;
} catch (e) {
rethrow;
}
}
Future<void> deleteMessage(String messageId) async {
try {
await _apiClient.delete('/sphere/chat/${room.id}/messages/$messageId');
pendingMessages.remove(messageId);
await _database.deleteMessage(messageId);
} catch (e) {
rethrow;
}
}
Future<LocalChatMessage?> getMessageById(String messageId) async {
try {
// Attempt to get the message from the local database
final localMessage =
await (_database.select(_database.chatMessages)
..where((tbl) => tbl.id.equals(messageId))).getSingleOrNull();
if (localMessage != null) {
return _database.companionToMessage(localMessage);
}
// If not found locally, fetch from the server
final response = await _apiClient.get(
'/sphere/chat/${room.id}/messages/$messageId',
);
final remoteMessage = SnChatMessage.fromJson(response.data);
final message = LocalChatMessage.fromRemoteMessage(
remoteMessage,
MessageStatus.sent,
);
// Save the fetched message to the local database
await _database.saveMessage(_database.messageToCompanion(message));
return message;
} catch (e) {
if (e is DioException) return null;
// Handle errors
rethrow;
}
}
}

View File

@@ -61,10 +61,8 @@ class DefaultFirebaseOptions {
messagingSenderId: '961776991058',
projectId: 'solian-0x001',
storageBucket: 'solian-0x001.firebasestorage.app',
androidClientId:
'961776991058-r4iv9qoio57ul7utbfpgfrda2etvtch8.apps.googleusercontent.com',
iosClientId:
'961776991058-stt7et4qvn3cpscl4r61gl1hnlatqkig.apps.googleusercontent.com',
androidClientId: '961776991058-r4iv9qoio57ul7utbfpgfrda2etvtch8.apps.googleusercontent.com',
iosClientId: '961776991058-stt7et4qvn3cpscl4r61gl1hnlatqkig.apps.googleusercontent.com',
iosBundleId: 'dev.solsynth.solian',
);
@@ -74,10 +72,8 @@ class DefaultFirebaseOptions {
messagingSenderId: '961776991058',
projectId: 'solian-0x001',
storageBucket: 'solian-0x001.firebasestorage.app',
androidClientId:
'961776991058-r4iv9qoio57ul7utbfpgfrda2etvtch8.apps.googleusercontent.com',
iosClientId:
'961776991058-stt7et4qvn3cpscl4r61gl1hnlatqkig.apps.googleusercontent.com',
androidClientId: '961776991058-r4iv9qoio57ul7utbfpgfrda2etvtch8.apps.googleusercontent.com',
iosClientId: '961776991058-stt7et4qvn3cpscl4r61gl1hnlatqkig.apps.googleusercontent.com',
iosBundleId: 'dev.solsynth.solian',
);
@@ -90,4 +86,5 @@ class DefaultFirebaseOptions {
storageBucket: 'solian-0x001.firebasestorage.app',
measurementId: 'G-JD1YEG9D6F',
);
}

View File

@@ -4,6 +4,7 @@ import 'dart:io';
import 'package:croppy/croppy.dart';
import 'package:easy_localization/easy_localization.dart' hide TextDirection;
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
@@ -61,6 +62,17 @@ void main() async {
FirebaseMessaging.onBackgroundMessage(
_firebaseMessagingBackgroundHandler,
);
// Although previous if case checked this. Still check is web or not
// Otherwise the web platform will broke due to there is no Platform api on the web
// Skip crashlytics setup on debug mode to prevent unexpected report to firebase
if ((kIsWeb || !Platform.isWindows) && !kDebugMode) {
FlutterError.onError =
FirebaseCrashlytics.instance.recordFlutterFatalError;
PlatformDispatcher.instance.onError = (error, stack) {
FirebaseCrashlytics.instance.recordError(error, stack, fatal: true);
return true;
};
}
}
log("[SplashScreen] Firebase is ready!");

View File

@@ -1,9 +1,10 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:island/models/auth.dart';
import 'package:island/models/file.dart';
import 'package:island/models/wallet.dart';
part 'user.freezed.dart';
part 'user.g.dart';
part 'account.freezed.dart';
part 'account.g.dart';
@freezed
sealed class SnAccount with _$SnAccount {
@@ -174,3 +175,36 @@ sealed class SnVerificationMark with _$SnVerificationMark {
factory SnVerificationMark.fromJson(Map<String, dynamic> json) =>
_$SnVerificationMarkFromJson(json);
}
@freezed
sealed class SnAuthDevice with _$SnAuthDevice {
const factory SnAuthDevice({
required String id,
required String deviceId,
required String deviceName,
required String? deviceLabel,
required String accountId,
required int platform,
@Default(false) bool isCurrent,
}) = _SnAuthDevice;
factory SnAuthDevice.fromJson(Map<String, dynamic> json) =>
_$SnAuthDeviceFromJson(json);
}
@freezed
sealed class SnAuthDeviceWithChallenge with _$SnAuthDeviceWithChallenge {
const factory SnAuthDeviceWithChallenge({
required String id,
required String deviceId,
required String deviceName,
required String? deviceLabel,
required String accountId,
required int platform,
required List<SnAuthChallenge> challenges,
@Default(false) bool isCurrent,
}) = _SnAuthDeviceWithChallengee;
factory SnAuthDeviceWithChallenge.fromJson(Map<String, dynamic> json) =>
_$SnAuthDeviceWithChallengeFromJson(json);
}

View File

@@ -3,7 +3,7 @@
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'user.dart';
part of 'account.dart';
// **************************************************************************
// FreezedGenerator
@@ -2452,6 +2452,572 @@ as String?,
}
}
/// @nodoc
mixin _$SnAuthDevice {
String get id; String get deviceId; String get deviceName; String? get deviceLabel; String get accountId; int get platform; 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&&(identical(other.id, id) || other.id == id)&&(identical(other.deviceId, deviceId) || other.deviceId == deviceId)&&(identical(other.deviceName, deviceName) || other.deviceName == deviceName)&&(identical(other.deviceLabel, deviceLabel) || other.deviceLabel == deviceLabel)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.platform, platform) || other.platform == platform)&&(identical(other.isCurrent, isCurrent) || other.isCurrent == isCurrent));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,id,deviceId,deviceName,deviceLabel,accountId,platform,isCurrent);
@override
String toString() {
return 'SnAuthDevice(id: $id, deviceId: $deviceId, deviceName: $deviceName, deviceLabel: $deviceLabel, accountId: $accountId, platform: $platform, isCurrent: $isCurrent)';
}
}
/// @nodoc
abstract mixin class $SnAuthDeviceCopyWith<$Res> {
factory $SnAuthDeviceCopyWith(SnAuthDevice value, $Res Function(SnAuthDevice) _then) = _$SnAuthDeviceCopyWithImpl;
@useResult
$Res call({
String id, String deviceId, String deviceName, String? deviceLabel, String accountId, int platform, 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? id = null,Object? deviceId = null,Object? deviceName = null,Object? deviceLabel = freezed,Object? accountId = null,Object? platform = null,Object? isCurrent = null,}) {
return _then(_self.copyWith(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,deviceId: null == deviceId ? _self.deviceId : deviceId // ignore: cast_nullable_to_non_nullable
as String,deviceName: null == deviceName ? _self.deviceName : deviceName // ignore: cast_nullable_to_non_nullable
as String,deviceLabel: freezed == deviceLabel ? _self.deviceLabel : deviceLabel // ignore: cast_nullable_to_non_nullable
as String?,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable
as String,platform: null == platform ? _self.platform : platform // ignore: cast_nullable_to_non_nullable
as int,isCurrent: null == isCurrent ? _self.isCurrent : isCurrent // ignore: cast_nullable_to_non_nullable
as bool,
));
}
}
/// Adds pattern-matching-related methods to [SnAuthDevice].
extension SnAuthDevicePatterns on SnAuthDevice {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _SnAuthDevice value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _SnAuthDevice() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _SnAuthDevice value) $default,){
final _that = this;
switch (_that) {
case _SnAuthDevice():
return $default(_that);}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _SnAuthDevice value)? $default,){
final _that = this;
switch (_that) {
case _SnAuthDevice() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, String deviceId, String deviceName, String? deviceLabel, String accountId, int platform, bool isCurrent)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _SnAuthDevice() when $default != null:
return $default(_that.id,_that.deviceId,_that.deviceName,_that.deviceLabel,_that.accountId,_that.platform,_that.isCurrent);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, String deviceId, String deviceName, String? deviceLabel, String accountId, int platform, bool isCurrent) $default,) {final _that = this;
switch (_that) {
case _SnAuthDevice():
return $default(_that.id,_that.deviceId,_that.deviceName,_that.deviceLabel,_that.accountId,_that.platform,_that.isCurrent);}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, String deviceId, String deviceName, String? deviceLabel, String accountId, int platform, bool isCurrent)? $default,) {final _that = this;
switch (_that) {
case _SnAuthDevice() when $default != null:
return $default(_that.id,_that.deviceId,_that.deviceName,_that.deviceLabel,_that.accountId,_that.platform,_that.isCurrent);case _:
return null;
}
}
}
/// @nodoc
@JsonSerializable()
class _SnAuthDevice implements SnAuthDevice {
const _SnAuthDevice({required this.id, required this.deviceId, required this.deviceName, required this.deviceLabel, required this.accountId, required this.platform, this.isCurrent = false});
factory _SnAuthDevice.fromJson(Map<String, dynamic> json) => _$SnAuthDeviceFromJson(json);
@override final String id;
@override final String deviceId;
@override final String deviceName;
@override final String? deviceLabel;
@override final String accountId;
@override final int platform;
@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&&(identical(other.id, id) || other.id == id)&&(identical(other.deviceId, deviceId) || other.deviceId == deviceId)&&(identical(other.deviceName, deviceName) || other.deviceName == deviceName)&&(identical(other.deviceLabel, deviceLabel) || other.deviceLabel == deviceLabel)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.platform, platform) || other.platform == platform)&&(identical(other.isCurrent, isCurrent) || other.isCurrent == isCurrent));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,id,deviceId,deviceName,deviceLabel,accountId,platform,isCurrent);
@override
String toString() {
return 'SnAuthDevice(id: $id, deviceId: $deviceId, deviceName: $deviceName, deviceLabel: $deviceLabel, accountId: $accountId, platform: $platform, isCurrent: $isCurrent)';
}
}
/// @nodoc
abstract mixin class _$SnAuthDeviceCopyWith<$Res> implements $SnAuthDeviceCopyWith<$Res> {
factory _$SnAuthDeviceCopyWith(_SnAuthDevice value, $Res Function(_SnAuthDevice) _then) = __$SnAuthDeviceCopyWithImpl;
@override @useResult
$Res call({
String id, String deviceId, String deviceName, String? deviceLabel, String accountId, int platform, 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? id = null,Object? deviceId = null,Object? deviceName = null,Object? deviceLabel = freezed,Object? accountId = null,Object? platform = null,Object? isCurrent = null,}) {
return _then(_SnAuthDevice(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,deviceId: null == deviceId ? _self.deviceId : deviceId // ignore: cast_nullable_to_non_nullable
as String,deviceName: null == deviceName ? _self.deviceName : deviceName // ignore: cast_nullable_to_non_nullable
as String,deviceLabel: freezed == deviceLabel ? _self.deviceLabel : deviceLabel // ignore: cast_nullable_to_non_nullable
as String?,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable
as String,platform: null == platform ? _self.platform : platform // ignore: cast_nullable_to_non_nullable
as int,isCurrent: null == isCurrent ? _self.isCurrent : isCurrent // ignore: cast_nullable_to_non_nullable
as bool,
));
}
}
SnAuthDeviceWithChallenge _$SnAuthDeviceWithChallengeFromJson(
Map<String, dynamic> json
) {
return _SnAuthDeviceWithChallengee.fromJson(
json
);
}
/// @nodoc
mixin _$SnAuthDeviceWithChallenge {
String get id; String get deviceId; String get deviceName; String? get deviceLabel; String get accountId; int get platform; List<SnAuthChallenge> get challenges; bool get isCurrent;
/// Create a copy of SnAuthDeviceWithChallenge
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$SnAuthDeviceWithChallengeCopyWith<SnAuthDeviceWithChallenge> get copyWith => _$SnAuthDeviceWithChallengeCopyWithImpl<SnAuthDeviceWithChallenge>(this as SnAuthDeviceWithChallenge, _$identity);
/// Serializes this SnAuthDeviceWithChallenge to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnAuthDeviceWithChallenge&&(identical(other.id, id) || other.id == id)&&(identical(other.deviceId, deviceId) || other.deviceId == deviceId)&&(identical(other.deviceName, deviceName) || other.deviceName == deviceName)&&(identical(other.deviceLabel, deviceLabel) || other.deviceLabel == deviceLabel)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.platform, platform) || other.platform == platform)&&const DeepCollectionEquality().equals(other.challenges, challenges)&&(identical(other.isCurrent, isCurrent) || other.isCurrent == isCurrent));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,id,deviceId,deviceName,deviceLabel,accountId,platform,const DeepCollectionEquality().hash(challenges),isCurrent);
@override
String toString() {
return 'SnAuthDeviceWithChallenge(id: $id, deviceId: $deviceId, deviceName: $deviceName, deviceLabel: $deviceLabel, accountId: $accountId, platform: $platform, challenges: $challenges, isCurrent: $isCurrent)';
}
}
/// @nodoc
abstract mixin class $SnAuthDeviceWithChallengeCopyWith<$Res> {
factory $SnAuthDeviceWithChallengeCopyWith(SnAuthDeviceWithChallenge value, $Res Function(SnAuthDeviceWithChallenge) _then) = _$SnAuthDeviceWithChallengeCopyWithImpl;
@useResult
$Res call({
String id, String deviceId, String deviceName, String? deviceLabel, String accountId, int platform, List<SnAuthChallenge> challenges, bool isCurrent
});
}
/// @nodoc
class _$SnAuthDeviceWithChallengeCopyWithImpl<$Res>
implements $SnAuthDeviceWithChallengeCopyWith<$Res> {
_$SnAuthDeviceWithChallengeCopyWithImpl(this._self, this._then);
final SnAuthDeviceWithChallenge _self;
final $Res Function(SnAuthDeviceWithChallenge) _then;
/// Create a copy of SnAuthDeviceWithChallenge
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? deviceId = null,Object? deviceName = null,Object? deviceLabel = freezed,Object? accountId = null,Object? platform = null,Object? challenges = null,Object? isCurrent = null,}) {
return _then(_self.copyWith(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,deviceId: null == deviceId ? _self.deviceId : deviceId // ignore: cast_nullable_to_non_nullable
as String,deviceName: null == deviceName ? _self.deviceName : deviceName // ignore: cast_nullable_to_non_nullable
as String,deviceLabel: freezed == deviceLabel ? _self.deviceLabel : deviceLabel // ignore: cast_nullable_to_non_nullable
as String?,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable
as String,platform: null == platform ? _self.platform : platform // ignore: cast_nullable_to_non_nullable
as int,challenges: null == challenges ? _self.challenges : challenges // ignore: cast_nullable_to_non_nullable
as List<SnAuthChallenge>,isCurrent: null == isCurrent ? _self.isCurrent : isCurrent // ignore: cast_nullable_to_non_nullable
as bool,
));
}
}
/// Adds pattern-matching-related methods to [SnAuthDeviceWithChallenge].
extension SnAuthDeviceWithChallengePatterns on SnAuthDeviceWithChallenge {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _SnAuthDeviceWithChallengee value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _SnAuthDeviceWithChallengee() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _SnAuthDeviceWithChallengee value) $default,){
final _that = this;
switch (_that) {
case _SnAuthDeviceWithChallengee():
return $default(_that);}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _SnAuthDeviceWithChallengee value)? $default,){
final _that = this;
switch (_that) {
case _SnAuthDeviceWithChallengee() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, String deviceId, String deviceName, String? deviceLabel, String accountId, int platform, List<SnAuthChallenge> challenges, bool isCurrent)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _SnAuthDeviceWithChallengee() when $default != null:
return $default(_that.id,_that.deviceId,_that.deviceName,_that.deviceLabel,_that.accountId,_that.platform,_that.challenges,_that.isCurrent);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, String deviceId, String deviceName, String? deviceLabel, String accountId, int platform, List<SnAuthChallenge> challenges, bool isCurrent) $default,) {final _that = this;
switch (_that) {
case _SnAuthDeviceWithChallengee():
return $default(_that.id,_that.deviceId,_that.deviceName,_that.deviceLabel,_that.accountId,_that.platform,_that.challenges,_that.isCurrent);}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, String deviceId, String deviceName, String? deviceLabel, String accountId, int platform, List<SnAuthChallenge> challenges, bool isCurrent)? $default,) {final _that = this;
switch (_that) {
case _SnAuthDeviceWithChallengee() when $default != null:
return $default(_that.id,_that.deviceId,_that.deviceName,_that.deviceLabel,_that.accountId,_that.platform,_that.challenges,_that.isCurrent);case _:
return null;
}
}
}
/// @nodoc
@JsonSerializable()
class _SnAuthDeviceWithChallengee implements SnAuthDeviceWithChallenge {
const _SnAuthDeviceWithChallengee({required this.id, required this.deviceId, required this.deviceName, required this.deviceLabel, required this.accountId, required this.platform, required final List<SnAuthChallenge> challenges, this.isCurrent = false}): _challenges = challenges;
factory _SnAuthDeviceWithChallengee.fromJson(Map<String, dynamic> json) => _$SnAuthDeviceWithChallengeeFromJson(json);
@override final String id;
@override final String deviceId;
@override final String deviceName;
@override final String? deviceLabel;
@override final String accountId;
@override final int platform;
final List<SnAuthChallenge> _challenges;
@override List<SnAuthChallenge> get challenges {
if (_challenges is EqualUnmodifiableListView) return _challenges;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_challenges);
}
@override@JsonKey() final bool isCurrent;
/// Create a copy of SnAuthDeviceWithChallenge
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$SnAuthDeviceWithChallengeeCopyWith<_SnAuthDeviceWithChallengee> get copyWith => __$SnAuthDeviceWithChallengeeCopyWithImpl<_SnAuthDeviceWithChallengee>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$SnAuthDeviceWithChallengeeToJson(this, );
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnAuthDeviceWithChallengee&&(identical(other.id, id) || other.id == id)&&(identical(other.deviceId, deviceId) || other.deviceId == deviceId)&&(identical(other.deviceName, deviceName) || other.deviceName == deviceName)&&(identical(other.deviceLabel, deviceLabel) || other.deviceLabel == deviceLabel)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.platform, platform) || other.platform == platform)&&const DeepCollectionEquality().equals(other._challenges, _challenges)&&(identical(other.isCurrent, isCurrent) || other.isCurrent == isCurrent));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,id,deviceId,deviceName,deviceLabel,accountId,platform,const DeepCollectionEquality().hash(_challenges),isCurrent);
@override
String toString() {
return 'SnAuthDeviceWithChallenge(id: $id, deviceId: $deviceId, deviceName: $deviceName, deviceLabel: $deviceLabel, accountId: $accountId, platform: $platform, challenges: $challenges, isCurrent: $isCurrent)';
}
}
/// @nodoc
abstract mixin class _$SnAuthDeviceWithChallengeeCopyWith<$Res> implements $SnAuthDeviceWithChallengeCopyWith<$Res> {
factory _$SnAuthDeviceWithChallengeeCopyWith(_SnAuthDeviceWithChallengee value, $Res Function(_SnAuthDeviceWithChallengee) _then) = __$SnAuthDeviceWithChallengeeCopyWithImpl;
@override @useResult
$Res call({
String id, String deviceId, String deviceName, String? deviceLabel, String accountId, int platform, List<SnAuthChallenge> challenges, bool isCurrent
});
}
/// @nodoc
class __$SnAuthDeviceWithChallengeeCopyWithImpl<$Res>
implements _$SnAuthDeviceWithChallengeeCopyWith<$Res> {
__$SnAuthDeviceWithChallengeeCopyWithImpl(this._self, this._then);
final _SnAuthDeviceWithChallengee _self;
final $Res Function(_SnAuthDeviceWithChallengee) _then;
/// Create a copy of SnAuthDeviceWithChallenge
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? deviceId = null,Object? deviceName = null,Object? deviceLabel = freezed,Object? accountId = null,Object? platform = null,Object? challenges = null,Object? isCurrent = null,}) {
return _then(_SnAuthDeviceWithChallengee(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,deviceId: null == deviceId ? _self.deviceId : deviceId // ignore: cast_nullable_to_non_nullable
as String,deviceName: null == deviceName ? _self.deviceName : deviceName // ignore: cast_nullable_to_non_nullable
as String,deviceLabel: freezed == deviceLabel ? _self.deviceLabel : deviceLabel // ignore: cast_nullable_to_non_nullable
as String?,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable
as String,platform: null == platform ? _self.platform : platform // ignore: cast_nullable_to_non_nullable
as int,challenges: null == challenges ? _self._challenges : challenges // ignore: cast_nullable_to_non_nullable
as List<SnAuthChallenge>,isCurrent: null == isCurrent ? _self.isCurrent : isCurrent // ignore: cast_nullable_to_non_nullable
as bool,
));
}
}
// dart format on

View File

@@ -1,6 +1,6 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'user.dart';
part of 'account.dart';
// **************************************************************************
// JsonSerializableGenerator
@@ -297,3 +297,54 @@ Map<String, dynamic> _$SnVerificationMarkToJson(_SnVerificationMark instance) =>
'description': instance.description,
'verified_by': instance.verifiedBy,
};
_SnAuthDevice _$SnAuthDeviceFromJson(Map<String, dynamic> json) =>
_SnAuthDevice(
id: json['id'] as String,
deviceId: json['device_id'] as String,
deviceName: json['device_name'] as String,
deviceLabel: json['device_label'] as String?,
accountId: json['account_id'] as String,
platform: (json['platform'] as num).toInt(),
isCurrent: json['is_current'] as bool? ?? false,
);
Map<String, dynamic> _$SnAuthDeviceToJson(_SnAuthDevice instance) =>
<String, dynamic>{
'id': instance.id,
'device_id': instance.deviceId,
'device_name': instance.deviceName,
'device_label': instance.deviceLabel,
'account_id': instance.accountId,
'platform': instance.platform,
'is_current': instance.isCurrent,
};
_SnAuthDeviceWithChallengee _$SnAuthDeviceWithChallengeeFromJson(
Map<String, dynamic> json,
) => _SnAuthDeviceWithChallengee(
id: json['id'] as String,
deviceId: json['device_id'] as String,
deviceName: json['device_name'] as String,
deviceLabel: json['device_label'] as String?,
accountId: json['account_id'] as String,
platform: (json['platform'] as num).toInt(),
challenges:
(json['challenges'] as List<dynamic>)
.map((e) => SnAuthChallenge.fromJson(e as Map<String, dynamic>))
.toList(),
isCurrent: json['is_current'] as bool? ?? false,
);
Map<String, dynamic> _$SnAuthDeviceWithChallengeeToJson(
_SnAuthDeviceWithChallengee instance,
) => <String, dynamic>{
'id': instance.id,
'device_id': instance.deviceId,
'device_name': instance.deviceName,
'device_label': instance.deviceLabel,
'account_id': instance.accountId,
'platform': instance.platform,
'challenges': instance.challenges.map((e) => e.toJson()).toList(),
'is_current': instance.isCurrent,
};

View File

@@ -1,5 +1,5 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:island/models/user.dart';
import 'package:island/models/account.dart';
part 'activity.freezed.dart';
part 'activity.g.dart';

View File

@@ -19,14 +19,12 @@ sealed class SnAuthChallenge with _$SnAuthChallenge {
required int stepRemain,
required int stepTotal,
required int failedAttempts,
required int platform,
required int type,
required List<String> blacklistFactors,
required List<dynamic> audiences,
required List<dynamic> scopes,
required String ipAddress,
required String userAgent,
required String deviceId,
required String? nonce,
required String? location,
required String accountId,
@@ -76,22 +74,6 @@ sealed class SnAuthFactor with _$SnAuthFactor {
_$SnAuthFactorFromJson(json);
}
@freezed
sealed class SnAuthDevice with _$SnAuthDevice {
const factory SnAuthDevice({
required dynamic label,
required String userAgent,
required String deviceId,
required int platform,
required List<SnAuthSession> sessions,
// Not from backend, used for UI
@Default(false) bool isCurrent,
}) = _SnAuthDevice;
factory SnAuthDevice.fromJson(Map<String, dynamic> json) =>
_$SnAuthDeviceFromJson(json);
}
@freezed
sealed class SnAccountConnection with _$SnAccountConnection {
const factory SnAccountConnection({

View File

@@ -272,7 +272,7 @@ as String,
/// @nodoc
mixin _$SnAuthChallenge {
String get id; DateTime get expiredAt; int get stepRemain; int get stepTotal; int get failedAttempts; int get platform; int get type; List<String> get blacklistFactors; List<dynamic> get audiences; List<dynamic> get scopes; String get ipAddress; String get userAgent; String get deviceId; String? get nonce; String? get location; String get accountId; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt;
String get id; DateTime get expiredAt; int get stepRemain; int get stepTotal; int get failedAttempts; int get type; List<String> get blacklistFactors; List<dynamic> get audiences; List<dynamic> get scopes; String get ipAddress; String get userAgent; String? get nonce; String? get location; String get accountId; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt;
/// Create a copy of SnAuthChallenge
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@@ -285,16 +285,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)&&(identical(other.failedAttempts, failedAttempts) || other.failedAttempts == failedAttempts)&&(identical(other.platform, platform) || other.platform == platform)&&(identical(other.type, type) || other.type == type)&&const DeepCollectionEquality().equals(other.blacklistFactors, blacklistFactors)&&const DeepCollectionEquality().equals(other.audiences, audiences)&&const DeepCollectionEquality().equals(other.scopes, scopes)&&(identical(other.ipAddress, ipAddress) || other.ipAddress == ipAddress)&&(identical(other.userAgent, userAgent) || other.userAgent == userAgent)&&(identical(other.deviceId, deviceId) || other.deviceId == deviceId)&&(identical(other.nonce, nonce) || other.nonce == nonce)&&(identical(other.location, location) || other.location == location)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnAuthChallenge&&(identical(other.id, id) || other.id == id)&&(identical(other.expiredAt, expiredAt) || other.expiredAt == expiredAt)&&(identical(other.stepRemain, stepRemain) || other.stepRemain == stepRemain)&&(identical(other.stepTotal, stepTotal) || other.stepTotal == stepTotal)&&(identical(other.failedAttempts, failedAttempts) || other.failedAttempts == failedAttempts)&&(identical(other.type, type) || other.type == type)&&const DeepCollectionEquality().equals(other.blacklistFactors, blacklistFactors)&&const DeepCollectionEquality().equals(other.audiences, audiences)&&const DeepCollectionEquality().equals(other.scopes, scopes)&&(identical(other.ipAddress, ipAddress) || other.ipAddress == ipAddress)&&(identical(other.userAgent, userAgent) || other.userAgent == userAgent)&&(identical(other.nonce, nonce) || other.nonce == nonce)&&(identical(other.location, location) || other.location == location)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hashAll([runtimeType,id,expiredAt,stepRemain,stepTotal,failedAttempts,platform,type,const DeepCollectionEquality().hash(blacklistFactors),const DeepCollectionEquality().hash(audiences),const DeepCollectionEquality().hash(scopes),ipAddress,userAgent,deviceId,nonce,location,accountId,createdAt,updatedAt,deletedAt]);
int get hashCode => Object.hash(runtimeType,id,expiredAt,stepRemain,stepTotal,failedAttempts,type,const DeepCollectionEquality().hash(blacklistFactors),const DeepCollectionEquality().hash(audiences),const DeepCollectionEquality().hash(scopes),ipAddress,userAgent,nonce,location,accountId,createdAt,updatedAt,deletedAt);
@override
String toString() {
return 'SnAuthChallenge(id: $id, expiredAt: $expiredAt, stepRemain: $stepRemain, stepTotal: $stepTotal, failedAttempts: $failedAttempts, platform: $platform, type: $type, blacklistFactors: $blacklistFactors, audiences: $audiences, scopes: $scopes, ipAddress: $ipAddress, userAgent: $userAgent, deviceId: $deviceId, nonce: $nonce, location: $location, accountId: $accountId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
return 'SnAuthChallenge(id: $id, expiredAt: $expiredAt, stepRemain: $stepRemain, stepTotal: $stepTotal, failedAttempts: $failedAttempts, type: $type, blacklistFactors: $blacklistFactors, audiences: $audiences, scopes: $scopes, ipAddress: $ipAddress, userAgent: $userAgent, nonce: $nonce, location: $location, accountId: $accountId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
}
@@ -305,7 +305,7 @@ abstract mixin class $SnAuthChallengeCopyWith<$Res> {
factory $SnAuthChallengeCopyWith(SnAuthChallenge value, $Res Function(SnAuthChallenge) _then) = _$SnAuthChallengeCopyWithImpl;
@useResult
$Res call({
String id, DateTime expiredAt, int stepRemain, int stepTotal, int failedAttempts, int platform, int type, List<String> blacklistFactors, List<dynamic> audiences, List<dynamic> scopes, String ipAddress, String userAgent, String deviceId, String? nonce, String? location, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
String id, DateTime expiredAt, int stepRemain, int stepTotal, int failedAttempts, int type, List<String> blacklistFactors, List<dynamic> audiences, List<dynamic> scopes, String ipAddress, String userAgent, String? nonce, String? location, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
});
@@ -322,21 +322,19 @@ class _$SnAuthChallengeCopyWithImpl<$Res>
/// Create a copy of SnAuthChallenge
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? expiredAt = null,Object? stepRemain = null,Object? stepTotal = null,Object? failedAttempts = null,Object? platform = null,Object? type = null,Object? blacklistFactors = null,Object? audiences = null,Object? scopes = null,Object? ipAddress = null,Object? userAgent = null,Object? deviceId = null,Object? nonce = freezed,Object? location = freezed,Object? accountId = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? expiredAt = null,Object? stepRemain = null,Object? stepTotal = null,Object? failedAttempts = null,Object? type = null,Object? blacklistFactors = null,Object? audiences = null,Object? scopes = null,Object? ipAddress = null,Object? userAgent = null,Object? nonce = freezed,Object? location = freezed,Object? accountId = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
return _then(_self.copyWith(
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<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: 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
@@ -425,10 +423,10 @@ return $default(_that);case _:
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, DateTime expiredAt, int stepRemain, int stepTotal, int failedAttempts, int platform, int type, List<String> blacklistFactors, List<dynamic> audiences, List<dynamic> scopes, String ipAddress, String userAgent, String deviceId, String? nonce, String? location, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt)? $default,{required TResult orElse(),}) {final _that = this;
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, DateTime expiredAt, int stepRemain, int stepTotal, int failedAttempts, int type, List<String> blacklistFactors, List<dynamic> audiences, List<dynamic> scopes, String ipAddress, String userAgent, String? nonce, String? location, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _SnAuthChallenge() when $default != null:
return $default(_that.id,_that.expiredAt,_that.stepRemain,_that.stepTotal,_that.failedAttempts,_that.platform,_that.type,_that.blacklistFactors,_that.audiences,_that.scopes,_that.ipAddress,_that.userAgent,_that.deviceId,_that.nonce,_that.location,_that.accountId,_that.createdAt,_that.updatedAt,_that.deletedAt);case _:
return $default(_that.id,_that.expiredAt,_that.stepRemain,_that.stepTotal,_that.failedAttempts,_that.type,_that.blacklistFactors,_that.audiences,_that.scopes,_that.ipAddress,_that.userAgent,_that.nonce,_that.location,_that.accountId,_that.createdAt,_that.updatedAt,_that.deletedAt);case _:
return orElse();
}
@@ -446,10 +444,10 @@ return $default(_that.id,_that.expiredAt,_that.stepRemain,_that.stepTotal,_that.
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, DateTime expiredAt, int stepRemain, int stepTotal, int failedAttempts, int platform, int type, List<String> blacklistFactors, List<dynamic> audiences, List<dynamic> scopes, String ipAddress, String userAgent, String deviceId, String? nonce, String? location, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt) $default,) {final _that = this;
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, DateTime expiredAt, int stepRemain, int stepTotal, int failedAttempts, int type, List<String> blacklistFactors, List<dynamic> audiences, List<dynamic> scopes, String ipAddress, String userAgent, String? nonce, String? location, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt) $default,) {final _that = this;
switch (_that) {
case _SnAuthChallenge():
return $default(_that.id,_that.expiredAt,_that.stepRemain,_that.stepTotal,_that.failedAttempts,_that.platform,_that.type,_that.blacklistFactors,_that.audiences,_that.scopes,_that.ipAddress,_that.userAgent,_that.deviceId,_that.nonce,_that.location,_that.accountId,_that.createdAt,_that.updatedAt,_that.deletedAt);}
return $default(_that.id,_that.expiredAt,_that.stepRemain,_that.stepTotal,_that.failedAttempts,_that.type,_that.blacklistFactors,_that.audiences,_that.scopes,_that.ipAddress,_that.userAgent,_that.nonce,_that.location,_that.accountId,_that.createdAt,_that.updatedAt,_that.deletedAt);}
}
/// A variant of `when` that fallback to returning `null`
///
@@ -463,10 +461,10 @@ return $default(_that.id,_that.expiredAt,_that.stepRemain,_that.stepTotal,_that.
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, DateTime expiredAt, int stepRemain, int stepTotal, int failedAttempts, int platform, int type, List<String> blacklistFactors, List<dynamic> audiences, List<dynamic> scopes, String ipAddress, String userAgent, String deviceId, String? nonce, String? location, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt)? $default,) {final _that = this;
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, DateTime expiredAt, int stepRemain, int stepTotal, int failedAttempts, int type, List<String> blacklistFactors, List<dynamic> audiences, List<dynamic> scopes, String ipAddress, String userAgent, String? nonce, String? location, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt)? $default,) {final _that = this;
switch (_that) {
case _SnAuthChallenge() when $default != null:
return $default(_that.id,_that.expiredAt,_that.stepRemain,_that.stepTotal,_that.failedAttempts,_that.platform,_that.type,_that.blacklistFactors,_that.audiences,_that.scopes,_that.ipAddress,_that.userAgent,_that.deviceId,_that.nonce,_that.location,_that.accountId,_that.createdAt,_that.updatedAt,_that.deletedAt);case _:
return $default(_that.id,_that.expiredAt,_that.stepRemain,_that.stepTotal,_that.failedAttempts,_that.type,_that.blacklistFactors,_that.audiences,_that.scopes,_that.ipAddress,_that.userAgent,_that.nonce,_that.location,_that.accountId,_that.createdAt,_that.updatedAt,_that.deletedAt);case _:
return null;
}
@@ -478,7 +476,7 @@ return $default(_that.id,_that.expiredAt,_that.stepRemain,_that.stepTotal,_that.
@JsonSerializable()
class _SnAuthChallenge implements SnAuthChallenge {
const _SnAuthChallenge({required this.id, required this.expiredAt, required this.stepRemain, required this.stepTotal, required this.failedAttempts, required this.platform, required this.type, required final List<String> blacklistFactors, required final List<dynamic> audiences, required final List<dynamic> scopes, required this.ipAddress, required this.userAgent, required this.deviceId, required this.nonce, required this.location, required this.accountId, required this.createdAt, required this.updatedAt, required this.deletedAt}): _blacklistFactors = blacklistFactors,_audiences = audiences,_scopes = scopes;
const _SnAuthChallenge({required this.id, required this.expiredAt, required this.stepRemain, required this.stepTotal, required this.failedAttempts, required this.type, required final List<String> blacklistFactors, required final List<dynamic> audiences, required final List<dynamic> scopes, required this.ipAddress, required this.userAgent, required this.nonce, required this.location, required this.accountId, required this.createdAt, required this.updatedAt, required this.deletedAt}): _blacklistFactors = blacklistFactors,_audiences = audiences,_scopes = scopes;
factory _SnAuthChallenge.fromJson(Map<String, dynamic> json) => _$SnAuthChallengeFromJson(json);
@override final String id;
@@ -486,7 +484,6 @@ class _SnAuthChallenge implements SnAuthChallenge {
@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 {
@@ -511,7 +508,6 @@ class _SnAuthChallenge implements SnAuthChallenge {
@override final String ipAddress;
@override final String userAgent;
@override final String deviceId;
@override final String? nonce;
@override final String? location;
@override final String accountId;
@@ -532,16 +528,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)&&(identical(other.failedAttempts, failedAttempts) || other.failedAttempts == failedAttempts)&&(identical(other.platform, platform) || other.platform == platform)&&(identical(other.type, type) || other.type == type)&&const DeepCollectionEquality().equals(other._blacklistFactors, _blacklistFactors)&&const DeepCollectionEquality().equals(other._audiences, _audiences)&&const DeepCollectionEquality().equals(other._scopes, _scopes)&&(identical(other.ipAddress, ipAddress) || other.ipAddress == ipAddress)&&(identical(other.userAgent, userAgent) || other.userAgent == userAgent)&&(identical(other.deviceId, deviceId) || other.deviceId == deviceId)&&(identical(other.nonce, nonce) || other.nonce == nonce)&&(identical(other.location, location) || other.location == location)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnAuthChallenge&&(identical(other.id, id) || other.id == id)&&(identical(other.expiredAt, expiredAt) || other.expiredAt == expiredAt)&&(identical(other.stepRemain, stepRemain) || other.stepRemain == stepRemain)&&(identical(other.stepTotal, stepTotal) || other.stepTotal == stepTotal)&&(identical(other.failedAttempts, failedAttempts) || other.failedAttempts == failedAttempts)&&(identical(other.type, type) || other.type == type)&&const DeepCollectionEquality().equals(other._blacklistFactors, _blacklistFactors)&&const DeepCollectionEquality().equals(other._audiences, _audiences)&&const DeepCollectionEquality().equals(other._scopes, _scopes)&&(identical(other.ipAddress, ipAddress) || other.ipAddress == ipAddress)&&(identical(other.userAgent, userAgent) || other.userAgent == userAgent)&&(identical(other.nonce, nonce) || other.nonce == nonce)&&(identical(other.location, location) || other.location == location)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hashAll([runtimeType,id,expiredAt,stepRemain,stepTotal,failedAttempts,platform,type,const DeepCollectionEquality().hash(_blacklistFactors),const DeepCollectionEquality().hash(_audiences),const DeepCollectionEquality().hash(_scopes),ipAddress,userAgent,deviceId,nonce,location,accountId,createdAt,updatedAt,deletedAt]);
int get hashCode => Object.hash(runtimeType,id,expiredAt,stepRemain,stepTotal,failedAttempts,type,const DeepCollectionEquality().hash(_blacklistFactors),const DeepCollectionEquality().hash(_audiences),const DeepCollectionEquality().hash(_scopes),ipAddress,userAgent,nonce,location,accountId,createdAt,updatedAt,deletedAt);
@override
String toString() {
return 'SnAuthChallenge(id: $id, expiredAt: $expiredAt, stepRemain: $stepRemain, stepTotal: $stepTotal, failedAttempts: $failedAttempts, platform: $platform, type: $type, blacklistFactors: $blacklistFactors, audiences: $audiences, scopes: $scopes, ipAddress: $ipAddress, userAgent: $userAgent, deviceId: $deviceId, nonce: $nonce, location: $location, accountId: $accountId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
return 'SnAuthChallenge(id: $id, expiredAt: $expiredAt, stepRemain: $stepRemain, stepTotal: $stepTotal, failedAttempts: $failedAttempts, type: $type, blacklistFactors: $blacklistFactors, audiences: $audiences, scopes: $scopes, ipAddress: $ipAddress, userAgent: $userAgent, nonce: $nonce, location: $location, accountId: $accountId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
}
@@ -552,7 +548,7 @@ abstract mixin class _$SnAuthChallengeCopyWith<$Res> implements $SnAuthChallenge
factory _$SnAuthChallengeCopyWith(_SnAuthChallenge value, $Res Function(_SnAuthChallenge) _then) = __$SnAuthChallengeCopyWithImpl;
@override @useResult
$Res call({
String id, DateTime expiredAt, int stepRemain, int stepTotal, int failedAttempts, int platform, int type, List<String> blacklistFactors, List<dynamic> audiences, List<dynamic> scopes, String ipAddress, String userAgent, String deviceId, String? nonce, String? location, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
String id, DateTime expiredAt, int stepRemain, int stepTotal, int failedAttempts, int type, List<String> blacklistFactors, List<dynamic> audiences, List<dynamic> scopes, String ipAddress, String userAgent, String? nonce, String? location, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
});
@@ -569,21 +565,19 @@ class __$SnAuthChallengeCopyWithImpl<$Res>
/// Create a copy of SnAuthChallenge
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? expiredAt = null,Object? stepRemain = null,Object? stepTotal = null,Object? failedAttempts = null,Object? platform = null,Object? type = null,Object? blacklistFactors = null,Object? audiences = null,Object? scopes = null,Object? ipAddress = null,Object? userAgent = null,Object? deviceId = null,Object? nonce = freezed,Object? location = freezed,Object? accountId = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? expiredAt = null,Object? stepRemain = null,Object? stepTotal = null,Object? failedAttempts = null,Object? type = null,Object? blacklistFactors = null,Object? audiences = null,Object? scopes = null,Object? ipAddress = null,Object? userAgent = null,Object? nonce = freezed,Object? location = freezed,Object? accountId = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
return _then(_SnAuthChallenge(
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<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: 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
@@ -1189,286 +1183,6 @@ as Map<String, dynamic>?,
}
/// @nodoc
mixin _$SnAuthDevice {
dynamic get label; String get userAgent; String get deviceId; int get platform; List<SnAuthSession> get sessions;// Not from backend, used for UI
bool get isCurrent;
/// Create a copy of SnAuthDevice
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$SnAuthDeviceCopyWith<SnAuthDevice> get copyWith => _$SnAuthDeviceCopyWithImpl<SnAuthDevice>(this as SnAuthDevice, _$identity);
/// Serializes this SnAuthDevice to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnAuthDevice&&const DeepCollectionEquality().equals(other.label, label)&&(identical(other.userAgent, userAgent) || other.userAgent == userAgent)&&(identical(other.deviceId, deviceId) || other.deviceId == deviceId)&&(identical(other.platform, platform) || other.platform == platform)&&const DeepCollectionEquality().equals(other.sessions, sessions)&&(identical(other.isCurrent, isCurrent) || other.isCurrent == isCurrent));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(label),userAgent,deviceId,platform,const DeepCollectionEquality().hash(sessions),isCurrent);
@override
String toString() {
return 'SnAuthDevice(label: $label, userAgent: $userAgent, deviceId: $deviceId, platform: $platform, sessions: $sessions, isCurrent: $isCurrent)';
}
}
/// @nodoc
abstract mixin class $SnAuthDeviceCopyWith<$Res> {
factory $SnAuthDeviceCopyWith(SnAuthDevice value, $Res Function(SnAuthDevice) _then) = _$SnAuthDeviceCopyWithImpl;
@useResult
$Res call({
dynamic label, String userAgent, String deviceId, int platform, List<SnAuthSession> sessions, bool isCurrent
});
}
/// @nodoc
class _$SnAuthDeviceCopyWithImpl<$Res>
implements $SnAuthDeviceCopyWith<$Res> {
_$SnAuthDeviceCopyWithImpl(this._self, this._then);
final SnAuthDevice _self;
final $Res Function(SnAuthDevice) _then;
/// Create a copy of SnAuthDevice
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? label = freezed,Object? userAgent = null,Object? deviceId = null,Object? platform = null,Object? sessions = null,Object? isCurrent = null,}) {
return _then(_self.copyWith(
label: freezed == label ? _self.label : label // ignore: cast_nullable_to_non_nullable
as dynamic,userAgent: null == userAgent ? _self.userAgent : userAgent // ignore: cast_nullable_to_non_nullable
as String,deviceId: null == deviceId ? _self.deviceId : deviceId // ignore: cast_nullable_to_non_nullable
as String,platform: null == platform ? _self.platform : platform // ignore: cast_nullable_to_non_nullable
as int,sessions: null == sessions ? _self.sessions : sessions // ignore: cast_nullable_to_non_nullable
as List<SnAuthSession>,isCurrent: null == isCurrent ? _self.isCurrent : isCurrent // ignore: cast_nullable_to_non_nullable
as bool,
));
}
}
/// Adds pattern-matching-related methods to [SnAuthDevice].
extension SnAuthDevicePatterns on SnAuthDevice {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _SnAuthDevice value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _SnAuthDevice() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _SnAuthDevice value) $default,){
final _that = this;
switch (_that) {
case _SnAuthDevice():
return $default(_that);}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _SnAuthDevice value)? $default,){
final _that = this;
switch (_that) {
case _SnAuthDevice() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( dynamic label, String userAgent, String deviceId, int platform, List<SnAuthSession> sessions, bool isCurrent)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _SnAuthDevice() when $default != null:
return $default(_that.label,_that.userAgent,_that.deviceId,_that.platform,_that.sessions,_that.isCurrent);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( dynamic label, String userAgent, String deviceId, int platform, List<SnAuthSession> sessions, bool isCurrent) $default,) {final _that = this;
switch (_that) {
case _SnAuthDevice():
return $default(_that.label,_that.userAgent,_that.deviceId,_that.platform,_that.sessions,_that.isCurrent);}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( dynamic label, String userAgent, String deviceId, int platform, List<SnAuthSession> sessions, bool isCurrent)? $default,) {final _that = this;
switch (_that) {
case _SnAuthDevice() when $default != null:
return $default(_that.label,_that.userAgent,_that.deviceId,_that.platform,_that.sessions,_that.isCurrent);case _:
return null;
}
}
}
/// @nodoc
@JsonSerializable()
class _SnAuthDevice implements SnAuthDevice {
const _SnAuthDevice({required this.label, required this.userAgent, required this.deviceId, required this.platform, required final List<SnAuthSession> sessions, this.isCurrent = false}): _sessions = sessions;
factory _SnAuthDevice.fromJson(Map<String, dynamic> json) => _$SnAuthDeviceFromJson(json);
@override final dynamic label;
@override final String userAgent;
@override final String deviceId;
@override final int platform;
final List<SnAuthSession> _sessions;
@override List<SnAuthSession> get sessions {
if (_sessions is EqualUnmodifiableListView) return _sessions;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_sessions);
}
// Not from backend, used for UI
@override@JsonKey() final bool isCurrent;
/// Create a copy of SnAuthDevice
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$SnAuthDeviceCopyWith<_SnAuthDevice> get copyWith => __$SnAuthDeviceCopyWithImpl<_SnAuthDevice>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$SnAuthDeviceToJson(this, );
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnAuthDevice&&const DeepCollectionEquality().equals(other.label, label)&&(identical(other.userAgent, userAgent) || other.userAgent == userAgent)&&(identical(other.deviceId, deviceId) || other.deviceId == deviceId)&&(identical(other.platform, platform) || other.platform == platform)&&const DeepCollectionEquality().equals(other._sessions, _sessions)&&(identical(other.isCurrent, isCurrent) || other.isCurrent == isCurrent));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(label),userAgent,deviceId,platform,const DeepCollectionEquality().hash(_sessions),isCurrent);
@override
String toString() {
return 'SnAuthDevice(label: $label, userAgent: $userAgent, deviceId: $deviceId, platform: $platform, sessions: $sessions, isCurrent: $isCurrent)';
}
}
/// @nodoc
abstract mixin class _$SnAuthDeviceCopyWith<$Res> implements $SnAuthDeviceCopyWith<$Res> {
factory _$SnAuthDeviceCopyWith(_SnAuthDevice value, $Res Function(_SnAuthDevice) _then) = __$SnAuthDeviceCopyWithImpl;
@override @useResult
$Res call({
dynamic label, String userAgent, String deviceId, int platform, List<SnAuthSession> sessions, bool isCurrent
});
}
/// @nodoc
class __$SnAuthDeviceCopyWithImpl<$Res>
implements _$SnAuthDeviceCopyWith<$Res> {
__$SnAuthDeviceCopyWithImpl(this._self, this._then);
final _SnAuthDevice _self;
final $Res Function(_SnAuthDevice) _then;
/// Create a copy of SnAuthDevice
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? label = freezed,Object? userAgent = null,Object? deviceId = null,Object? platform = null,Object? sessions = null,Object? isCurrent = null,}) {
return _then(_SnAuthDevice(
label: freezed == label ? _self.label : label // ignore: cast_nullable_to_non_nullable
as dynamic,userAgent: null == userAgent ? _self.userAgent : userAgent // ignore: cast_nullable_to_non_nullable
as String,deviceId: null == deviceId ? _self.deviceId : deviceId // ignore: cast_nullable_to_non_nullable
as String,platform: null == platform ? _self.platform : platform // ignore: cast_nullable_to_non_nullable
as int,sessions: null == sessions ? _self._sessions : sessions // ignore: cast_nullable_to_non_nullable
as List<SnAuthSession>,isCurrent: null == isCurrent ? _self.isCurrent : isCurrent // ignore: cast_nullable_to_non_nullable
as bool,
));
}
}
/// @nodoc
mixin _$SnAccountConnection {

View File

@@ -20,7 +20,6 @@ _SnAuthChallenge _$SnAuthChallengeFromJson(Map<String, dynamic> json) =>
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>)
@@ -30,7 +29,6 @@ _SnAuthChallenge _$SnAuthChallengeFromJson(Map<String, dynamic> json) =>
scopes: json['scopes'] as List<dynamic>,
ipAddress: json['ip_address'] as String,
userAgent: json['user_agent'] as String,
deviceId: json['device_id'] as String,
nonce: json['nonce'] as String?,
location: json['location'] as String?,
accountId: json['account_id'] as String,
@@ -49,14 +47,12 @@ Map<String, dynamic> _$SnAuthChallengeToJson(_SnAuthChallenge instance) =>
'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,
'ip_address': instance.ipAddress,
'user_agent': instance.userAgent,
'device_id': instance.deviceId,
'nonce': instance.nonce,
'location': instance.location,
'account_id': instance.accountId,
@@ -133,29 +129,6 @@ Map<String, dynamic> _$SnAuthFactorToJson(_SnAuthFactor instance) =>
'created_response': instance.createdResponse,
};
_SnAuthDevice _$SnAuthDeviceFromJson(Map<String, dynamic> json) =>
_SnAuthDevice(
label: json['label'],
userAgent: json['user_agent'] as String,
deviceId: json['device_id'] as String,
platform: (json['platform'] as num).toInt(),
sessions:
(json['sessions'] as List<dynamic>)
.map((e) => SnAuthSession.fromJson(e as Map<String, dynamic>))
.toList(),
isCurrent: json['is_current'] as bool? ?? false,
);
Map<String, dynamic> _$SnAuthDeviceToJson(_SnAuthDevice instance) =>
<String, dynamic>{
'label': instance.label,
'user_agent': instance.userAgent,
'device_id': instance.deviceId,
'platform': instance.platform,
'sessions': instance.sessions.map((e) => e.toJson()).toList(),
'is_current': instance.isCurrent,
};
_SnAccountConnection _$SnAccountConnectionFromJson(Map<String, dynamic> json) =>
_SnAccountConnection(
id: json['id'] as String,

View File

@@ -1,7 +1,7 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:island/models/file.dart';
import 'package:island/models/realm.dart';
import 'package:island/models/user.dart';
import 'package:island/models/account.dart';
part 'chat.freezed.dart';
part 'chat.g.dart';
@@ -91,6 +91,7 @@ sealed class SnChatMember with _$SnChatMember {
required DateTime? breakUntil,
required DateTime? timeoutUntil,
required bool isBot,
required SnAccountStatus? status,
// Frontend data
DateTime? lastTyped,
}) = _SnChatMember;

View File

@@ -1037,7 +1037,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; DateTime? get breakUntil; DateTime? get timeoutUntil; bool get isBot;// Frontend data
DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; String get id; String get chatRoomId; SnChatRoom? get chatRoom; String get accountId; SnAccount get account; String? get nick; int get role; int get notify; DateTime? get joinedAt; DateTime? get breakUntil; DateTime? get timeoutUntil; bool get isBot; SnAccountStatus? get status;// Frontend data
DateTime? get lastTyped;
/// Create a copy of SnChatMember
/// with the given fields replaced by the non-null parameter values.
@@ -1051,16 +1051,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.breakUntil, breakUntil) || other.breakUntil == breakUntil)&&(identical(other.timeoutUntil, timeoutUntil) || other.timeoutUntil == timeoutUntil)&&(identical(other.isBot, isBot) || other.isBot == isBot)&&(identical(other.lastTyped, lastTyped) || other.lastTyped == lastTyped));
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnChatMember&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.id, id) || other.id == id)&&(identical(other.chatRoomId, chatRoomId) || other.chatRoomId == chatRoomId)&&(identical(other.chatRoom, chatRoom) || other.chatRoom == chatRoom)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.account, account) || other.account == account)&&(identical(other.nick, nick) || other.nick == nick)&&(identical(other.role, role) || other.role == role)&&(identical(other.notify, notify) || other.notify == notify)&&(identical(other.joinedAt, joinedAt) || other.joinedAt == joinedAt)&&(identical(other.breakUntil, breakUntil) || other.breakUntil == breakUntil)&&(identical(other.timeoutUntil, timeoutUntil) || other.timeoutUntil == timeoutUntil)&&(identical(other.isBot, isBot) || other.isBot == isBot)&&(identical(other.status, status) || other.status == status)&&(identical(other.lastTyped, lastTyped) || other.lastTyped == lastTyped));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,createdAt,updatedAt,deletedAt,id,chatRoomId,chatRoom,accountId,account,nick,role,notify,joinedAt,breakUntil,timeoutUntil,isBot,lastTyped);
int get hashCode => Object.hash(runtimeType,createdAt,updatedAt,deletedAt,id,chatRoomId,chatRoom,accountId,account,nick,role,notify,joinedAt,breakUntil,timeoutUntil,isBot,status,lastTyped);
@override
String toString() {
return 'SnChatMember(createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, id: $id, chatRoomId: $chatRoomId, chatRoom: $chatRoom, accountId: $accountId, account: $account, nick: $nick, role: $role, notify: $notify, joinedAt: $joinedAt, breakUntil: $breakUntil, timeoutUntil: $timeoutUntil, isBot: $isBot, lastTyped: $lastTyped)';
return 'SnChatMember(createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, id: $id, chatRoomId: $chatRoomId, chatRoom: $chatRoom, accountId: $accountId, account: $account, nick: $nick, role: $role, notify: $notify, joinedAt: $joinedAt, breakUntil: $breakUntil, timeoutUntil: $timeoutUntil, isBot: $isBot, status: $status, lastTyped: $lastTyped)';
}
@@ -1071,11 +1071,11 @@ abstract mixin class $SnChatMemberCopyWith<$Res> {
factory $SnChatMemberCopyWith(SnChatMember value, $Res Function(SnChatMember) _then) = _$SnChatMemberCopyWithImpl;
@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, DateTime? breakUntil, DateTime? timeoutUntil, bool isBot, DateTime? lastTyped
DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, String id, String chatRoomId, SnChatRoom? chatRoom, String accountId, SnAccount account, String? nick, int role, int notify, DateTime? joinedAt, DateTime? breakUntil, DateTime? timeoutUntil, bool isBot, SnAccountStatus? status, DateTime? lastTyped
});
$SnChatRoomCopyWith<$Res>? get chatRoom;$SnAccountCopyWith<$Res> get account;
$SnChatRoomCopyWith<$Res>? get chatRoom;$SnAccountCopyWith<$Res> get account;$SnAccountStatusCopyWith<$Res>? get status;
}
/// @nodoc
@@ -1088,7 +1088,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? breakUntil = freezed,Object? timeoutUntil = freezed,Object? isBot = null,Object? lastTyped = freezed,}) {
@pragma('vm:prefer-inline') @override $Res call({Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? id = null,Object? chatRoomId = null,Object? chatRoom = freezed,Object? accountId = null,Object? account = null,Object? nick = freezed,Object? role = null,Object? notify = null,Object? joinedAt = freezed,Object? breakUntil = freezed,Object? timeoutUntil = freezed,Object? isBot = null,Object? status = freezed,Object? lastTyped = freezed,}) {
return _then(_self.copyWith(
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
@@ -1105,7 +1105,8 @@ as int,joinedAt: freezed == joinedAt ? _self.joinedAt : joinedAt // ignore: cast
as DateTime?,breakUntil: freezed == breakUntil ? _self.breakUntil : breakUntil // ignore: cast_nullable_to_non_nullable
as DateTime?,timeoutUntil: freezed == timeoutUntil ? _self.timeoutUntil : timeoutUntil // ignore: cast_nullable_to_non_nullable
as DateTime?,isBot: null == isBot ? _self.isBot : isBot // ignore: cast_nullable_to_non_nullable
as bool,lastTyped: freezed == lastTyped ? _self.lastTyped : lastTyped // ignore: cast_nullable_to_non_nullable
as bool,status: freezed == status ? _self.status : status // ignore: cast_nullable_to_non_nullable
as SnAccountStatus?,lastTyped: freezed == lastTyped ? _self.lastTyped : lastTyped // ignore: cast_nullable_to_non_nullable
as DateTime?,
));
}
@@ -1130,6 +1131,18 @@ $SnAccountCopyWith<$Res> get account {
return $SnAccountCopyWith<$Res>(_self.account, (value) {
return _then(_self.copyWith(account: value));
});
}/// Create a copy of SnChatMember
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$SnAccountStatusCopyWith<$Res>? get status {
if (_self.status == null) {
return null;
}
return $SnAccountStatusCopyWith<$Res>(_self.status!, (value) {
return _then(_self.copyWith(status: value));
});
}
}
@@ -1209,10 +1222,10 @@ return $default(_that);case _:
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, String id, String chatRoomId, SnChatRoom? chatRoom, String accountId, SnAccount account, String? nick, int role, int notify, DateTime? joinedAt, DateTime? breakUntil, DateTime? timeoutUntil, bool isBot, DateTime? lastTyped)? $default,{required TResult orElse(),}) {final _that = this;
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, String id, String chatRoomId, SnChatRoom? chatRoom, String accountId, SnAccount account, String? nick, int role, int notify, DateTime? joinedAt, DateTime? breakUntil, DateTime? timeoutUntil, bool isBot, SnAccountStatus? status, DateTime? lastTyped)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _SnChatMember() when $default != null:
return $default(_that.createdAt,_that.updatedAt,_that.deletedAt,_that.id,_that.chatRoomId,_that.chatRoom,_that.accountId,_that.account,_that.nick,_that.role,_that.notify,_that.joinedAt,_that.breakUntil,_that.timeoutUntil,_that.isBot,_that.lastTyped);case _:
return $default(_that.createdAt,_that.updatedAt,_that.deletedAt,_that.id,_that.chatRoomId,_that.chatRoom,_that.accountId,_that.account,_that.nick,_that.role,_that.notify,_that.joinedAt,_that.breakUntil,_that.timeoutUntil,_that.isBot,_that.status,_that.lastTyped);case _:
return orElse();
}
@@ -1230,10 +1243,10 @@ return $default(_that.createdAt,_that.updatedAt,_that.deletedAt,_that.id,_that.c
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, String id, String chatRoomId, SnChatRoom? chatRoom, String accountId, SnAccount account, String? nick, int role, int notify, DateTime? joinedAt, DateTime? breakUntil, DateTime? timeoutUntil, bool isBot, DateTime? lastTyped) $default,) {final _that = this;
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, String id, String chatRoomId, SnChatRoom? chatRoom, String accountId, SnAccount account, String? nick, int role, int notify, DateTime? joinedAt, DateTime? breakUntil, DateTime? timeoutUntil, bool isBot, SnAccountStatus? status, DateTime? lastTyped) $default,) {final _that = this;
switch (_that) {
case _SnChatMember():
return $default(_that.createdAt,_that.updatedAt,_that.deletedAt,_that.id,_that.chatRoomId,_that.chatRoom,_that.accountId,_that.account,_that.nick,_that.role,_that.notify,_that.joinedAt,_that.breakUntil,_that.timeoutUntil,_that.isBot,_that.lastTyped);}
return $default(_that.createdAt,_that.updatedAt,_that.deletedAt,_that.id,_that.chatRoomId,_that.chatRoom,_that.accountId,_that.account,_that.nick,_that.role,_that.notify,_that.joinedAt,_that.breakUntil,_that.timeoutUntil,_that.isBot,_that.status,_that.lastTyped);}
}
/// A variant of `when` that fallback to returning `null`
///
@@ -1247,10 +1260,10 @@ return $default(_that.createdAt,_that.updatedAt,_that.deletedAt,_that.id,_that.c
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, String id, String chatRoomId, SnChatRoom? chatRoom, String accountId, SnAccount account, String? nick, int role, int notify, DateTime? joinedAt, DateTime? breakUntil, DateTime? timeoutUntil, bool isBot, DateTime? lastTyped)? $default,) {final _that = this;
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, String id, String chatRoomId, SnChatRoom? chatRoom, String accountId, SnAccount account, String? nick, int role, int notify, DateTime? joinedAt, DateTime? breakUntil, DateTime? timeoutUntil, bool isBot, SnAccountStatus? status, DateTime? lastTyped)? $default,) {final _that = this;
switch (_that) {
case _SnChatMember() when $default != null:
return $default(_that.createdAt,_that.updatedAt,_that.deletedAt,_that.id,_that.chatRoomId,_that.chatRoom,_that.accountId,_that.account,_that.nick,_that.role,_that.notify,_that.joinedAt,_that.breakUntil,_that.timeoutUntil,_that.isBot,_that.lastTyped);case _:
return $default(_that.createdAt,_that.updatedAt,_that.deletedAt,_that.id,_that.chatRoomId,_that.chatRoom,_that.accountId,_that.account,_that.nick,_that.role,_that.notify,_that.joinedAt,_that.breakUntil,_that.timeoutUntil,_that.isBot,_that.status,_that.lastTyped);case _:
return null;
}
@@ -1262,7 +1275,7 @@ return $default(_that.createdAt,_that.updatedAt,_that.deletedAt,_that.id,_that.c
@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.breakUntil, required this.timeoutUntil, required this.isBot, this.lastTyped});
const _SnChatMember({required this.createdAt, required this.updatedAt, required this.deletedAt, required this.id, required this.chatRoomId, required this.chatRoom, required this.accountId, required this.account, required this.nick, required this.role, required this.notify, required this.joinedAt, required this.breakUntil, required this.timeoutUntil, required this.isBot, required this.status, this.lastTyped});
factory _SnChatMember.fromJson(Map<String, dynamic> json) => _$SnChatMemberFromJson(json);
@override final DateTime createdAt;
@@ -1280,6 +1293,7 @@ class _SnChatMember implements SnChatMember {
@override final DateTime? breakUntil;
@override final DateTime? timeoutUntil;
@override final bool isBot;
@override final SnAccountStatus? status;
// Frontend data
@override final DateTime? lastTyped;
@@ -1296,16 +1310,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.breakUntil, breakUntil) || other.breakUntil == breakUntil)&&(identical(other.timeoutUntil, timeoutUntil) || other.timeoutUntil == timeoutUntil)&&(identical(other.isBot, isBot) || other.isBot == isBot)&&(identical(other.lastTyped, lastTyped) || other.lastTyped == lastTyped));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnChatMember&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.id, id) || other.id == id)&&(identical(other.chatRoomId, chatRoomId) || other.chatRoomId == chatRoomId)&&(identical(other.chatRoom, chatRoom) || other.chatRoom == chatRoom)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.account, account) || other.account == account)&&(identical(other.nick, nick) || other.nick == nick)&&(identical(other.role, role) || other.role == role)&&(identical(other.notify, notify) || other.notify == notify)&&(identical(other.joinedAt, joinedAt) || other.joinedAt == joinedAt)&&(identical(other.breakUntil, breakUntil) || other.breakUntil == breakUntil)&&(identical(other.timeoutUntil, timeoutUntil) || other.timeoutUntil == timeoutUntil)&&(identical(other.isBot, isBot) || other.isBot == isBot)&&(identical(other.status, status) || other.status == status)&&(identical(other.lastTyped, lastTyped) || other.lastTyped == lastTyped));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,createdAt,updatedAt,deletedAt,id,chatRoomId,chatRoom,accountId,account,nick,role,notify,joinedAt,breakUntil,timeoutUntil,isBot,lastTyped);
int get hashCode => Object.hash(runtimeType,createdAt,updatedAt,deletedAt,id,chatRoomId,chatRoom,accountId,account,nick,role,notify,joinedAt,breakUntil,timeoutUntil,isBot,status,lastTyped);
@override
String toString() {
return 'SnChatMember(createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, id: $id, chatRoomId: $chatRoomId, chatRoom: $chatRoom, accountId: $accountId, account: $account, nick: $nick, role: $role, notify: $notify, joinedAt: $joinedAt, breakUntil: $breakUntil, timeoutUntil: $timeoutUntil, isBot: $isBot, lastTyped: $lastTyped)';
return 'SnChatMember(createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, id: $id, chatRoomId: $chatRoomId, chatRoom: $chatRoom, accountId: $accountId, account: $account, nick: $nick, role: $role, notify: $notify, joinedAt: $joinedAt, breakUntil: $breakUntil, timeoutUntil: $timeoutUntil, isBot: $isBot, status: $status, lastTyped: $lastTyped)';
}
@@ -1316,11 +1330,11 @@ abstract mixin class _$SnChatMemberCopyWith<$Res> implements $SnChatMemberCopyWi
factory _$SnChatMemberCopyWith(_SnChatMember value, $Res Function(_SnChatMember) _then) = __$SnChatMemberCopyWithImpl;
@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, DateTime? breakUntil, DateTime? timeoutUntil, bool isBot, DateTime? lastTyped
DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, String id, String chatRoomId, SnChatRoom? chatRoom, String accountId, SnAccount account, String? nick, int role, int notify, DateTime? joinedAt, DateTime? breakUntil, DateTime? timeoutUntil, bool isBot, SnAccountStatus? status, DateTime? lastTyped
});
@override $SnChatRoomCopyWith<$Res>? get chatRoom;@override $SnAccountCopyWith<$Res> get account;
@override $SnChatRoomCopyWith<$Res>? get chatRoom;@override $SnAccountCopyWith<$Res> get account;@override $SnAccountStatusCopyWith<$Res>? get status;
}
/// @nodoc
@@ -1333,7 +1347,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? breakUntil = freezed,Object? timeoutUntil = freezed,Object? isBot = null,Object? lastTyped = freezed,}) {
@override @pragma('vm:prefer-inline') $Res call({Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? id = null,Object? chatRoomId = null,Object? chatRoom = freezed,Object? accountId = null,Object? account = null,Object? nick = freezed,Object? role = null,Object? notify = null,Object? joinedAt = freezed,Object? breakUntil = freezed,Object? timeoutUntil = freezed,Object? isBot = null,Object? status = freezed,Object? lastTyped = freezed,}) {
return _then(_SnChatMember(
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
@@ -1350,7 +1364,8 @@ as int,joinedAt: freezed == joinedAt ? _self.joinedAt : joinedAt // ignore: cast
as DateTime?,breakUntil: freezed == breakUntil ? _self.breakUntil : breakUntil // ignore: cast_nullable_to_non_nullable
as DateTime?,timeoutUntil: freezed == timeoutUntil ? _self.timeoutUntil : timeoutUntil // ignore: cast_nullable_to_non_nullable
as DateTime?,isBot: null == isBot ? _self.isBot : isBot // ignore: cast_nullable_to_non_nullable
as bool,lastTyped: freezed == lastTyped ? _self.lastTyped : lastTyped // ignore: cast_nullable_to_non_nullable
as bool,status: freezed == status ? _self.status : status // ignore: cast_nullable_to_non_nullable
as SnAccountStatus?,lastTyped: freezed == lastTyped ? _self.lastTyped : lastTyped // ignore: cast_nullable_to_non_nullable
as DateTime?,
));
}
@@ -1376,6 +1391,18 @@ $SnAccountCopyWith<$Res> get account {
return $SnAccountCopyWith<$Res>(_self.account, (value) {
return _then(_self.copyWith(account: value));
});
}/// Create a copy of SnChatMember
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$SnAccountStatusCopyWith<$Res>? get status {
if (_self.status == null) {
return null;
}
return $SnAccountStatusCopyWith<$Res>(_self.status!, (value) {
return _then(_self.copyWith(status: value));
});
}
}

View File

@@ -177,6 +177,12 @@ _SnChatMember _$SnChatMemberFromJson(Map<String, dynamic> json) =>
? null
: DateTime.parse(json['timeout_until'] as String),
isBot: json['is_bot'] as bool,
status:
json['status'] == null
? null
: SnAccountStatus.fromJson(
json['status'] as Map<String, dynamic>,
),
lastTyped:
json['last_typed'] == null
? null
@@ -200,6 +206,7 @@ Map<String, dynamic> _$SnChatMemberToJson(_SnChatMember instance) =>
'break_until': instance.breakUntil?.toIso8601String(),
'timeout_until': instance.timeoutUntil?.toIso8601String(),
'is_bot': instance.isBot,
'status': instance.status?.toJson(),
'last_typed': instance.lastTyped?.toIso8601String(),
};

View File

@@ -1,6 +1,6 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:island/models/file.dart';
import 'package:island/models/user.dart';
import 'package:island/models/account.dart';
part 'custom_app.freezed.dart';
part 'custom_app.g.dart';

View File

@@ -11,8 +11,8 @@ sealed class SnScrappedLink with _$SnScrappedLink {
required String title,
required String? description,
required String? imageUrl,
required String faviconUrl,
required String siteName,
required String? faviconUrl,
required String? siteName,
required String? contentType,
required String? author,
required DateTime? publishedDate,

View File

@@ -15,7 +15,7 @@ T _$identity<T>(T value) => value;
/// @nodoc
mixin _$SnScrappedLink {
String get type; String get url; String get title; String? get description; String? get imageUrl; String get faviconUrl; String get siteName; String? get contentType; String? get author; DateTime? get publishedDate;
String get type; String get url; String get title; String? get description; String? get imageUrl; String? get faviconUrl; String? get siteName; String? get contentType; String? get author; DateTime? get publishedDate;
/// Create a copy of SnScrappedLink
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@@ -48,7 +48,7 @@ abstract mixin class $SnScrappedLinkCopyWith<$Res> {
factory $SnScrappedLinkCopyWith(SnScrappedLink value, $Res Function(SnScrappedLink) _then) = _$SnScrappedLinkCopyWithImpl;
@useResult
$Res call({
String type, String url, String title, String? description, String? imageUrl, String faviconUrl, String siteName, String? contentType, String? author, DateTime? publishedDate
String type, String url, String title, String? description, String? imageUrl, String? faviconUrl, String? siteName, String? contentType, String? author, DateTime? publishedDate
});
@@ -65,16 +65,16 @@ class _$SnScrappedLinkCopyWithImpl<$Res>
/// Create a copy of SnScrappedLink
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? type = null,Object? url = null,Object? title = null,Object? description = freezed,Object? imageUrl = freezed,Object? faviconUrl = null,Object? siteName = null,Object? contentType = freezed,Object? author = freezed,Object? publishedDate = freezed,}) {
@pragma('vm:prefer-inline') @override $Res call({Object? type = null,Object? url = null,Object? title = null,Object? description = freezed,Object? imageUrl = freezed,Object? faviconUrl = freezed,Object? siteName = freezed,Object? contentType = freezed,Object? author = freezed,Object? publishedDate = freezed,}) {
return _then(_self.copyWith(
type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
as String,url: null == url ? _self.url : url // ignore: cast_nullable_to_non_nullable
as String,title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable
as String,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
as String?,imageUrl: freezed == imageUrl ? _self.imageUrl : imageUrl // ignore: cast_nullable_to_non_nullable
as String?,faviconUrl: null == faviconUrl ? _self.faviconUrl : faviconUrl // ignore: cast_nullable_to_non_nullable
as String,siteName: null == siteName ? _self.siteName : siteName // ignore: cast_nullable_to_non_nullable
as String,contentType: freezed == contentType ? _self.contentType : contentType // ignore: cast_nullable_to_non_nullable
as String?,faviconUrl: freezed == faviconUrl ? _self.faviconUrl : faviconUrl // ignore: cast_nullable_to_non_nullable
as String?,siteName: freezed == siteName ? _self.siteName : siteName // ignore: cast_nullable_to_non_nullable
as String?,contentType: freezed == contentType ? _self.contentType : contentType // ignore: cast_nullable_to_non_nullable
as String?,author: freezed == author ? _self.author : author // ignore: cast_nullable_to_non_nullable
as String?,publishedDate: freezed == publishedDate ? _self.publishedDate : publishedDate // ignore: cast_nullable_to_non_nullable
as DateTime?,
@@ -159,7 +159,7 @@ return $default(_that);case _:
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String type, String url, String title, String? description, String? imageUrl, String faviconUrl, String siteName, String? contentType, String? author, DateTime? publishedDate)? $default,{required TResult orElse(),}) {final _that = this;
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String type, String url, String title, String? description, String? imageUrl, String? faviconUrl, String? siteName, String? contentType, String? author, DateTime? publishedDate)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _SnScrappedLink() when $default != null:
return $default(_that.type,_that.url,_that.title,_that.description,_that.imageUrl,_that.faviconUrl,_that.siteName,_that.contentType,_that.author,_that.publishedDate);case _:
@@ -180,7 +180,7 @@ return $default(_that.type,_that.url,_that.title,_that.description,_that.imageUr
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String type, String url, String title, String? description, String? imageUrl, String faviconUrl, String siteName, String? contentType, String? author, DateTime? publishedDate) $default,) {final _that = this;
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String type, String url, String title, String? description, String? imageUrl, String? faviconUrl, String? siteName, String? contentType, String? author, DateTime? publishedDate) $default,) {final _that = this;
switch (_that) {
case _SnScrappedLink():
return $default(_that.type,_that.url,_that.title,_that.description,_that.imageUrl,_that.faviconUrl,_that.siteName,_that.contentType,_that.author,_that.publishedDate);}
@@ -197,7 +197,7 @@ return $default(_that.type,_that.url,_that.title,_that.description,_that.imageUr
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String type, String url, String title, String? description, String? imageUrl, String faviconUrl, String siteName, String? contentType, String? author, DateTime? publishedDate)? $default,) {final _that = this;
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String type, String url, String title, String? description, String? imageUrl, String? faviconUrl, String? siteName, String? contentType, String? author, DateTime? publishedDate)? $default,) {final _that = this;
switch (_that) {
case _SnScrappedLink() when $default != null:
return $default(_that.type,_that.url,_that.title,_that.description,_that.imageUrl,_that.faviconUrl,_that.siteName,_that.contentType,_that.author,_that.publishedDate);case _:
@@ -220,8 +220,8 @@ class _SnScrappedLink implements SnScrappedLink {
@override final String title;
@override final String? description;
@override final String? imageUrl;
@override final String faviconUrl;
@override final String siteName;
@override final String? faviconUrl;
@override final String? siteName;
@override final String? contentType;
@override final String? author;
@override final DateTime? publishedDate;
@@ -259,7 +259,7 @@ abstract mixin class _$SnScrappedLinkCopyWith<$Res> implements $SnScrappedLinkCo
factory _$SnScrappedLinkCopyWith(_SnScrappedLink value, $Res Function(_SnScrappedLink) _then) = __$SnScrappedLinkCopyWithImpl;
@override @useResult
$Res call({
String type, String url, String title, String? description, String? imageUrl, String faviconUrl, String siteName, String? contentType, String? author, DateTime? publishedDate
String type, String url, String title, String? description, String? imageUrl, String? faviconUrl, String? siteName, String? contentType, String? author, DateTime? publishedDate
});
@@ -276,16 +276,16 @@ class __$SnScrappedLinkCopyWithImpl<$Res>
/// Create a copy of SnScrappedLink
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? type = null,Object? url = null,Object? title = null,Object? description = freezed,Object? imageUrl = freezed,Object? faviconUrl = null,Object? siteName = null,Object? contentType = freezed,Object? author = freezed,Object? publishedDate = freezed,}) {
@override @pragma('vm:prefer-inline') $Res call({Object? type = null,Object? url = null,Object? title = null,Object? description = freezed,Object? imageUrl = freezed,Object? faviconUrl = freezed,Object? siteName = freezed,Object? contentType = freezed,Object? author = freezed,Object? publishedDate = freezed,}) {
return _then(_SnScrappedLink(
type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
as String,url: null == url ? _self.url : url // ignore: cast_nullable_to_non_nullable
as String,title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable
as String,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
as String?,imageUrl: freezed == imageUrl ? _self.imageUrl : imageUrl // ignore: cast_nullable_to_non_nullable
as String?,faviconUrl: null == faviconUrl ? _self.faviconUrl : faviconUrl // ignore: cast_nullable_to_non_nullable
as String,siteName: null == siteName ? _self.siteName : siteName // ignore: cast_nullable_to_non_nullable
as String,contentType: freezed == contentType ? _self.contentType : contentType // ignore: cast_nullable_to_non_nullable
as String?,faviconUrl: freezed == faviconUrl ? _self.faviconUrl : faviconUrl // ignore: cast_nullable_to_non_nullable
as String?,siteName: freezed == siteName ? _self.siteName : siteName // ignore: cast_nullable_to_non_nullable
as String?,contentType: freezed == contentType ? _self.contentType : contentType // ignore: cast_nullable_to_non_nullable
as String?,author: freezed == author ? _self.author : author // ignore: cast_nullable_to_non_nullable
as String?,publishedDate: freezed == publishedDate ? _self.publishedDate : publishedDate // ignore: cast_nullable_to_non_nullable
as DateTime?,

View File

@@ -13,8 +13,8 @@ _SnScrappedLink _$SnScrappedLinkFromJson(Map<String, dynamic> json) =>
title: json['title'] as String,
description: json['description'] as String?,
imageUrl: json['image_url'] as String?,
faviconUrl: json['favicon_url'] as String,
siteName: json['site_name'] as String,
faviconUrl: json['favicon_url'] as String?,
siteName: json['site_name'] as String?,
contentType: json['content_type'] as String?,
author: json['author'] as String?,
publishedDate:

View File

@@ -8,7 +8,7 @@ part 'poll.g.dart';
sealed class SnPollWithStats with _$SnPollWithStats {
const factory SnPollWithStats({
required Map<String, dynamic>? userAnswer,
required Map<String, dynamic> stats,
@Default({}) Map<String, dynamic> stats,
required String id,
required List<SnPollQuestion> questions,
String? title,

View File

@@ -213,7 +213,7 @@ return $default(_that.userAnswer,_that.stats,_that.id,_that.questions,_that.titl
@JsonSerializable()
class _SnPollWithStats implements SnPollWithStats {
const _SnPollWithStats({required final Map<String, dynamic>? userAnswer, required final Map<String, dynamic> stats, required this.id, required final List<SnPollQuestion> questions, this.title, this.description, this.endedAt, required this.publisherId, required this.createdAt, required this.updatedAt, this.deletedAt}): _userAnswer = userAnswer,_stats = stats,_questions = questions;
const _SnPollWithStats({required final Map<String, dynamic>? userAnswer, final Map<String, dynamic> stats = const {}, required this.id, required final List<SnPollQuestion> questions, this.title, this.description, this.endedAt, required this.publisherId, required this.createdAt, required this.updatedAt, this.deletedAt}): _userAnswer = userAnswer,_stats = stats,_questions = questions;
factory _SnPollWithStats.fromJson(Map<String, dynamic> json) => _$SnPollWithStatsFromJson(json);
final Map<String, dynamic>? _userAnswer;
@@ -226,7 +226,7 @@ class _SnPollWithStats implements SnPollWithStats {
}
final Map<String, dynamic> _stats;
@override Map<String, dynamic> get stats {
@override@JsonKey() Map<String, dynamic> get stats {
if (_stats is EqualUnmodifiableMapView) return _stats;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(_stats);

View File

@@ -9,7 +9,7 @@ part of 'poll.dart';
_SnPollWithStats _$SnPollWithStatsFromJson(Map<String, dynamic> json) =>
_SnPollWithStats(
userAnswer: json['user_answer'] as Map<String, dynamic>?,
stats: json['stats'] as Map<String, dynamic>,
stats: json['stats'] as Map<String, dynamic>? ?? const {},
id: json['id'] as String,
questions:
(json['questions'] as List<dynamic>)

View File

@@ -3,6 +3,7 @@ import 'package:island/models/file.dart';
import 'package:island/models/post_category.dart';
import 'package:island/models/post_tag.dart';
import 'package:island/models/publisher.dart';
import 'package:island/models/realm.dart';
part 'post.freezed.dart';
part 'post.g.dart';
@@ -18,6 +19,7 @@ sealed class SnPost with _$SnPost {
@Default(null) DateTime? publishedAt,
@Default(0) int visibility,
String? content,
String? slug,
@Default(0) int type,
Map<String, dynamic>? meta,
@Default(0) int viewsUnique,
@@ -31,6 +33,8 @@ sealed class SnPost with _$SnPost {
SnPost? repliedPost,
String? forwardedPostId,
SnPost? forwardedPost,
String? realmId,
SnRealm? realm,
@Default([]) List<SnCloudFile> attachments,
required SnPublisher publisher,
@Default({}) Map<String, int> reactionsCount,

View File

@@ -15,7 +15,7 @@ T _$identity<T>(T value) => value;
/// @nodoc
mixin _$SnPost {
String get id; String? get title; String? get description; String? get language; DateTime? get editedAt; DateTime? get publishedAt; int get visibility; String? get content; int get type; Map<String, dynamic>? get meta; int get viewsUnique; int get viewsTotal; int get upvotes; int get downvotes; int get repliesCount; String? get threadedPostId; SnPost? get threadedPost; String? get repliedPostId; SnPost? get repliedPost; String? get forwardedPostId; SnPost? get forwardedPost; List<SnCloudFile> get attachments; SnPublisher get publisher; Map<String, int> get reactionsCount; Map<String, bool> get reactionsMade; List<dynamic> get reactions; List<SnPostTag> get tags; List<SnPostCategory> get categories; List<dynamic> get collections; DateTime? get createdAt; DateTime? get updatedAt; DateTime? get deletedAt; bool get isTruncated;
String get id; String? get title; String? get description; String? get language; DateTime? get editedAt; DateTime? get publishedAt; int get visibility; String? get content; String? get slug; int get type; Map<String, dynamic>? get meta; int get viewsUnique; int get viewsTotal; int get upvotes; int get downvotes; int get repliesCount; String? get threadedPostId; SnPost? get threadedPost; String? get repliedPostId; SnPost? get repliedPost; String? get forwardedPostId; SnPost? get forwardedPost; String? get realmId; SnRealm? get realm; List<SnCloudFile> get attachments; SnPublisher get publisher; Map<String, int> get reactionsCount; Map<String, bool> get reactionsMade; List<dynamic> get reactions; List<SnPostTag> get tags; List<SnPostCategory> get categories; List<dynamic> get collections; DateTime? get createdAt; DateTime? get updatedAt; DateTime? get deletedAt; bool get isTruncated;
/// Create a copy of SnPost
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@@ -28,16 +28,16 @@ $SnPostCopyWith<SnPost> get copyWith => _$SnPostCopyWithImpl<SnPost>(this as SnP
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnPost&&(identical(other.id, id) || other.id == id)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)&&(identical(other.language, language) || other.language == language)&&(identical(other.editedAt, editedAt) || other.editedAt == editedAt)&&(identical(other.publishedAt, publishedAt) || other.publishedAt == publishedAt)&&(identical(other.visibility, visibility) || other.visibility == visibility)&&(identical(other.content, content) || other.content == content)&&(identical(other.type, type) || other.type == type)&&const DeepCollectionEquality().equals(other.meta, meta)&&(identical(other.viewsUnique, viewsUnique) || other.viewsUnique == viewsUnique)&&(identical(other.viewsTotal, viewsTotal) || other.viewsTotal == viewsTotal)&&(identical(other.upvotes, upvotes) || other.upvotes == upvotes)&&(identical(other.downvotes, downvotes) || other.downvotes == downvotes)&&(identical(other.repliesCount, repliesCount) || other.repliesCount == repliesCount)&&(identical(other.threadedPostId, threadedPostId) || other.threadedPostId == threadedPostId)&&(identical(other.threadedPost, threadedPost) || other.threadedPost == threadedPost)&&(identical(other.repliedPostId, repliedPostId) || other.repliedPostId == repliedPostId)&&(identical(other.repliedPost, repliedPost) || other.repliedPost == repliedPost)&&(identical(other.forwardedPostId, forwardedPostId) || other.forwardedPostId == forwardedPostId)&&(identical(other.forwardedPost, forwardedPost) || other.forwardedPost == forwardedPost)&&const DeepCollectionEquality().equals(other.attachments, attachments)&&(identical(other.publisher, publisher) || other.publisher == publisher)&&const DeepCollectionEquality().equals(other.reactionsCount, reactionsCount)&&const DeepCollectionEquality().equals(other.reactionsMade, reactionsMade)&&const DeepCollectionEquality().equals(other.reactions, reactions)&&const DeepCollectionEquality().equals(other.tags, tags)&&const DeepCollectionEquality().equals(other.categories, categories)&&const DeepCollectionEquality().equals(other.collections, collections)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.isTruncated, isTruncated) || other.isTruncated == isTruncated));
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnPost&&(identical(other.id, id) || other.id == id)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)&&(identical(other.language, language) || other.language == language)&&(identical(other.editedAt, editedAt) || other.editedAt == editedAt)&&(identical(other.publishedAt, publishedAt) || other.publishedAt == publishedAt)&&(identical(other.visibility, visibility) || other.visibility == visibility)&&(identical(other.content, content) || other.content == content)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.type, type) || other.type == type)&&const DeepCollectionEquality().equals(other.meta, meta)&&(identical(other.viewsUnique, viewsUnique) || other.viewsUnique == viewsUnique)&&(identical(other.viewsTotal, viewsTotal) || other.viewsTotal == viewsTotal)&&(identical(other.upvotes, upvotes) || other.upvotes == upvotes)&&(identical(other.downvotes, downvotes) || other.downvotes == downvotes)&&(identical(other.repliesCount, repliesCount) || other.repliesCount == repliesCount)&&(identical(other.threadedPostId, threadedPostId) || other.threadedPostId == threadedPostId)&&(identical(other.threadedPost, threadedPost) || other.threadedPost == threadedPost)&&(identical(other.repliedPostId, repliedPostId) || other.repliedPostId == repliedPostId)&&(identical(other.repliedPost, repliedPost) || other.repliedPost == repliedPost)&&(identical(other.forwardedPostId, forwardedPostId) || other.forwardedPostId == forwardedPostId)&&(identical(other.forwardedPost, forwardedPost) || other.forwardedPost == forwardedPost)&&(identical(other.realmId, realmId) || other.realmId == realmId)&&(identical(other.realm, realm) || other.realm == realm)&&const DeepCollectionEquality().equals(other.attachments, attachments)&&(identical(other.publisher, publisher) || other.publisher == publisher)&&const DeepCollectionEquality().equals(other.reactionsCount, reactionsCount)&&const DeepCollectionEquality().equals(other.reactionsMade, reactionsMade)&&const DeepCollectionEquality().equals(other.reactions, reactions)&&const DeepCollectionEquality().equals(other.tags, tags)&&const DeepCollectionEquality().equals(other.categories, categories)&&const DeepCollectionEquality().equals(other.collections, collections)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.isTruncated, isTruncated) || other.isTruncated == isTruncated));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hashAll([runtimeType,id,title,description,language,editedAt,publishedAt,visibility,content,type,const DeepCollectionEquality().hash(meta),viewsUnique,viewsTotal,upvotes,downvotes,repliesCount,threadedPostId,threadedPost,repliedPostId,repliedPost,forwardedPostId,forwardedPost,const DeepCollectionEquality().hash(attachments),publisher,const DeepCollectionEquality().hash(reactionsCount),const DeepCollectionEquality().hash(reactionsMade),const DeepCollectionEquality().hash(reactions),const DeepCollectionEquality().hash(tags),const DeepCollectionEquality().hash(categories),const DeepCollectionEquality().hash(collections),createdAt,updatedAt,deletedAt,isTruncated]);
int get hashCode => Object.hashAll([runtimeType,id,title,description,language,editedAt,publishedAt,visibility,content,slug,type,const DeepCollectionEquality().hash(meta),viewsUnique,viewsTotal,upvotes,downvotes,repliesCount,threadedPostId,threadedPost,repliedPostId,repliedPost,forwardedPostId,forwardedPost,realmId,realm,const DeepCollectionEquality().hash(attachments),publisher,const DeepCollectionEquality().hash(reactionsCount),const DeepCollectionEquality().hash(reactionsMade),const DeepCollectionEquality().hash(reactions),const DeepCollectionEquality().hash(tags),const DeepCollectionEquality().hash(categories),const DeepCollectionEquality().hash(collections),createdAt,updatedAt,deletedAt,isTruncated]);
@override
String toString() {
return 'SnPost(id: $id, title: $title, description: $description, language: $language, editedAt: $editedAt, publishedAt: $publishedAt, visibility: $visibility, content: $content, type: $type, meta: $meta, viewsUnique: $viewsUnique, viewsTotal: $viewsTotal, upvotes: $upvotes, downvotes: $downvotes, repliesCount: $repliesCount, threadedPostId: $threadedPostId, threadedPost: $threadedPost, repliedPostId: $repliedPostId, repliedPost: $repliedPost, forwardedPostId: $forwardedPostId, forwardedPost: $forwardedPost, attachments: $attachments, publisher: $publisher, reactionsCount: $reactionsCount, reactionsMade: $reactionsMade, reactions: $reactions, tags: $tags, categories: $categories, collections: $collections, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, isTruncated: $isTruncated)';
return 'SnPost(id: $id, title: $title, description: $description, language: $language, editedAt: $editedAt, publishedAt: $publishedAt, visibility: $visibility, content: $content, slug: $slug, type: $type, meta: $meta, viewsUnique: $viewsUnique, viewsTotal: $viewsTotal, upvotes: $upvotes, downvotes: $downvotes, repliesCount: $repliesCount, threadedPostId: $threadedPostId, threadedPost: $threadedPost, repliedPostId: $repliedPostId, repliedPost: $repliedPost, forwardedPostId: $forwardedPostId, forwardedPost: $forwardedPost, realmId: $realmId, realm: $realm, attachments: $attachments, publisher: $publisher, reactionsCount: $reactionsCount, reactionsMade: $reactionsMade, reactions: $reactions, tags: $tags, categories: $categories, collections: $collections, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, isTruncated: $isTruncated)';
}
@@ -48,11 +48,11 @@ abstract mixin class $SnPostCopyWith<$Res> {
factory $SnPostCopyWith(SnPost value, $Res Function(SnPost) _then) = _$SnPostCopyWithImpl;
@useResult
$Res call({
String id, String? title, String? description, String? language, DateTime? editedAt, DateTime? publishedAt, int visibility, String? content, int type, Map<String, dynamic>? meta, int viewsUnique, int viewsTotal, int upvotes, int downvotes, int repliesCount, String? threadedPostId, SnPost? threadedPost, String? repliedPostId, SnPost? repliedPost, String? forwardedPostId, SnPost? forwardedPost, List<SnCloudFile> attachments, SnPublisher publisher, Map<String, int> reactionsCount, Map<String, bool> reactionsMade, List<dynamic> reactions, List<SnPostTag> tags, List<SnPostCategory> categories, List<dynamic> collections, DateTime? createdAt, DateTime? updatedAt, DateTime? deletedAt, bool isTruncated
String id, String? title, String? description, String? language, DateTime? editedAt, DateTime? publishedAt, int visibility, String? content, String? slug, int type, Map<String, dynamic>? meta, int viewsUnique, int viewsTotal, int upvotes, int downvotes, int repliesCount, String? threadedPostId, SnPost? threadedPost, String? repliedPostId, SnPost? repliedPost, String? forwardedPostId, SnPost? forwardedPost, String? realmId, SnRealm? realm, List<SnCloudFile> attachments, SnPublisher publisher, Map<String, int> reactionsCount, Map<String, bool> reactionsMade, List<dynamic> reactions, List<SnPostTag> tags, List<SnPostCategory> categories, List<dynamic> collections, DateTime? createdAt, DateTime? updatedAt, DateTime? deletedAt, bool isTruncated
});
$SnPostCopyWith<$Res>? get threadedPost;$SnPostCopyWith<$Res>? get repliedPost;$SnPostCopyWith<$Res>? get forwardedPost;$SnPublisherCopyWith<$Res> get publisher;
$SnPostCopyWith<$Res>? get threadedPost;$SnPostCopyWith<$Res>? get repliedPost;$SnPostCopyWith<$Res>? get forwardedPost;$SnRealmCopyWith<$Res>? get realm;$SnPublisherCopyWith<$Res> get publisher;
}
/// @nodoc
@@ -65,7 +65,7 @@ class _$SnPostCopyWithImpl<$Res>
/// Create a copy of SnPost
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? title = freezed,Object? description = freezed,Object? language = freezed,Object? editedAt = freezed,Object? publishedAt = freezed,Object? visibility = null,Object? content = freezed,Object? type = null,Object? meta = freezed,Object? viewsUnique = null,Object? viewsTotal = null,Object? upvotes = null,Object? downvotes = null,Object? repliesCount = null,Object? threadedPostId = freezed,Object? threadedPost = freezed,Object? repliedPostId = freezed,Object? repliedPost = freezed,Object? forwardedPostId = freezed,Object? forwardedPost = freezed,Object? attachments = null,Object? publisher = null,Object? reactionsCount = null,Object? reactionsMade = null,Object? reactions = null,Object? tags = null,Object? categories = null,Object? collections = null,Object? createdAt = freezed,Object? updatedAt = freezed,Object? deletedAt = freezed,Object? isTruncated = null,}) {
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? title = freezed,Object? description = freezed,Object? language = freezed,Object? editedAt = freezed,Object? publishedAt = freezed,Object? visibility = null,Object? content = freezed,Object? slug = freezed,Object? type = null,Object? meta = freezed,Object? viewsUnique = null,Object? viewsTotal = null,Object? upvotes = null,Object? downvotes = null,Object? repliesCount = null,Object? threadedPostId = freezed,Object? threadedPost = freezed,Object? repliedPostId = freezed,Object? repliedPost = freezed,Object? forwardedPostId = freezed,Object? forwardedPost = freezed,Object? realmId = freezed,Object? realm = freezed,Object? attachments = null,Object? publisher = null,Object? reactionsCount = null,Object? reactionsMade = null,Object? reactions = null,Object? tags = null,Object? categories = null,Object? collections = null,Object? createdAt = freezed,Object? updatedAt = freezed,Object? deletedAt = freezed,Object? isTruncated = null,}) {
return _then(_self.copyWith(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,title: freezed == title ? _self.title : title // ignore: cast_nullable_to_non_nullable
@@ -75,6 +75,7 @@ as String?,editedAt: freezed == editedAt ? _self.editedAt : editedAt // ignore:
as DateTime?,publishedAt: freezed == publishedAt ? _self.publishedAt : publishedAt // ignore: cast_nullable_to_non_nullable
as DateTime?,visibility: null == visibility ? _self.visibility : visibility // ignore: cast_nullable_to_non_nullable
as int,content: freezed == content ? _self.content : content // ignore: cast_nullable_to_non_nullable
as String?,slug: freezed == slug ? _self.slug : slug // ignore: cast_nullable_to_non_nullable
as String?,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
as int,meta: freezed == meta ? _self.meta : meta // ignore: cast_nullable_to_non_nullable
as Map<String, dynamic>?,viewsUnique: null == viewsUnique ? _self.viewsUnique : viewsUnique // ignore: cast_nullable_to_non_nullable
@@ -88,7 +89,9 @@ as SnPost?,repliedPostId: freezed == repliedPostId ? _self.repliedPostId : repli
as String?,repliedPost: freezed == repliedPost ? _self.repliedPost : repliedPost // ignore: cast_nullable_to_non_nullable
as SnPost?,forwardedPostId: freezed == forwardedPostId ? _self.forwardedPostId : forwardedPostId // ignore: cast_nullable_to_non_nullable
as String?,forwardedPost: freezed == forwardedPost ? _self.forwardedPost : forwardedPost // ignore: cast_nullable_to_non_nullable
as SnPost?,attachments: null == attachments ? _self.attachments : attachments // ignore: cast_nullable_to_non_nullable
as SnPost?,realmId: freezed == realmId ? _self.realmId : realmId // ignore: cast_nullable_to_non_nullable
as String?,realm: freezed == realm ? _self.realm : realm // ignore: cast_nullable_to_non_nullable
as SnRealm?,attachments: null == attachments ? _self.attachments : attachments // ignore: cast_nullable_to_non_nullable
as List<SnCloudFile>,publisher: null == publisher ? _self.publisher : publisher // ignore: cast_nullable_to_non_nullable
as SnPublisher,reactionsCount: null == reactionsCount ? _self.reactionsCount : reactionsCount // ignore: cast_nullable_to_non_nullable
as Map<String, int>,reactionsMade: null == reactionsMade ? _self.reactionsMade : reactionsMade // ignore: cast_nullable_to_non_nullable
@@ -143,6 +146,18 @@ $SnPostCopyWith<$Res>? get forwardedPost {
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$SnRealmCopyWith<$Res>? get realm {
if (_self.realm == null) {
return null;
}
return $SnRealmCopyWith<$Res>(_self.realm!, (value) {
return _then(_self.copyWith(realm: value));
});
}/// Create a copy of SnPost
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$SnPublisherCopyWith<$Res> get publisher {
return $SnPublisherCopyWith<$Res>(_self.publisher, (value) {
@@ -227,10 +242,10 @@ return $default(_that);case _:
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, String? title, String? description, String? language, DateTime? editedAt, DateTime? publishedAt, int visibility, String? content, int type, Map<String, dynamic>? meta, int viewsUnique, int viewsTotal, int upvotes, int downvotes, int repliesCount, String? threadedPostId, SnPost? threadedPost, String? repliedPostId, SnPost? repliedPost, String? forwardedPostId, SnPost? forwardedPost, List<SnCloudFile> attachments, SnPublisher publisher, Map<String, int> reactionsCount, Map<String, bool> reactionsMade, List<dynamic> reactions, List<SnPostTag> tags, List<SnPostCategory> categories, List<dynamic> collections, DateTime? createdAt, DateTime? updatedAt, DateTime? deletedAt, bool isTruncated)? $default,{required TResult orElse(),}) {final _that = this;
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, String? title, String? description, String? language, DateTime? editedAt, DateTime? publishedAt, int visibility, String? content, String? slug, int type, Map<String, dynamic>? meta, int viewsUnique, int viewsTotal, int upvotes, int downvotes, int repliesCount, String? threadedPostId, SnPost? threadedPost, String? repliedPostId, SnPost? repliedPost, String? forwardedPostId, SnPost? forwardedPost, String? realmId, SnRealm? realm, List<SnCloudFile> attachments, SnPublisher publisher, Map<String, int> reactionsCount, Map<String, bool> reactionsMade, List<dynamic> reactions, List<SnPostTag> tags, List<SnPostCategory> categories, List<dynamic> collections, DateTime? createdAt, DateTime? updatedAt, DateTime? deletedAt, bool isTruncated)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _SnPost() when $default != null:
return $default(_that.id,_that.title,_that.description,_that.language,_that.editedAt,_that.publishedAt,_that.visibility,_that.content,_that.type,_that.meta,_that.viewsUnique,_that.viewsTotal,_that.upvotes,_that.downvotes,_that.repliesCount,_that.threadedPostId,_that.threadedPost,_that.repliedPostId,_that.repliedPost,_that.forwardedPostId,_that.forwardedPost,_that.attachments,_that.publisher,_that.reactionsCount,_that.reactionsMade,_that.reactions,_that.tags,_that.categories,_that.collections,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.isTruncated);case _:
return $default(_that.id,_that.title,_that.description,_that.language,_that.editedAt,_that.publishedAt,_that.visibility,_that.content,_that.slug,_that.type,_that.meta,_that.viewsUnique,_that.viewsTotal,_that.upvotes,_that.downvotes,_that.repliesCount,_that.threadedPostId,_that.threadedPost,_that.repliedPostId,_that.repliedPost,_that.forwardedPostId,_that.forwardedPost,_that.realmId,_that.realm,_that.attachments,_that.publisher,_that.reactionsCount,_that.reactionsMade,_that.reactions,_that.tags,_that.categories,_that.collections,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.isTruncated);case _:
return orElse();
}
@@ -248,10 +263,10 @@ return $default(_that.id,_that.title,_that.description,_that.language,_that.edit
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, String? title, String? description, String? language, DateTime? editedAt, DateTime? publishedAt, int visibility, String? content, int type, Map<String, dynamic>? meta, int viewsUnique, int viewsTotal, int upvotes, int downvotes, int repliesCount, String? threadedPostId, SnPost? threadedPost, String? repliedPostId, SnPost? repliedPost, String? forwardedPostId, SnPost? forwardedPost, List<SnCloudFile> attachments, SnPublisher publisher, Map<String, int> reactionsCount, Map<String, bool> reactionsMade, List<dynamic> reactions, List<SnPostTag> tags, List<SnPostCategory> categories, List<dynamic> collections, DateTime? createdAt, DateTime? updatedAt, DateTime? deletedAt, bool isTruncated) $default,) {final _that = this;
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, String? title, String? description, String? language, DateTime? editedAt, DateTime? publishedAt, int visibility, String? content, String? slug, int type, Map<String, dynamic>? meta, int viewsUnique, int viewsTotal, int upvotes, int downvotes, int repliesCount, String? threadedPostId, SnPost? threadedPost, String? repliedPostId, SnPost? repliedPost, String? forwardedPostId, SnPost? forwardedPost, String? realmId, SnRealm? realm, List<SnCloudFile> attachments, SnPublisher publisher, Map<String, int> reactionsCount, Map<String, bool> reactionsMade, List<dynamic> reactions, List<SnPostTag> tags, List<SnPostCategory> categories, List<dynamic> collections, DateTime? createdAt, DateTime? updatedAt, DateTime? deletedAt, bool isTruncated) $default,) {final _that = this;
switch (_that) {
case _SnPost():
return $default(_that.id,_that.title,_that.description,_that.language,_that.editedAt,_that.publishedAt,_that.visibility,_that.content,_that.type,_that.meta,_that.viewsUnique,_that.viewsTotal,_that.upvotes,_that.downvotes,_that.repliesCount,_that.threadedPostId,_that.threadedPost,_that.repliedPostId,_that.repliedPost,_that.forwardedPostId,_that.forwardedPost,_that.attachments,_that.publisher,_that.reactionsCount,_that.reactionsMade,_that.reactions,_that.tags,_that.categories,_that.collections,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.isTruncated);}
return $default(_that.id,_that.title,_that.description,_that.language,_that.editedAt,_that.publishedAt,_that.visibility,_that.content,_that.slug,_that.type,_that.meta,_that.viewsUnique,_that.viewsTotal,_that.upvotes,_that.downvotes,_that.repliesCount,_that.threadedPostId,_that.threadedPost,_that.repliedPostId,_that.repliedPost,_that.forwardedPostId,_that.forwardedPost,_that.realmId,_that.realm,_that.attachments,_that.publisher,_that.reactionsCount,_that.reactionsMade,_that.reactions,_that.tags,_that.categories,_that.collections,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.isTruncated);}
}
/// A variant of `when` that fallback to returning `null`
///
@@ -265,10 +280,10 @@ return $default(_that.id,_that.title,_that.description,_that.language,_that.edit
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, String? title, String? description, String? language, DateTime? editedAt, DateTime? publishedAt, int visibility, String? content, int type, Map<String, dynamic>? meta, int viewsUnique, int viewsTotal, int upvotes, int downvotes, int repliesCount, String? threadedPostId, SnPost? threadedPost, String? repliedPostId, SnPost? repliedPost, String? forwardedPostId, SnPost? forwardedPost, List<SnCloudFile> attachments, SnPublisher publisher, Map<String, int> reactionsCount, Map<String, bool> reactionsMade, List<dynamic> reactions, List<SnPostTag> tags, List<SnPostCategory> categories, List<dynamic> collections, DateTime? createdAt, DateTime? updatedAt, DateTime? deletedAt, bool isTruncated)? $default,) {final _that = this;
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, String? title, String? description, String? language, DateTime? editedAt, DateTime? publishedAt, int visibility, String? content, String? slug, int type, Map<String, dynamic>? meta, int viewsUnique, int viewsTotal, int upvotes, int downvotes, int repliesCount, String? threadedPostId, SnPost? threadedPost, String? repliedPostId, SnPost? repliedPost, String? forwardedPostId, SnPost? forwardedPost, String? realmId, SnRealm? realm, List<SnCloudFile> attachments, SnPublisher publisher, Map<String, int> reactionsCount, Map<String, bool> reactionsMade, List<dynamic> reactions, List<SnPostTag> tags, List<SnPostCategory> categories, List<dynamic> collections, DateTime? createdAt, DateTime? updatedAt, DateTime? deletedAt, bool isTruncated)? $default,) {final _that = this;
switch (_that) {
case _SnPost() when $default != null:
return $default(_that.id,_that.title,_that.description,_that.language,_that.editedAt,_that.publishedAt,_that.visibility,_that.content,_that.type,_that.meta,_that.viewsUnique,_that.viewsTotal,_that.upvotes,_that.downvotes,_that.repliesCount,_that.threadedPostId,_that.threadedPost,_that.repliedPostId,_that.repliedPost,_that.forwardedPostId,_that.forwardedPost,_that.attachments,_that.publisher,_that.reactionsCount,_that.reactionsMade,_that.reactions,_that.tags,_that.categories,_that.collections,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.isTruncated);case _:
return $default(_that.id,_that.title,_that.description,_that.language,_that.editedAt,_that.publishedAt,_that.visibility,_that.content,_that.slug,_that.type,_that.meta,_that.viewsUnique,_that.viewsTotal,_that.upvotes,_that.downvotes,_that.repliesCount,_that.threadedPostId,_that.threadedPost,_that.repliedPostId,_that.repliedPost,_that.forwardedPostId,_that.forwardedPost,_that.realmId,_that.realm,_that.attachments,_that.publisher,_that.reactionsCount,_that.reactionsMade,_that.reactions,_that.tags,_that.categories,_that.collections,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.isTruncated);case _:
return null;
}
@@ -280,7 +295,7 @@ return $default(_that.id,_that.title,_that.description,_that.language,_that.edit
@JsonSerializable()
class _SnPost implements SnPost {
const _SnPost({required this.id, this.title, this.description, this.language, this.editedAt, this.publishedAt = null, this.visibility = 0, this.content, this.type = 0, final Map<String, dynamic>? meta, this.viewsUnique = 0, this.viewsTotal = 0, this.upvotes = 0, this.downvotes = 0, this.repliesCount = 0, this.threadedPostId, this.threadedPost, this.repliedPostId, this.repliedPost, this.forwardedPostId, this.forwardedPost, final List<SnCloudFile> attachments = const [], required this.publisher, final Map<String, int> reactionsCount = const {}, final Map<String, bool> reactionsMade = const {}, final List<dynamic> reactions = const [], final List<SnPostTag> tags = const [], final List<SnPostCategory> categories = const [], final List<dynamic> collections = const [], this.createdAt = null, this.updatedAt = null, this.deletedAt, this.isTruncated = false}): _meta = meta,_attachments = attachments,_reactionsCount = reactionsCount,_reactionsMade = reactionsMade,_reactions = reactions,_tags = tags,_categories = categories,_collections = collections;
const _SnPost({required this.id, this.title, this.description, this.language, this.editedAt, this.publishedAt = null, this.visibility = 0, this.content, this.slug, this.type = 0, final Map<String, dynamic>? meta, this.viewsUnique = 0, this.viewsTotal = 0, this.upvotes = 0, this.downvotes = 0, this.repliesCount = 0, this.threadedPostId, this.threadedPost, this.repliedPostId, this.repliedPost, this.forwardedPostId, this.forwardedPost, this.realmId, this.realm, final List<SnCloudFile> attachments = const [], required this.publisher, final Map<String, int> reactionsCount = const {}, final Map<String, bool> reactionsMade = const {}, final List<dynamic> reactions = const [], final List<SnPostTag> tags = const [], final List<SnPostCategory> categories = const [], final List<dynamic> collections = const [], this.createdAt = null, this.updatedAt = null, this.deletedAt, this.isTruncated = false}): _meta = meta,_attachments = attachments,_reactionsCount = reactionsCount,_reactionsMade = reactionsMade,_reactions = reactions,_tags = tags,_categories = categories,_collections = collections;
factory _SnPost.fromJson(Map<String, dynamic> json) => _$SnPostFromJson(json);
@override final String id;
@@ -291,6 +306,7 @@ class _SnPost implements SnPost {
@override@JsonKey() final DateTime? publishedAt;
@override@JsonKey() final int visibility;
@override final String? content;
@override final String? slug;
@override@JsonKey() final int type;
final Map<String, dynamic>? _meta;
@override Map<String, dynamic>? get meta {
@@ -312,6 +328,8 @@ class _SnPost implements SnPost {
@override final SnPost? repliedPost;
@override final String? forwardedPostId;
@override final SnPost? forwardedPost;
@override final String? realmId;
@override final SnRealm? realm;
final List<SnCloudFile> _attachments;
@override@JsonKey() List<SnCloudFile> get attachments {
if (_attachments is EqualUnmodifiableListView) return _attachments;
@@ -380,16 +398,16 @@ Map<String, dynamic> toJson() {
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnPost&&(identical(other.id, id) || other.id == id)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)&&(identical(other.language, language) || other.language == language)&&(identical(other.editedAt, editedAt) || other.editedAt == editedAt)&&(identical(other.publishedAt, publishedAt) || other.publishedAt == publishedAt)&&(identical(other.visibility, visibility) || other.visibility == visibility)&&(identical(other.content, content) || other.content == content)&&(identical(other.type, type) || other.type == type)&&const DeepCollectionEquality().equals(other._meta, _meta)&&(identical(other.viewsUnique, viewsUnique) || other.viewsUnique == viewsUnique)&&(identical(other.viewsTotal, viewsTotal) || other.viewsTotal == viewsTotal)&&(identical(other.upvotes, upvotes) || other.upvotes == upvotes)&&(identical(other.downvotes, downvotes) || other.downvotes == downvotes)&&(identical(other.repliesCount, repliesCount) || other.repliesCount == repliesCount)&&(identical(other.threadedPostId, threadedPostId) || other.threadedPostId == threadedPostId)&&(identical(other.threadedPost, threadedPost) || other.threadedPost == threadedPost)&&(identical(other.repliedPostId, repliedPostId) || other.repliedPostId == repliedPostId)&&(identical(other.repliedPost, repliedPost) || other.repliedPost == repliedPost)&&(identical(other.forwardedPostId, forwardedPostId) || other.forwardedPostId == forwardedPostId)&&(identical(other.forwardedPost, forwardedPost) || other.forwardedPost == forwardedPost)&&const DeepCollectionEquality().equals(other._attachments, _attachments)&&(identical(other.publisher, publisher) || other.publisher == publisher)&&const DeepCollectionEquality().equals(other._reactionsCount, _reactionsCount)&&const DeepCollectionEquality().equals(other._reactionsMade, _reactionsMade)&&const DeepCollectionEquality().equals(other._reactions, _reactions)&&const DeepCollectionEquality().equals(other._tags, _tags)&&const DeepCollectionEquality().equals(other._categories, _categories)&&const DeepCollectionEquality().equals(other._collections, _collections)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.isTruncated, isTruncated) || other.isTruncated == isTruncated));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnPost&&(identical(other.id, id) || other.id == id)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)&&(identical(other.language, language) || other.language == language)&&(identical(other.editedAt, editedAt) || other.editedAt == editedAt)&&(identical(other.publishedAt, publishedAt) || other.publishedAt == publishedAt)&&(identical(other.visibility, visibility) || other.visibility == visibility)&&(identical(other.content, content) || other.content == content)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.type, type) || other.type == type)&&const DeepCollectionEquality().equals(other._meta, _meta)&&(identical(other.viewsUnique, viewsUnique) || other.viewsUnique == viewsUnique)&&(identical(other.viewsTotal, viewsTotal) || other.viewsTotal == viewsTotal)&&(identical(other.upvotes, upvotes) || other.upvotes == upvotes)&&(identical(other.downvotes, downvotes) || other.downvotes == downvotes)&&(identical(other.repliesCount, repliesCount) || other.repliesCount == repliesCount)&&(identical(other.threadedPostId, threadedPostId) || other.threadedPostId == threadedPostId)&&(identical(other.threadedPost, threadedPost) || other.threadedPost == threadedPost)&&(identical(other.repliedPostId, repliedPostId) || other.repliedPostId == repliedPostId)&&(identical(other.repliedPost, repliedPost) || other.repliedPost == repliedPost)&&(identical(other.forwardedPostId, forwardedPostId) || other.forwardedPostId == forwardedPostId)&&(identical(other.forwardedPost, forwardedPost) || other.forwardedPost == forwardedPost)&&(identical(other.realmId, realmId) || other.realmId == realmId)&&(identical(other.realm, realm) || other.realm == realm)&&const DeepCollectionEquality().equals(other._attachments, _attachments)&&(identical(other.publisher, publisher) || other.publisher == publisher)&&const DeepCollectionEquality().equals(other._reactionsCount, _reactionsCount)&&const DeepCollectionEquality().equals(other._reactionsMade, _reactionsMade)&&const DeepCollectionEquality().equals(other._reactions, _reactions)&&const DeepCollectionEquality().equals(other._tags, _tags)&&const DeepCollectionEquality().equals(other._categories, _categories)&&const DeepCollectionEquality().equals(other._collections, _collections)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.isTruncated, isTruncated) || other.isTruncated == isTruncated));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hashAll([runtimeType,id,title,description,language,editedAt,publishedAt,visibility,content,type,const DeepCollectionEquality().hash(_meta),viewsUnique,viewsTotal,upvotes,downvotes,repliesCount,threadedPostId,threadedPost,repliedPostId,repliedPost,forwardedPostId,forwardedPost,const DeepCollectionEquality().hash(_attachments),publisher,const DeepCollectionEquality().hash(_reactionsCount),const DeepCollectionEquality().hash(_reactionsMade),const DeepCollectionEquality().hash(_reactions),const DeepCollectionEquality().hash(_tags),const DeepCollectionEquality().hash(_categories),const DeepCollectionEquality().hash(_collections),createdAt,updatedAt,deletedAt,isTruncated]);
int get hashCode => Object.hashAll([runtimeType,id,title,description,language,editedAt,publishedAt,visibility,content,slug,type,const DeepCollectionEquality().hash(_meta),viewsUnique,viewsTotal,upvotes,downvotes,repliesCount,threadedPostId,threadedPost,repliedPostId,repliedPost,forwardedPostId,forwardedPost,realmId,realm,const DeepCollectionEquality().hash(_attachments),publisher,const DeepCollectionEquality().hash(_reactionsCount),const DeepCollectionEquality().hash(_reactionsMade),const DeepCollectionEquality().hash(_reactions),const DeepCollectionEquality().hash(_tags),const DeepCollectionEquality().hash(_categories),const DeepCollectionEquality().hash(_collections),createdAt,updatedAt,deletedAt,isTruncated]);
@override
String toString() {
return 'SnPost(id: $id, title: $title, description: $description, language: $language, editedAt: $editedAt, publishedAt: $publishedAt, visibility: $visibility, content: $content, type: $type, meta: $meta, viewsUnique: $viewsUnique, viewsTotal: $viewsTotal, upvotes: $upvotes, downvotes: $downvotes, repliesCount: $repliesCount, threadedPostId: $threadedPostId, threadedPost: $threadedPost, repliedPostId: $repliedPostId, repliedPost: $repliedPost, forwardedPostId: $forwardedPostId, forwardedPost: $forwardedPost, attachments: $attachments, publisher: $publisher, reactionsCount: $reactionsCount, reactionsMade: $reactionsMade, reactions: $reactions, tags: $tags, categories: $categories, collections: $collections, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, isTruncated: $isTruncated)';
return 'SnPost(id: $id, title: $title, description: $description, language: $language, editedAt: $editedAt, publishedAt: $publishedAt, visibility: $visibility, content: $content, slug: $slug, type: $type, meta: $meta, viewsUnique: $viewsUnique, viewsTotal: $viewsTotal, upvotes: $upvotes, downvotes: $downvotes, repliesCount: $repliesCount, threadedPostId: $threadedPostId, threadedPost: $threadedPost, repliedPostId: $repliedPostId, repliedPost: $repliedPost, forwardedPostId: $forwardedPostId, forwardedPost: $forwardedPost, realmId: $realmId, realm: $realm, attachments: $attachments, publisher: $publisher, reactionsCount: $reactionsCount, reactionsMade: $reactionsMade, reactions: $reactions, tags: $tags, categories: $categories, collections: $collections, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, isTruncated: $isTruncated)';
}
@@ -400,11 +418,11 @@ abstract mixin class _$SnPostCopyWith<$Res> implements $SnPostCopyWith<$Res> {
factory _$SnPostCopyWith(_SnPost value, $Res Function(_SnPost) _then) = __$SnPostCopyWithImpl;
@override @useResult
$Res call({
String id, String? title, String? description, String? language, DateTime? editedAt, DateTime? publishedAt, int visibility, String? content, int type, Map<String, dynamic>? meta, int viewsUnique, int viewsTotal, int upvotes, int downvotes, int repliesCount, String? threadedPostId, SnPost? threadedPost, String? repliedPostId, SnPost? repliedPost, String? forwardedPostId, SnPost? forwardedPost, List<SnCloudFile> attachments, SnPublisher publisher, Map<String, int> reactionsCount, Map<String, bool> reactionsMade, List<dynamic> reactions, List<SnPostTag> tags, List<SnPostCategory> categories, List<dynamic> collections, DateTime? createdAt, DateTime? updatedAt, DateTime? deletedAt, bool isTruncated
String id, String? title, String? description, String? language, DateTime? editedAt, DateTime? publishedAt, int visibility, String? content, String? slug, int type, Map<String, dynamic>? meta, int viewsUnique, int viewsTotal, int upvotes, int downvotes, int repliesCount, String? threadedPostId, SnPost? threadedPost, String? repliedPostId, SnPost? repliedPost, String? forwardedPostId, SnPost? forwardedPost, String? realmId, SnRealm? realm, List<SnCloudFile> attachments, SnPublisher publisher, Map<String, int> reactionsCount, Map<String, bool> reactionsMade, List<dynamic> reactions, List<SnPostTag> tags, List<SnPostCategory> categories, List<dynamic> collections, DateTime? createdAt, DateTime? updatedAt, DateTime? deletedAt, bool isTruncated
});
@override $SnPostCopyWith<$Res>? get threadedPost;@override $SnPostCopyWith<$Res>? get repliedPost;@override $SnPostCopyWith<$Res>? get forwardedPost;@override $SnPublisherCopyWith<$Res> get publisher;
@override $SnPostCopyWith<$Res>? get threadedPost;@override $SnPostCopyWith<$Res>? get repliedPost;@override $SnPostCopyWith<$Res>? get forwardedPost;@override $SnRealmCopyWith<$Res>? get realm;@override $SnPublisherCopyWith<$Res> get publisher;
}
/// @nodoc
@@ -417,7 +435,7 @@ class __$SnPostCopyWithImpl<$Res>
/// Create a copy of SnPost
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? title = freezed,Object? description = freezed,Object? language = freezed,Object? editedAt = freezed,Object? publishedAt = freezed,Object? visibility = null,Object? content = freezed,Object? type = null,Object? meta = freezed,Object? viewsUnique = null,Object? viewsTotal = null,Object? upvotes = null,Object? downvotes = null,Object? repliesCount = null,Object? threadedPostId = freezed,Object? threadedPost = freezed,Object? repliedPostId = freezed,Object? repliedPost = freezed,Object? forwardedPostId = freezed,Object? forwardedPost = freezed,Object? attachments = null,Object? publisher = null,Object? reactionsCount = null,Object? reactionsMade = null,Object? reactions = null,Object? tags = null,Object? categories = null,Object? collections = null,Object? createdAt = freezed,Object? updatedAt = freezed,Object? deletedAt = freezed,Object? isTruncated = null,}) {
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? title = freezed,Object? description = freezed,Object? language = freezed,Object? editedAt = freezed,Object? publishedAt = freezed,Object? visibility = null,Object? content = freezed,Object? slug = freezed,Object? type = null,Object? meta = freezed,Object? viewsUnique = null,Object? viewsTotal = null,Object? upvotes = null,Object? downvotes = null,Object? repliesCount = null,Object? threadedPostId = freezed,Object? threadedPost = freezed,Object? repliedPostId = freezed,Object? repliedPost = freezed,Object? forwardedPostId = freezed,Object? forwardedPost = freezed,Object? realmId = freezed,Object? realm = freezed,Object? attachments = null,Object? publisher = null,Object? reactionsCount = null,Object? reactionsMade = null,Object? reactions = null,Object? tags = null,Object? categories = null,Object? collections = null,Object? createdAt = freezed,Object? updatedAt = freezed,Object? deletedAt = freezed,Object? isTruncated = null,}) {
return _then(_SnPost(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,title: freezed == title ? _self.title : title // ignore: cast_nullable_to_non_nullable
@@ -427,6 +445,7 @@ as String?,editedAt: freezed == editedAt ? _self.editedAt : editedAt // ignore:
as DateTime?,publishedAt: freezed == publishedAt ? _self.publishedAt : publishedAt // ignore: cast_nullable_to_non_nullable
as DateTime?,visibility: null == visibility ? _self.visibility : visibility // ignore: cast_nullable_to_non_nullable
as int,content: freezed == content ? _self.content : content // ignore: cast_nullable_to_non_nullable
as String?,slug: freezed == slug ? _self.slug : slug // ignore: cast_nullable_to_non_nullable
as String?,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
as int,meta: freezed == meta ? _self._meta : meta // ignore: cast_nullable_to_non_nullable
as Map<String, dynamic>?,viewsUnique: null == viewsUnique ? _self.viewsUnique : viewsUnique // ignore: cast_nullable_to_non_nullable
@@ -440,7 +459,9 @@ as SnPost?,repliedPostId: freezed == repliedPostId ? _self.repliedPostId : repli
as String?,repliedPost: freezed == repliedPost ? _self.repliedPost : repliedPost // ignore: cast_nullable_to_non_nullable
as SnPost?,forwardedPostId: freezed == forwardedPostId ? _self.forwardedPostId : forwardedPostId // ignore: cast_nullable_to_non_nullable
as String?,forwardedPost: freezed == forwardedPost ? _self.forwardedPost : forwardedPost // ignore: cast_nullable_to_non_nullable
as SnPost?,attachments: null == attachments ? _self._attachments : attachments // ignore: cast_nullable_to_non_nullable
as SnPost?,realmId: freezed == realmId ? _self.realmId : realmId // ignore: cast_nullable_to_non_nullable
as String?,realm: freezed == realm ? _self.realm : realm // ignore: cast_nullable_to_non_nullable
as SnRealm?,attachments: null == attachments ? _self._attachments : attachments // ignore: cast_nullable_to_non_nullable
as List<SnCloudFile>,publisher: null == publisher ? _self.publisher : publisher // ignore: cast_nullable_to_non_nullable
as SnPublisher,reactionsCount: null == reactionsCount ? _self._reactionsCount : reactionsCount // ignore: cast_nullable_to_non_nullable
as Map<String, int>,reactionsMade: null == reactionsMade ? _self._reactionsMade : reactionsMade // ignore: cast_nullable_to_non_nullable
@@ -496,6 +517,18 @@ $SnPostCopyWith<$Res>? get forwardedPost {
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$SnRealmCopyWith<$Res>? get realm {
if (_self.realm == null) {
return null;
}
return $SnRealmCopyWith<$Res>(_self.realm!, (value) {
return _then(_self.copyWith(realm: value));
});
}/// Create a copy of SnPost
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$SnPublisherCopyWith<$Res> get publisher {
return $SnPublisherCopyWith<$Res>(_self.publisher, (value) {

View File

@@ -21,6 +21,7 @@ _SnPost _$SnPostFromJson(Map<String, dynamic> json) => _SnPost(
: DateTime.parse(json['published_at'] as String),
visibility: (json['visibility'] as num?)?.toInt() ?? 0,
content: json['content'] as String?,
slug: json['slug'] as String?,
type: (json['type'] as num?)?.toInt() ?? 0,
meta: json['meta'] as Map<String, dynamic>?,
viewsUnique: (json['views_unique'] as num?)?.toInt() ?? 0,
@@ -43,6 +44,11 @@ _SnPost _$SnPostFromJson(Map<String, dynamic> json) => _SnPost(
json['forwarded_post'] == null
? null
: SnPost.fromJson(json['forwarded_post'] as Map<String, dynamic>),
realmId: json['realm_id'] as String?,
realm:
json['realm'] == null
? null
: SnRealm.fromJson(json['realm'] as Map<String, dynamic>),
attachments:
(json['attachments'] as List<dynamic>?)
?.map((e) => SnCloudFile.fromJson(e as Map<String, dynamic>))
@@ -95,6 +101,7 @@ Map<String, dynamic> _$SnPostToJson(_SnPost instance) => <String, dynamic>{
'published_at': instance.publishedAt?.toIso8601String(),
'visibility': instance.visibility,
'content': instance.content,
'slug': instance.slug,
'type': instance.type,
'meta': instance.meta,
'views_unique': instance.viewsUnique,
@@ -108,6 +115,8 @@ Map<String, dynamic> _$SnPostToJson(_SnPost instance) => <String, dynamic>{
'replied_post': instance.repliedPost?.toJson(),
'forwarded_post_id': instance.forwardedPostId,
'forwarded_post': instance.forwardedPost?.toJson(),
'realm_id': instance.realmId,
'realm': instance.realm?.toJson(),
'attachments': instance.attachments.map((e) => e.toJson()).toList(),
'publisher': instance.publisher.toJson(),
'reactions_count': instance.reactionsCount,

View File

@@ -1,6 +1,6 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:island/models/file.dart';
import 'package:island/models/user.dart';
import 'package:island/models/account.dart';
part 'publisher.freezed.dart';
part 'publisher.g.dart';

View File

@@ -1,6 +1,6 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:island/models/file.dart';
import 'package:island/models/user.dart';
import 'package:island/models/account.dart';
part 'realm.freezed.dart';
part 'realm.g.dart';
@@ -40,6 +40,7 @@ sealed class SnRealmMember with _$SnRealmMember {
required DateTime createdAt,
required DateTime updatedAt,
required DateTime? deletedAt,
required SnAccountStatus? status,
}) = _SnRealmMember;
factory SnRealmMember.fromJson(Map<String, dynamic> json) =>

View File

@@ -359,7 +359,7 @@ $SnCloudFileCopyWith<$Res>? get background {
/// @nodoc
mixin _$SnRealmMember {
String get realmId; SnRealm? get realm; String get accountId; SnAccount? get account; int get role; DateTime? get joinedAt; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt;
String get realmId; SnRealm? get realm; String get accountId; SnAccount? get account; int get role; DateTime? get joinedAt; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; SnAccountStatus? get status;
/// Create a copy of SnRealmMember
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@@ -372,16 +372,16 @@ $SnRealmMemberCopyWith<SnRealmMember> get copyWith => _$SnRealmMemberCopyWithImp
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnRealmMember&&(identical(other.realmId, realmId) || other.realmId == realmId)&&(identical(other.realm, realm) || other.realm == realm)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.account, account) || other.account == account)&&(identical(other.role, role) || other.role == role)&&(identical(other.joinedAt, joinedAt) || other.joinedAt == joinedAt)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnRealmMember&&(identical(other.realmId, realmId) || other.realmId == realmId)&&(identical(other.realm, realm) || other.realm == realm)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.account, account) || other.account == account)&&(identical(other.role, role) || other.role == role)&&(identical(other.joinedAt, joinedAt) || other.joinedAt == joinedAt)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.status, status) || other.status == status));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,realmId,realm,accountId,account,role,joinedAt,createdAt,updatedAt,deletedAt);
int get hashCode => Object.hash(runtimeType,realmId,realm,accountId,account,role,joinedAt,createdAt,updatedAt,deletedAt,status);
@override
String toString() {
return 'SnRealmMember(realmId: $realmId, realm: $realm, accountId: $accountId, account: $account, role: $role, joinedAt: $joinedAt, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
return 'SnRealmMember(realmId: $realmId, realm: $realm, accountId: $accountId, account: $account, role: $role, joinedAt: $joinedAt, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, status: $status)';
}
@@ -392,11 +392,11 @@ abstract mixin class $SnRealmMemberCopyWith<$Res> {
factory $SnRealmMemberCopyWith(SnRealmMember value, $Res Function(SnRealmMember) _then) = _$SnRealmMemberCopyWithImpl;
@useResult
$Res call({
String realmId, SnRealm? realm, String accountId, SnAccount? account, int role, DateTime? joinedAt, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
String realmId, SnRealm? realm, String accountId, SnAccount? account, int role, DateTime? joinedAt, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, SnAccountStatus? status
});
$SnRealmCopyWith<$Res>? get realm;$SnAccountCopyWith<$Res>? get account;
$SnRealmCopyWith<$Res>? get realm;$SnAccountCopyWith<$Res>? get account;$SnAccountStatusCopyWith<$Res>? get status;
}
/// @nodoc
@@ -409,7 +409,7 @@ class _$SnRealmMemberCopyWithImpl<$Res>
/// Create a copy of SnRealmMember
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? realmId = null,Object? realm = freezed,Object? accountId = null,Object? account = freezed,Object? role = null,Object? joinedAt = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
@pragma('vm:prefer-inline') @override $Res call({Object? realmId = null,Object? realm = freezed,Object? accountId = null,Object? account = freezed,Object? role = null,Object? joinedAt = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? status = freezed,}) {
return _then(_self.copyWith(
realmId: null == realmId ? _self.realmId : realmId // ignore: cast_nullable_to_non_nullable
as String,realm: freezed == realm ? _self.realm : realm // ignore: cast_nullable_to_non_nullable
@@ -420,7 +420,8 @@ as int,joinedAt: freezed == joinedAt ? _self.joinedAt : joinedAt // ignore: cast
as DateTime?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
as DateTime,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?,status: freezed == status ? _self.status : status // ignore: cast_nullable_to_non_nullable
as SnAccountStatus?,
));
}
/// Create a copy of SnRealmMember
@@ -447,6 +448,18 @@ $SnAccountCopyWith<$Res>? get account {
return $SnAccountCopyWith<$Res>(_self.account!, (value) {
return _then(_self.copyWith(account: value));
});
}/// Create a copy of SnRealmMember
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$SnAccountStatusCopyWith<$Res>? get status {
if (_self.status == null) {
return null;
}
return $SnAccountStatusCopyWith<$Res>(_self.status!, (value) {
return _then(_self.copyWith(status: value));
});
}
}
@@ -526,10 +539,10 @@ return $default(_that);case _:
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String realmId, SnRealm? realm, String accountId, SnAccount? account, int role, DateTime? joinedAt, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt)? $default,{required TResult orElse(),}) {final _that = this;
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String realmId, SnRealm? realm, String accountId, SnAccount? account, int role, DateTime? joinedAt, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, SnAccountStatus? status)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _SnRealmMember() when $default != null:
return $default(_that.realmId,_that.realm,_that.accountId,_that.account,_that.role,_that.joinedAt,_that.createdAt,_that.updatedAt,_that.deletedAt);case _:
return $default(_that.realmId,_that.realm,_that.accountId,_that.account,_that.role,_that.joinedAt,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.status);case _:
return orElse();
}
@@ -547,10 +560,10 @@ return $default(_that.realmId,_that.realm,_that.accountId,_that.account,_that.ro
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String realmId, SnRealm? realm, String accountId, SnAccount? account, int role, DateTime? joinedAt, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt) $default,) {final _that = this;
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String realmId, SnRealm? realm, String accountId, SnAccount? account, int role, DateTime? joinedAt, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, SnAccountStatus? status) $default,) {final _that = this;
switch (_that) {
case _SnRealmMember():
return $default(_that.realmId,_that.realm,_that.accountId,_that.account,_that.role,_that.joinedAt,_that.createdAt,_that.updatedAt,_that.deletedAt);}
return $default(_that.realmId,_that.realm,_that.accountId,_that.account,_that.role,_that.joinedAt,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.status);}
}
/// A variant of `when` that fallback to returning `null`
///
@@ -564,10 +577,10 @@ return $default(_that.realmId,_that.realm,_that.accountId,_that.account,_that.ro
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String realmId, SnRealm? realm, String accountId, SnAccount? account, int role, DateTime? joinedAt, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt)? $default,) {final _that = this;
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String realmId, SnRealm? realm, String accountId, SnAccount? account, int role, DateTime? joinedAt, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, SnAccountStatus? status)? $default,) {final _that = this;
switch (_that) {
case _SnRealmMember() when $default != null:
return $default(_that.realmId,_that.realm,_that.accountId,_that.account,_that.role,_that.joinedAt,_that.createdAt,_that.updatedAt,_that.deletedAt);case _:
return $default(_that.realmId,_that.realm,_that.accountId,_that.account,_that.role,_that.joinedAt,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.status);case _:
return null;
}
@@ -579,7 +592,7 @@ return $default(_that.realmId,_that.realm,_that.accountId,_that.account,_that.ro
@JsonSerializable()
class _SnRealmMember implements SnRealmMember {
const _SnRealmMember({required this.realmId, required this.realm, required this.accountId, required this.account, required this.role, required this.joinedAt, required this.createdAt, required this.updatedAt, required this.deletedAt});
const _SnRealmMember({required this.realmId, required this.realm, required this.accountId, required this.account, required this.role, required this.joinedAt, required this.createdAt, required this.updatedAt, required this.deletedAt, required this.status});
factory _SnRealmMember.fromJson(Map<String, dynamic> json) => _$SnRealmMemberFromJson(json);
@override final String realmId;
@@ -591,6 +604,7 @@ class _SnRealmMember implements SnRealmMember {
@override final DateTime createdAt;
@override final DateTime updatedAt;
@override final DateTime? deletedAt;
@override final SnAccountStatus? status;
/// Create a copy of SnRealmMember
/// with the given fields replaced by the non-null parameter values.
@@ -605,16 +619,16 @@ Map<String, dynamic> toJson() {
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnRealmMember&&(identical(other.realmId, realmId) || other.realmId == realmId)&&(identical(other.realm, realm) || other.realm == realm)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.account, account) || other.account == account)&&(identical(other.role, role) || other.role == role)&&(identical(other.joinedAt, joinedAt) || other.joinedAt == joinedAt)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnRealmMember&&(identical(other.realmId, realmId) || other.realmId == realmId)&&(identical(other.realm, realm) || other.realm == realm)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.account, account) || other.account == account)&&(identical(other.role, role) || other.role == role)&&(identical(other.joinedAt, joinedAt) || other.joinedAt == joinedAt)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.status, status) || other.status == status));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,realmId,realm,accountId,account,role,joinedAt,createdAt,updatedAt,deletedAt);
int get hashCode => Object.hash(runtimeType,realmId,realm,accountId,account,role,joinedAt,createdAt,updatedAt,deletedAt,status);
@override
String toString() {
return 'SnRealmMember(realmId: $realmId, realm: $realm, accountId: $accountId, account: $account, role: $role, joinedAt: $joinedAt, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
return 'SnRealmMember(realmId: $realmId, realm: $realm, accountId: $accountId, account: $account, role: $role, joinedAt: $joinedAt, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, status: $status)';
}
@@ -625,11 +639,11 @@ abstract mixin class _$SnRealmMemberCopyWith<$Res> implements $SnRealmMemberCopy
factory _$SnRealmMemberCopyWith(_SnRealmMember value, $Res Function(_SnRealmMember) _then) = __$SnRealmMemberCopyWithImpl;
@override @useResult
$Res call({
String realmId, SnRealm? realm, String accountId, SnAccount? account, int role, DateTime? joinedAt, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
String realmId, SnRealm? realm, String accountId, SnAccount? account, int role, DateTime? joinedAt, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, SnAccountStatus? status
});
@override $SnRealmCopyWith<$Res>? get realm;@override $SnAccountCopyWith<$Res>? get account;
@override $SnRealmCopyWith<$Res>? get realm;@override $SnAccountCopyWith<$Res>? get account;@override $SnAccountStatusCopyWith<$Res>? get status;
}
/// @nodoc
@@ -642,7 +656,7 @@ class __$SnRealmMemberCopyWithImpl<$Res>
/// Create a copy of SnRealmMember
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? realmId = null,Object? realm = freezed,Object? accountId = null,Object? account = freezed,Object? role = null,Object? joinedAt = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
@override @pragma('vm:prefer-inline') $Res call({Object? realmId = null,Object? realm = freezed,Object? accountId = null,Object? account = freezed,Object? role = null,Object? joinedAt = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? status = freezed,}) {
return _then(_SnRealmMember(
realmId: null == realmId ? _self.realmId : realmId // ignore: cast_nullable_to_non_nullable
as String,realm: freezed == realm ? _self.realm : realm // ignore: cast_nullable_to_non_nullable
@@ -653,7 +667,8 @@ as int,joinedAt: freezed == joinedAt ? _self.joinedAt : joinedAt // ignore: cast
as DateTime?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
as DateTime,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?,status: freezed == status ? _self.status : status // ignore: cast_nullable_to_non_nullable
as SnAccountStatus?,
));
}
@@ -681,6 +696,18 @@ $SnAccountCopyWith<$Res>? get account {
return $SnAccountCopyWith<$Res>(_self.account!, (value) {
return _then(_self.copyWith(account: value));
});
}/// Create a copy of SnRealmMember
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$SnAccountStatusCopyWith<$Res>? get status {
if (_self.status == null) {
return null;
}
return $SnAccountStatusCopyWith<$Res>(_self.status!, (value) {
return _then(_self.copyWith(status: value));
});
}
}

View File

@@ -75,6 +75,12 @@ _SnRealmMember _$SnRealmMemberFromJson(Map<String, dynamic> json) =>
json['deleted_at'] == null
? null
: DateTime.parse(json['deleted_at'] as String),
status:
json['status'] == null
? null
: SnAccountStatus.fromJson(
json['status'] as Map<String, dynamic>,
),
);
Map<String, dynamic> _$SnRealmMemberToJson(_SnRealmMember instance) =>
@@ -88,4 +94,5 @@ Map<String, dynamic> _$SnRealmMemberToJson(_SnRealmMember instance) =>
'created_at': instance.createdAt.toIso8601String(),
'updated_at': instance.updatedAt.toIso8601String(),
'deleted_at': instance.deletedAt?.toIso8601String(),
'status': instance.status?.toJson(),
};

View File

@@ -1,6 +1,6 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:island/models/user.dart';
import 'package:island/models/account.dart';
part 'relationship.freezed.dart';
part 'relationship.g.dart';

View File

@@ -1,5 +1,5 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:island/models/user.dart';
import 'package:island/models/account.dart';
part 'wallet.freezed.dart';
part 'wallet.g.dart';

View File

@@ -23,6 +23,8 @@ const kAppSoundEffects = 'app_sound_effects';
const kAppAprilFoolFeatures = 'app_april_fool_features';
const kAppWindowSize = 'app_window_size';
const kAppEnterToSend = 'app_enter_to_send';
const kFeaturedPostsCollapsedId =
'featured_posts_collapsed_id'; // Key for storing the ID of the collapsed featured post
const Map<String, FilterQuality> kImageQualityLevel = {
'settingsImageQualityLowest': FilterQuality.none,

View File

@@ -1,8 +1,9 @@
import 'dart:developer';
import 'package:firebase_analytics/firebase_analytics.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/models/user.dart';
import 'package:island/models/account.dart';
import 'package:island/pods/config.dart';
import 'package:island/pods/network.dart';
@@ -17,6 +18,7 @@ class UserInfoNotifier extends StateNotifier<AsyncValue<SnAccount?>> {
final response = await client.get('/id/accounts/me');
final user = SnAccount.fromJson(response.data);
state = AsyncValue.data(user);
FirebaseAnalytics.instance.setUserId(id: user.id);
} catch (error, stackTrace) {
log(
"[UserInfo] Failed to fetch user info...",
@@ -33,6 +35,7 @@ class UserInfoNotifier extends StateNotifier<AsyncValue<SnAccount?>> {
final prefs = _ref.read(sharedPreferencesProvider);
await prefs.remove(kTokenPairStoreKey);
_ref.invalidate(tokenProvider);
FirebaseAnalytics.instance.setUserId(id: null);
}
}

View File

@@ -1,4 +1,8 @@
import 'dart:io' show Platform;
import 'package:animations/animations.dart';
import 'package:firebase_analytics/firebase_analytics.dart';
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/screens/about.dart';
@@ -18,9 +22,9 @@ import 'package:island/screens/notification.dart';
import 'package:island/screens/wallet.dart';
import 'package:island/screens/account/relationship.dart';
import 'package:island/screens/account/profile.dart';
import 'package:island/screens/account/me/update.dart';
import 'package:island/screens/account/me/profile_update.dart';
import 'package:island/screens/account/leveling.dart';
import 'package:island/screens/account/me/settings.dart';
import 'package:island/screens/account/me/account_settings.dart';
import 'package:island/screens/chat/chat.dart';
import 'package:island/screens/chat/room.dart';
import 'package:island/screens/chat/room_detail.dart';
@@ -54,11 +58,36 @@ final rootNavigatorKey = GlobalKey<NavigatorState>();
final _shellNavigatorKey = GlobalKey<NavigatorState>();
final _tabsShellKey = GlobalKey<NavigatorState>();
Widget _tabPagesTransitionBuilder(
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child,
) {
return FadeThroughTransition(
animation: animation,
secondaryAnimation: secondaryAnimation,
fillColor: Theme.of(context).colorScheme.surface,
child: child,
);
}
bool get _supportsAnalytics =>
kIsWeb ||
Platform.isAndroid ||
Platform.isIOS ||
Platform.isMacOS ||
Platform.isWindows;
// Provider for the router
final routerProvider = Provider<GoRouter>((ref) {
return GoRouter(
navigatorKey: rootNavigatorKey,
initialLocation: '/',
observers: [
if (_supportsAnalytics)
FirebaseAnalyticsObserver(analytics: FirebaseAnalytics.instance),
],
routes: [
ShellRoute(
navigatorKey: _shellNavigatorKey,
@@ -334,7 +363,12 @@ final routerProvider = Provider<GoRouter>((ref) {
GoRoute(
name: 'explore',
path: '/',
builder: (context, state) => const ExploreScreen(),
pageBuilder:
(context, state) => CustomTransitionPage(
key: const ValueKey('explore'),
child: const ExploreScreen(),
transitionsBuilder: _tabPagesTransitionBuilder,
),
),
GoRoute(
name: 'postSearch',
@@ -384,8 +418,12 @@ final routerProvider = Provider<GoRouter>((ref) {
// Chat tab
ShellRoute(
builder:
(context, state, child) => ChatShellScreen(child: child),
pageBuilder:
(context, state, child) => CustomTransitionPage(
key: const ValueKey('chat'),
child: ChatShellScreen(child: child),
transitionsBuilder: _tabPagesTransitionBuilder,
),
routes: [
GoRoute(
name: 'chatList',
@@ -428,7 +466,12 @@ final routerProvider = Provider<GoRouter>((ref) {
GoRoute(
name: 'realmList',
path: '/realms',
builder: (context, state) => const RealmListScreen(),
pageBuilder:
(context, state) => CustomTransitionPage(
key: const ValueKey('realms'),
child: const RealmListScreen(),
transitionsBuilder: _tabPagesTransitionBuilder,
),
routes: [
GoRoute(
name: 'realmNew',
@@ -456,8 +499,12 @@ final routerProvider = Provider<GoRouter>((ref) {
// Account tab
ShellRoute(
builder:
(context, state, child) => AccountShellScreen(child: child),
pageBuilder:
(context, state, child) => CustomTransitionPage(
key: const ValueKey('account'),
child: AccountShellScreen(child: child),
transitionsBuilder: _tabPagesTransitionBuilder,
),
routes: [
GoRoute(
name: 'account',

View File

@@ -178,7 +178,8 @@ class _AboutScreenState extends ConsumerState<AboutScreen> {
context,
icon: Symbols.label,
label: 'aboutDeviceName'.tr(),
value: _deviceInfo?.data['name'],
value:
_deviceInfo?.data['name'] ?? 'unknown'.tr(),
),
_buildInfoItem(
context,

View File

@@ -3,12 +3,14 @@ import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/pods/network.dart';
import 'package:island/pods/userinfo.dart';
import 'package:island/screens/notification.dart';
import 'package:island/services/responsive.dart';
import 'package:island/widgets/account/account_name.dart';
import 'package:island/widgets/account/status.dart';
import 'package:island/widgets/account/leveling_progress.dart';
import 'package:island/widgets/alert.dart';
import 'package:island/widgets/app_scaffold.dart';
import 'package:island/widgets/content/cloud_files.dart';
import 'package:island/widgets/debug_sheet.dart';
@@ -236,7 +238,7 @@ class AccountScreen extends HookConsumerWidget {
),
ListTile(
minTileHeight: 48,
title: Text('abuseReports').tr(),
title: Text('abuseReport').tr(),
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
leading: const Icon(Symbols.gavel),
trailing: const Icon(Symbols.chevron_right),
@@ -303,7 +305,12 @@ class AccountScreen extends HookConsumerWidget {
trailing: const Icon(Symbols.chevron_right),
contentPadding: EdgeInsets.symmetric(horizontal: 24),
title: Text('logout').tr(),
onTap: () {
onTap: () async {
final apiClient = ref.watch(apiClientProvider);
showLoadingModal(context);
await apiClient.delete('/id/accounts/me/sessions/current');
if (!context.mounted) return;
hideLoadingModal(context);
final userNotifier = ref.read(userInfoProvider.notifier);
userNotifier.logOut();
},

View File

@@ -6,7 +6,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/models/auth.dart';
import 'package:island/models/user.dart';
import 'package:island/models/account.dart';
import 'package:island/pods/network.dart';
import 'package:island/pods/userinfo.dart';
import 'package:island/screens/account/me/settings_auth_factors.dart';
@@ -15,7 +15,7 @@ import 'package:island/screens/account/me/settings_contacts.dart';
import 'package:island/screens/auth/captcha.dart';
import 'package:island/screens/auth/login.dart';
import 'package:island/services/responsive.dart';
import 'package:island/widgets/account/account_session_sheet.dart';
import 'package:island/widgets/account/account_devices.dart';
import 'package:island/widgets/alert.dart';
import 'package:island/widgets/app_scaffold.dart';
import 'package:island/widgets/response.dart';
@@ -23,7 +23,7 @@ import 'package:material_symbols_icons/symbols.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:styled_widget/styled_widget.dart';
part 'settings.g.dart';
part 'account_settings.g.dart';
@riverpod
Future<List<SnAuthFactor>> authFactors(Ref ref) async {

View File

@@ -1,6 +1,6 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'settings.dart';
part of 'account_settings.dart';
// **************************************************************************
// RiverpodGenerator

View File

@@ -1,3 +1,4 @@
import 'package:collection/collection.dart';
import 'package:croppy/croppy.dart' hide cropImage;
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:easy_localization/easy_localization.dart';
@@ -7,7 +8,7 @@ import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:image_picker/image_picker.dart';
import 'package:island/models/file.dart';
import 'package:island/models/user.dart';
import 'package:island/models/account.dart';
import 'package:island/pods/config.dart';
import 'package:island/pods/network.dart';
import 'package:island/pods/userinfo.dart';
@@ -168,11 +169,18 @@ class UpdateProfileScreen extends HookConsumerWidget {
'location': locationController.text,
'time_zone': timeZoneController.text,
'birthday': birthday.value?.toUtc().toIso8601String(),
'links': links.value,
'links':
links.value
.where((e) => e.name.isNotEmpty && e.url.isNotEmpty)
.toList(),
},
);
final userNotifier = ref.read(userInfoProvider.notifier);
userNotifier.fetchUser();
links.value =
links.value
.where((e) => e.name.isNotEmpty && e.url.isNotEmpty)
.toList();
} catch (err) {
showErrorAlert(err);
} finally {
@@ -568,6 +576,7 @@ class UpdateProfileScreen extends HookConsumerWidget {
children: [
for (var i = 0; i < links.value.length; i++)
Row(
key: ValueKey(links.value[i].hashCode),
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Expanded(
@@ -610,8 +619,10 @@ class UpdateProfileScreen extends HookConsumerWidget {
IconButton(
icon: const Icon(Symbols.delete),
onPressed: () {
links.value = List.from(links.value)
..removeAt(i);
links.value =
links.value
.whereIndexed((idx, _) => idx != i)
.toList();
},
),
],

View File

@@ -6,7 +6,7 @@ import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/models/auth.dart';
import 'package:island/pods/network.dart';
import 'package:island/screens/account/me/settings.dart';
import 'package:island/screens/account/me/account_settings.dart';
import 'package:island/screens/auth/oidc.native.dart';
import 'package:island/services/text.dart';
import 'package:island/services/time.dart';
@@ -166,7 +166,7 @@ class AccountConnectionNewSheet extends HookConsumerWidget {
webAuthenticationOptions: WebAuthenticationOptions(
clientId: 'dev.solsynth.solarpass',
redirectUri: Uri.parse(
'https://nt.solian.app/auth/callback/apple',
'https://id.solian.app/auth/callback/apple',
),
),
);

View File

@@ -3,7 +3,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/models/user.dart';
import 'package:island/models/account.dart';
import 'package:island/pods/network.dart';
import 'package:island/widgets/alert.dart';
import 'package:island/widgets/content/sheet.dart';

View File

@@ -2,12 +2,13 @@ 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:go_router/go_router.dart';
import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/models/chat.dart';
import 'package:island/models/relationship.dart';
import 'package:island/models/user.dart';
import 'package:island/models/account.dart';
import 'package:island/pods/config.dart';
import 'package:island/pods/event_calendar.dart';
import 'package:island/pods/network.dart';
@@ -262,6 +263,10 @@ class AccountProfileScreen extends HookConsumerWidget {
}
final user = ref.watch(userInfoProvider);
final isCurrentUser = useMemoized(
() => user.value?.id == account.value?.id,
[user, account],
);
Widget accountBasicInfo(SnAccount data) => Padding(
padding: const EdgeInsets.fromLTRB(24, 24, 24, 8),
@@ -589,7 +594,7 @@ class AccountProfileScreen extends HookConsumerWidget {
child: CustomScrollView(
slivers: [
SliverGap(24),
if (user.value != null)
if (user.value != null && !isCurrentUser)
SliverToBoxAdapter(child: accountAction(data)),
SliverToBoxAdapter(
child: Card(
@@ -686,7 +691,7 @@ class AccountProfileScreen extends HookConsumerWidget {
data,
).padding(horizontal: 4),
),
if (user.value != null)
if (user.value != null && !isCurrentUser)
SliverToBoxAdapter(
child: accountAction(data).padding(horizontal: 4),
),

View File

@@ -216,6 +216,7 @@ class RelationshipScreen extends HookConsumerWidget {
final result = await showModalBottomSheet(
context: context,
useRootNavigator: true,
isScrollControlled: true,
builder: (context) => AccountPickerSheet(),
);
if (result == null) return;

View File

@@ -6,7 +6,7 @@ import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/pods/network.dart';
import 'package:island/screens/account/me/update.dart';
import 'package:island/screens/account/me/profile_update.dart';
import 'package:island/widgets/alert.dart';
import 'package:island/widgets/app_scaffold.dart';
import 'package:material_symbols_icons/symbols.dart';

View File

@@ -42,6 +42,22 @@ final Map<int, (String, String, IconData)> kFactorTypes = {
4: ('authFactorPin', 'authFactorPinDescription', Symbols.nest_secure_alarm),
};
Future<String?> getDeviceName() async {
if (kIsWeb) return null;
String? name;
if (Platform.isIOS) {
final deviceInfo = await DeviceInfoPlugin().iosInfo;
name = deviceInfo.name;
} else if (Platform.isAndroid) {
final deviceInfo = await DeviceInfoPlugin().androidInfo;
name = deviceInfo.name;
} else if (Platform.isWindows) {
final deviceInfo = await DeviceInfoPlugin().windowsInfo;
name = deviceInfo.computerName;
}
return name;
}
class LoginScreen extends HookConsumerWidget {
const LoginScreen({super.key});
@@ -198,28 +214,6 @@ 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) {
final deviceInfo = await DeviceInfoPlugin().iosInfo;
name = deviceInfo.name;
} else if (Platform.isAndroid) {
final deviceInfo = await DeviceInfoPlugin().androidInfo;
name = deviceInfo.name;
} else if (Platform.isWindows) {
final deviceInfo = await DeviceInfoPlugin().windowsInfo;
name = deviceInfo.computerName;
}
if (name != null) {
final client = ref.watch(apiClientProvider);
await client.patch(
'/id/accounts/me/sessions/current/label',
data: jsonEncode(name),
);
}
}
}
useEffect(() {
@@ -578,6 +572,7 @@ class _LoginLookupScreen extends HookConsumerWidget {
data: {
'account': uname,
'device_id': await getUdid(),
'device_name': await getDeviceName(),
'platform':
kIsWeb
? 1
@@ -628,6 +623,7 @@ class _LoginLookupScreen extends HookConsumerWidget {
'identity_token': credential.identityToken!,
'authorization_code': credential.authorizationCode,
'device_id': await getUdid(),
'device_name': await getDeviceName(),
},
);

View File

@@ -23,7 +23,7 @@ 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/realms/selection_dropdown.dart';
import 'package:island/widgets/realm/realm_selection_dropdown.dart';
import 'package:island/widgets/response.dart';
import 'package:island/screens/tabs.dart';
import 'package:material_symbols_icons/symbols.dart';
@@ -80,10 +80,12 @@ class ChatRoomListTile extends HookConsumerWidget {
),
),
Row(
spacing: 4,
children: [
Text(
'${data.lastMessage.sender.account.name}: ',
style: Theme.of(context).textTheme.bodySmall,
Badge(
label: Text(data.lastMessage.sender.account.nick),
textColor: Theme.of(context).colorScheme.onPrimary,
backgroundColor: Theme.of(context).colorScheme.primary,
),
Expanded(
child: Text(
@@ -227,6 +229,7 @@ class ChatListScreen extends HookConsumerWidget {
final result = await showModalBottomSheet(
context: context,
useRootNavigator: true,
isScrollControlled: true,
builder: (context) => const AccountPickerSheet(),
);
if (result == null) return;

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -10,6 +10,7 @@ import 'package:island/models/chat.dart';
import 'package:island/pods/network.dart';
import 'package:island/screens/chat/chat.dart';
import 'package:island/widgets/account/account_picker.dart';
import 'package:island/widgets/account/status.dart';
import 'package:island/widgets/alert.dart';
import 'package:island/widgets/app_scaffold.dart';
import 'package:island/widgets/content/cloud_files.dart';
@@ -544,7 +545,7 @@ class ChatMemberListNotifier extends _$ChatMemberListNotifier
final apiClient = ref.watch(apiClientProvider);
final response = await apiClient.get(
'/sphere/chat/$roomId/members',
queryParameters: {'offset': offset, 'take': take},
queryParameters: {'offset': offset, 'take': take, 'withStatus': true},
);
final total = int.parse(response.headers.value('X-Total') ?? '0');
@@ -589,6 +590,7 @@ class _ChatMemberListSheet extends HookConsumerWidget {
final result = await showModalBottomSheet(
context: context,
useRootNavigator: true,
isScrollControlled: true,
builder: (context) => const AccountPickerSheet(),
);
if (result == null) return;
@@ -671,6 +673,8 @@ class _ChatMemberListSheet extends HookConsumerWidget {
spacing: 6,
children: [
Flexible(child: Text(member.account.nick)),
if (member.status != null)
AccountStatusLabel(status: member.status!),
if (member.joinedAt == null)
const Icon(Symbols.pending_actions, size: 20),
],
@@ -727,7 +731,7 @@ class _ChatMemberListSheet extends HookConsumerWidget {
apiClientProvider,
);
await apiClient.delete(
'/chat/$roomId/members/${member.accountId}',
'/sphere/chat/$roomId/members/${member.accountId}',
);
// Refresh both providers
memberNotifier.reset();

View File

@@ -212,30 +212,6 @@ class CreatorHubScreen extends HookConsumerWidget {
leading: !isWide ? const PageBackButton() : null,
title: Text('creatorHub').tr(),
actions: [
IconButton(
icon: Badge(
label: Text(
publisherInvites.when(
data: (invites) => invites.length.toString(),
error: (_, _) => '0',
loading: () => '0',
),
),
isLabelVisible: publisherInvites.when(
data: (invites) => invites.isNotEmpty,
error: (_, _) => false,
loading: () => false,
),
child: const Icon(Symbols.email),
),
onPressed: () {
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (_) => const _PublisherInviteSheet(),
);
},
),
DropdownButtonHideUnderline(
child: DropdownButton2<SnPublisher>(
alignment: Alignment.centerRight,
@@ -323,6 +299,23 @@ class CreatorHubScreen extends HookConsumerWidget {
),
) ??
[]),
ListTile(
leading: const CircleAvatar(
child: Icon(Symbols.mail),
),
title: Text('publisherCollabInvitation').tr(),
subtitle: Text(
'publisherCollabInvitationCount',
).plural(publisherInvites.value?.length ?? 0),
trailing: const Icon(Symbols.chevron_right),
onTap: () {
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (_) => const _PublisherInviteSheet(),
);
},
),
ListTile(
leading: const CircleAvatar(
child: Icon(Symbols.add),
@@ -382,7 +375,7 @@ class CreatorHubScreen extends HookConsumerWidget {
),
ListTile(
minTileHeight: 48,
title: const Text('Polls'),
title: Text('polls').tr(),
trailing: const Icon(Symbols.chevron_right),
leading: const Icon(Symbols.poll),
contentPadding: const EdgeInsets.symmetric(
@@ -419,7 +412,7 @@ class CreatorHubScreen extends HookConsumerWidget {
),
ListTile(
minTileHeight: 48,
title: const Text('Web Feeds').tr(),
title: const Text('webFeeds').tr(),
trailing: const Icon(Symbols.chevron_right),
leading: const Icon(Symbols.rss_feed),
contentPadding: const EdgeInsets.symmetric(
@@ -659,7 +652,7 @@ class PublisherMemberNotifier extends StateNotifier<PublisherMemberState> {
try {
final response = await _apiClient.get(
'/publishers/$publisherUname/members',
'/sphere/publishers/$publisherUname/members',
queryParameters: {'offset': offset, 'take': take},
);
@@ -708,6 +701,7 @@ class _PublisherMemberListSheet extends HookConsumerWidget {
Future<void> invitePerson() async {
final result = await showModalBottomSheet(
useRootNavigator: true,
isScrollControlled: true,
context: context,
builder: (context) => const AccountPickerSheet(),
@@ -719,6 +713,9 @@ class _PublisherMemberListSheet extends HookConsumerWidget {
'/publishers/$publisherUname/invites',
data: {'related_user_id': result.id, 'role': 0},
);
// Refresh both providers
memberNotifier.reset();
await memberNotifier.loadMore();
ref.invalidate(memberListProvider);
} catch (err) {
showErrorAlert(err);
@@ -822,6 +819,9 @@ class _PublisherMemberListSheet extends HookConsumerWidget {
),
).then((value) {
if (value != null) {
// Refresh both providers
memberNotifier.reset();
memberNotifier.loadMore();
ref.invalidate(memberListProvider);
}
});
@@ -843,6 +843,9 @@ class _PublisherMemberListSheet extends HookConsumerWidget {
await apiClient.delete(
'/publishers/$publisherUname/members/${member.accountId}',
);
// Refresh both providers
memberNotifier.reset();
memberNotifier.loadMore();
ref.invalidate(memberListProvider);
} catch (err) {
showErrorAlert(err);

View File

@@ -14,17 +14,19 @@ part 'poll_list.g.dart';
@riverpod
class PollListNotifier extends _$PollListNotifier
with CursorPagingNotifierMixin<SnPoll> {
with CursorPagingNotifierMixin<SnPollWithStats> {
static const int _pageSize = 20;
@override
Future<CursorPagingData<SnPoll>> build(String? pubName) {
Future<CursorPagingData<SnPollWithStats>> build(String? pubName) {
// immediately load first page
return fetch(cursor: null);
}
@override
Future<CursorPagingData<SnPoll>> fetch({required String? cursor}) async {
Future<CursorPagingData<SnPollWithStats>> fetch({
required String? cursor,
}) async {
final client = ref.read(apiClientProvider);
final offset = cursor == null ? 0 : int.parse(cursor);
@@ -42,7 +44,7 @@ class PollListNotifier extends _$PollListNotifier
);
final total = int.parse(response.headers.value('X-Total') ?? '0');
final List<dynamic> data = response.data;
final items = data.map((json) => SnPoll.fromJson(json)).toList();
final items = data.map((json) => SnPollWithStats.fromJson(json)).toList();
final hasMore = offset + items.length < total;
final nextCursor = hasMore ? (offset + items.length).toString() : null;
@@ -55,6 +57,13 @@ class PollListNotifier extends _$PollListNotifier
}
}
@riverpod
Future<SnPollWithStats> pollWithStats(Ref ref, String id) async {
final apiClient = ref.watch(apiClientProvider);
final resp = await apiClient.get('/sphere/polls/$id');
return SnPollWithStats.fromJson(resp.data);
}
class CreatorPollListScreen extends HookConsumerWidget {
const CreatorPollListScreen({super.key, required this.pubName});
@@ -64,7 +73,7 @@ class CreatorPollListScreen extends HookConsumerWidget {
final result = await GoRouter.of(
context,
).pushNamed('creatorPollNew', pathParameters: {'name': pubName});
if (result is SnPoll && context.mounted) {
if (result is SnPollWithStats && context.mounted) {
Navigator.of(context).maybePop(result);
}
}
@@ -92,8 +101,11 @@ class CreatorPollListScreen extends HookConsumerWidget {
if (index == widgetCount - 1) {
return endItemView;
}
final poll = data.items[index];
return _CreatorPollItem(poll: poll, pubName: pubName);
final pollWithStats = data.items[index];
return _CreatorPollItem(
pollWithStats: pollWithStats,
pubName: pubName,
);
},
),
),
@@ -106,14 +118,14 @@ class CreatorPollListScreen extends HookConsumerWidget {
class _CreatorPollItem extends StatelessWidget {
final String pubName;
const _CreatorPollItem({required this.poll, required this.pubName});
const _CreatorPollItem({required this.pollWithStats, required this.pubName});
final SnPoll poll;
final SnPollWithStats pollWithStats;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final ended = poll.endedAt;
final ended = pollWithStats.endedAt;
final endedText =
ended == null
? 'No end'
@@ -123,15 +135,16 @@ class _CreatorPollItem extends StatelessWidget {
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
clipBehavior: Clip.antiAlias,
child: ListTile(
title: Text(poll.title ?? 'Untitled poll'),
title: Text(pollWithStats.title ?? 'Untitled poll'),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (poll.description != null && poll.description!.isNotEmpty)
if (pollWithStats.description != null &&
pollWithStats.description!.isNotEmpty)
Padding(
padding: const EdgeInsets.only(top: 4),
child: Text(
poll.description!,
pollWithStats.description!,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
@@ -139,7 +152,7 @@ class _CreatorPollItem extends StatelessWidget {
Padding(
padding: const EdgeInsets.only(top: 4),
child: Text(
'Questions: ${poll.questions.length} · Ends: $endedText',
'Questions: ${pollWithStats.questions.length} · Ends: $endedText',
style: theme.textTheme.bodySmall,
),
),
@@ -159,7 +172,7 @@ class _CreatorPollItem extends StatelessWidget {
onTap: () {
GoRouter.of(context).pushNamed(
'creatorPollEdit',
pathParameters: {'name': pubName, 'id': poll.id},
pathParameters: {'name': pubName, 'id': pollWithStats.id},
);
},
),
@@ -170,8 +183,7 @@ class _CreatorPollItem extends StatelessWidget {
context: context,
useRootNavigator: true,
isScrollControlled: true,
builder:
(context) => PollFeedbackSheet(pollId: poll.id, poll: poll),
builder: (context) => PollFeedbackSheet(pollId: pollWithStats.id),
);
},
),

View File

@@ -6,7 +6,7 @@ part of 'poll_list.dart';
// RiverpodGenerator
// **************************************************************************
String _$pollListNotifierHash() => r'd3da24ff6bbb8f35b06d57fc41625dc0312508e4';
String _$pollWithStatsHash() => r'6bb910046ce1e09368f9922dbec52fdc2cc86740';
/// Copied from Dart SDK
class _SystemHash {
@@ -29,11 +29,133 @@ class _SystemHash {
}
}
/// See also [pollWithStats].
@ProviderFor(pollWithStats)
const pollWithStatsProvider = PollWithStatsFamily();
/// See also [pollWithStats].
class PollWithStatsFamily extends Family<AsyncValue<SnPollWithStats>> {
/// See also [pollWithStats].
const PollWithStatsFamily();
/// See also [pollWithStats].
PollWithStatsProvider call(String id) {
return PollWithStatsProvider(id);
}
@override
PollWithStatsProvider getProviderOverride(
covariant PollWithStatsProvider provider,
) {
return call(provider.id);
}
static const Iterable<ProviderOrFamily>? _dependencies = null;
@override
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
@override
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
_allTransitiveDependencies;
@override
String? get name => r'pollWithStatsProvider';
}
/// See also [pollWithStats].
class PollWithStatsProvider extends AutoDisposeFutureProvider<SnPollWithStats> {
/// See also [pollWithStats].
PollWithStatsProvider(String id)
: this._internal(
(ref) => pollWithStats(ref as PollWithStatsRef, id),
from: pollWithStatsProvider,
name: r'pollWithStatsProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$pollWithStatsHash,
dependencies: PollWithStatsFamily._dependencies,
allTransitiveDependencies:
PollWithStatsFamily._allTransitiveDependencies,
id: id,
);
PollWithStatsProvider._internal(
super._createNotifier, {
required super.name,
required super.dependencies,
required super.allTransitiveDependencies,
required super.debugGetCreateSourceHash,
required super.from,
required this.id,
}) : super.internal();
final String id;
@override
Override overrideWith(
FutureOr<SnPollWithStats> Function(PollWithStatsRef provider) create,
) {
return ProviderOverride(
origin: this,
override: PollWithStatsProvider._internal(
(ref) => create(ref as PollWithStatsRef),
from: from,
name: null,
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
id: id,
),
);
}
@override
AutoDisposeFutureProviderElement<SnPollWithStats> createElement() {
return _PollWithStatsProviderElement(this);
}
@override
bool operator ==(Object other) {
return other is PollWithStatsProvider && other.id == id;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, id.hashCode);
return _SystemHash.finish(hash);
}
}
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
mixin PollWithStatsRef on AutoDisposeFutureProviderRef<SnPollWithStats> {
/// The parameter `id` of this provider.
String get id;
}
class _PollWithStatsProviderElement
extends AutoDisposeFutureProviderElement<SnPollWithStats>
with PollWithStatsRef {
_PollWithStatsProviderElement(super.provider);
@override
String get id => (origin as PollWithStatsProvider).id;
}
String _$pollListNotifierHash() => r'd5b822e737788be8982f5cb3b501d460441930c1';
abstract class _$PollListNotifier
extends BuildlessAutoDisposeAsyncNotifier<CursorPagingData<SnPoll>> {
extends
BuildlessAutoDisposeAsyncNotifier<CursorPagingData<SnPollWithStats>> {
late final String? pubName;
FutureOr<CursorPagingData<SnPoll>> build(String? pubName);
FutureOr<CursorPagingData<SnPollWithStats>> build(String? pubName);
}
/// See also [PollListNotifier].
@@ -42,7 +164,7 @@ const pollListNotifierProvider = PollListNotifierFamily();
/// See also [PollListNotifier].
class PollListNotifierFamily
extends Family<AsyncValue<CursorPagingData<SnPoll>>> {
extends Family<AsyncValue<CursorPagingData<SnPollWithStats>>> {
/// See also [PollListNotifier].
const PollListNotifierFamily();
@@ -78,7 +200,7 @@ class PollListNotifierProvider
extends
AutoDisposeAsyncNotifierProviderImpl<
PollListNotifier,
CursorPagingData<SnPoll>
CursorPagingData<SnPollWithStats>
> {
/// See also [PollListNotifier].
PollListNotifierProvider(String? pubName)
@@ -109,7 +231,7 @@ class PollListNotifierProvider
final String? pubName;
@override
FutureOr<CursorPagingData<SnPoll>> runNotifierBuild(
FutureOr<CursorPagingData<SnPollWithStats>> runNotifierBuild(
covariant PollListNotifier notifier,
) {
return notifier.build(pubName);
@@ -134,7 +256,7 @@ class PollListNotifierProvider
@override
AutoDisposeAsyncNotifierProviderElement<
PollListNotifier,
CursorPagingData<SnPoll>
CursorPagingData<SnPollWithStats>
>
createElement() {
return _PollListNotifierProviderElement(this);
@@ -157,7 +279,7 @@ class PollListNotifierProvider
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
mixin PollListNotifierRef
on AutoDisposeAsyncNotifierProviderRef<CursorPagingData<SnPoll>> {
on AutoDisposeAsyncNotifierProviderRef<CursorPagingData<SnPollWithStats>> {
/// The parameter `pubName` of this provider.
String? get pubName;
}
@@ -166,7 +288,7 @@ class _PollListNotifierProviderElement
extends
AutoDisposeAsyncNotifierProviderElement<
PollListNotifier,
CursorPagingData<SnPoll>
CursorPagingData<SnPollWithStats>
>
with PollListNotifierRef {
_PollListNotifierProviderElement(super.provider);

View File

@@ -19,7 +19,7 @@ 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/realms/selection_dropdown.dart';
import 'package:island/widgets/realm/realm_selection_dropdown.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:styled_widget/styled_widget.dart';

View File

@@ -180,6 +180,7 @@ class StickerPackDetailScreen extends HookConsumerWidget {
.pushNamed(
'creatorStickerEdit',
pathParameters: {
'name': pubName,
'packId': id,
'id': sticker.id,
},

View File

@@ -31,7 +31,7 @@ class StickersScreen extends HookConsumerWidget {
context
.pushNamed(
'creatorStickerPackNew',
queryParameters: {'name': pubName},
pathParameters: {'name': pubName},
)
.then((value) {
if (value != null) {
@@ -187,10 +187,8 @@ class EditStickerPacksScreen extends HookConsumerWidget {
'description': descriptionController.text,
'prefix': prefixController.text,
},
options: Options(
method: packId == null ? 'POST' : 'PATCH',
headers: {'X-Pub': pubName},
),
queryParameters: {'pub': pubName},
options: Options(method: packId == null ? 'POST' : 'PATCH'),
);
if (!context.mounted) return;
context.pop(SnStickerPack.fromJson(resp.data));

View File

@@ -11,6 +11,7 @@ import 'package:island/models/realm.dart';
import 'package:island/models/webfeed.dart';
import 'package:island/pods/event_calendar.dart';
import 'package:island/pods/userinfo.dart';
import 'package:island/screens/notification.dart';
import 'package:island/services/responsive.dart';
import 'package:island/widgets/account/fortune_graph.dart';
import 'package:island/widgets/app_scaffold.dart';
@@ -30,6 +31,33 @@ import 'package:styled_widget/styled_widget.dart';
part 'explore.g.dart';
Widget notificationIndicatorWidget(
BuildContext context, {
required int count,
EdgeInsets? margin,
}) => Card(
margin: margin,
child: ListTile(
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(8)),
),
leading: const Icon(Symbols.notifications),
title: Row(
children: [
Text('notifications').tr().fontSize(14),
const Gap(8),
Badge(label: Text(count.toString())),
],
),
trailing: const Icon(Symbols.chevron_right),
minTileHeight: 40,
contentPadding: EdgeInsets.only(left: 16, right: 15),
onTap: () {
GoRouter.of(context).pushNamed('notifications');
},
),
);
class ExploreScreen extends HookConsumerWidget {
const ExploreScreen({super.key});
@@ -77,6 +105,10 @@ class ExploreScreen extends HookConsumerWidget {
final user = ref.watch(userInfoProvider);
final notificationCount = ref.watch(
notificationUnreadCountNotifierProvider,
);
return AppScaffold(
isNoBackground: false,
appBar: AppBar(
@@ -185,7 +217,7 @@ class ExploreScreen extends HookConsumerWidget {
floatingActionButtonLocation: TabbedFabLocation(context),
body: Builder(
builder: (context) {
final isWider = isWiderScreen(context);
final isWide = isWideScreen(context);
final bodyView = _buildActivityList(
context,
@@ -193,13 +225,15 @@ class ExploreScreen extends HookConsumerWidget {
currentFilter.value,
);
if (isWider) {
if (isWide) {
return Row(
children: [
Flexible(flex: 3, child: bodyView.padding(left: 8)),
if (user.value != null)
Flexible(
flex: 2,
child: Align(
alignment: Alignment.topCenter,
child: SingleChildScrollView(
child: Column(
children: [
@@ -215,13 +249,28 @@ class ExploreScreen extends HookConsumerWidget {
);
},
),
if (notificationCount.value != null &&
notificationCount.value! > 0)
notificationIndicatorWidget(
context,
count: notificationCount.value ?? 0,
margin: EdgeInsets.only(
left: 8,
right: 12,
top: 8,
),
),
PostFeaturedList().padding(
left: 8,
right: 12,
top: 8,
),
FortuneGraphWidget(
margin: EdgeInsets.only(left: 8, right: 12, top: 8),
margin: EdgeInsets.only(
left: 8,
right: 12,
top: 8,
),
events: events,
constrainWidth: true,
onPointSelected: onDaySelected,
@@ -229,6 +278,7 @@ class ExploreScreen extends HookConsumerWidget {
],
),
),
),
)
else
Flexible(
@@ -268,7 +318,7 @@ class ExploreScreen extends HookConsumerWidget {
activityListNotifierProvider(filter).notifier,
);
final isWider = isWiderScreen(context);
final isWide = isWideScreen(context);
return RefreshIndicator(
onRefresh: () => Future.sync(activitiesNotifier.forceRefresh),
@@ -283,7 +333,7 @@ class ExploreScreen extends HookConsumerWidget {
widgetCount: widgetCount,
endItemView: endItemView,
activitiesNotifier: activitiesNotifier,
contentOnly: isWider || filter != null,
contentOnly: isWide || filter != null,
),
),
),
@@ -380,6 +430,10 @@ class _ActivityListView extends HookConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final user = ref.watch(userInfoProvider);
final notificationCount = ref.watch(
notificationUnreadCountNotifierProvider,
);
return CustomScrollView(
slivers: [
SliverGap(12),
@@ -393,6 +447,14 @@ class _ActivityListView extends HookConsumerWidget {
SliverToBoxAdapter(
child: PostFeaturedList().padding(horizontal: 8, bottom: 4, top: 4),
),
if (!contentOnly && (notificationCount.value ?? 0) > 0)
SliverToBoxAdapter(
child: notificationIndicatorWidget(
context,
count: notificationCount.value ?? 0,
margin: EdgeInsets.only(left: 8, right: 8, top: 4, bottom: 4),
),
),
SliverList.builder(
itemCount: widgetCount,
itemBuilder: (context, index) {

View File

@@ -3,14 +3,17 @@ import 'dart:math' as math;
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/models/user.dart';
import 'package:island/models/account.dart';
import 'package:island/pods/network.dart';
import 'package:island/pods/websocket.dart';
import 'package:island/route.dart';
import 'package:island/widgets/alert.dart';
import 'package:island/widgets/app_scaffold.dart';
import 'package:island/widgets/content/markdown.dart';
import 'package:material_symbols_icons/material_symbols_icons.dart';
import 'package:relative_time/relative_time.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
@@ -62,6 +65,10 @@ class NotificationUnreadCountNotifier
final current = await future;
state = AsyncData(math.max(current - count, 0));
}
void clear() async {
state = AsyncData(0);
}
}
@riverpod
@@ -111,8 +118,28 @@ class NotificationScreen extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
Future<void> markAllRead() async {
showLoadingModal(context);
final apiClient = ref.watch(apiClientProvider);
await apiClient.post('/pusher/notifications/all/read');
if (!context.mounted) return;
hideLoadingModal(context);
ref.invalidate(notificationListNotifierProvider);
ref.watch(notificationUnreadCountNotifierProvider.notifier).clear();
}
return AppScaffold(
appBar: AppBar(title: const Text('notifications').tr()),
appBar: AppBar(
leading: const PageBackButton(),
title: const Text('notifications').tr(),
actions: [
IconButton(
onPressed: markAllRead,
icon: const Icon(Symbols.mark_as_unread),
),
const Gap(8),
],
),
body: PagingHelperView(
provider: notificationListNotifierProvider,
futureRefreshable: notificationListNotifierProvider.future,

View File

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

View File

@@ -11,6 +11,7 @@ import 'package:island/widgets/alert.dart';
import 'package:island/models/poll.dart';
import 'package:island/widgets/app_scaffold.dart';
import 'package:uuid/uuid.dart';
import 'package:easy_localization/easy_localization.dart';
class PollEditorState {
String? id; // for editing
@@ -110,7 +111,7 @@ class PollEditor extends Notifier<PollEditorState> {
? [
SnPollOption(
id: const Uuid().v4(),
label: 'Option 1',
label: 'pollOptionDefaultLabel'.tr(),
order: 0,
),
]
@@ -191,7 +192,7 @@ class PollEditor extends Notifier<PollEditorState> {
: [
SnPollOption(
id: const Uuid().v4(),
label: 'Option 1',
label: 'pollOptionDefaultLabel'.tr(),
order: 0,
),
])
@@ -389,7 +390,7 @@ class PollEditorScreen extends ConsumerWidget {
data: body,
));
showSnackBar(isUpdate ? 'Poll updated.' : 'Poll created.');
showSnackBar(isUpdate ? 'pollUpdated'.tr() : 'pollCreated'.tr());
if (!context.mounted) return;
Navigator.of(context).maybePop(res.data);
@@ -416,11 +417,11 @@ class PollEditorScreen extends ConsumerWidget {
return AppScaffold(
appBar: AppBar(
title: Text(model.id == null ? 'Create Poll' : 'Edit Poll'),
title: Text(model.id == null ? 'pollCreate'.tr() : 'pollEdit'.tr()),
actions: [
if (kDebugMode)
IconButton(
tooltip: 'Preview JSON (debug)',
tooltip: 'pollPreviewJsonDebug'.tr(),
onPressed: () {
_showDebugPreview(context, model);
},
@@ -439,8 +440,8 @@ class PollEditorScreen extends ConsumerWidget {
children: [
TextFormField(
initialValue: model.title ?? '',
decoration: const InputDecoration(
labelText: 'Title',
decoration: InputDecoration(
labelText: 'title'.tr(),
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(16)),
),
@@ -452,7 +453,7 @@ class PollEditorScreen extends ConsumerWidget {
(_) => FocusManager.instance.primaryFocus?.unfocus(),
validator: (v) {
if (v == null || v.trim().isEmpty) {
return 'Title is required';
return 'pollTitleRequired'.tr();
}
return null;
},
@@ -460,8 +461,8 @@ class PollEditorScreen extends ConsumerWidget {
const Gap(12),
TextFormField(
initialValue: model.description ?? '',
decoration: const InputDecoration(
labelText: 'Description',
decoration: InputDecoration(
labelText: 'description'.tr(),
alignLabelWithHint: true,
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(16)),
@@ -482,7 +483,7 @@ class PollEditorScreen extends ConsumerWidget {
Row(
children: [
Text(
'Questions',
'questions'.tr(),
style: Theme.of(context).textTheme.titleLarge,
),
const Spacer(),
@@ -495,7 +496,7 @@ class PollEditorScreen extends ConsumerWidget {
: controller.open();
},
icon: const Icon(Icons.add),
label: const Text('Add question'),
label: Text('pollAddQuestion'.tr()),
);
},
menuChildren:
@@ -514,9 +515,9 @@ class PollEditorScreen extends ConsumerWidget {
const Gap(8),
if (model.questions.isEmpty)
_EmptyState(
title: 'No questions yet',
title: 'pollNoQuestionsYet'.tr(),
subtitle:
'Use "Add question" to start building your poll.',
'pollNoQuestionsHint'.tr(),
)
else
ReorderableListView.builder(
@@ -585,7 +586,7 @@ class PollEditorScreen extends ConsumerWidget {
Navigator.of(context).maybePop();
},
icon: const Icon(Icons.close),
label: const Text('Cancel'),
label: Text('cancel'.tr()),
),
const Spacer(),
FilledButton.icon(
@@ -593,7 +594,7 @@ class PollEditorScreen extends ConsumerWidget {
_submitPoll(context, ref);
},
icon: const Icon(Icons.cloud_upload_outlined),
label: Text(model.id == null ? 'Create' : 'Update'),
label: Text(model.id == null ? 'create'.tr() : 'update'.tr()),
),
],
),
@@ -637,14 +638,14 @@ class PollEditorScreen extends ConsumerWidget {
context: context,
builder:
(_) => AlertDialog(
title: const Text('Debug Preview'),
title: Text('pollDebugPreview'.tr()),
content: SingleChildScrollView(
child: SelectableText(buf.toString()),
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('Close'),
child: Text('close'.tr()),
),
],
),
@@ -673,15 +674,15 @@ IconData _iconForType(SnPollQuestionType t) {
String _labelForType(SnPollQuestionType t) {
switch (t) {
case SnPollQuestionType.singleChoice:
return 'Single choice';
return 'pollQuestionTypeSingleChoice'.tr();
case SnPollQuestionType.multipleChoice:
return 'Multiple choice';
return 'pollQuestionTypeMultipleChoice'.tr();
case SnPollQuestionType.freeText:
return 'Free text';
return 'pollQuestionTypeFreeText'.tr();
case SnPollQuestionType.yesNo:
return 'Yes / No';
return 'pollQuestionTypeYesNo'.tr();
case SnPollQuestionType.rating:
return 'Rating';
return 'pollQuestionTypeRating'.tr();
}
}
@@ -698,8 +699,8 @@ class _EndDatePicker extends StatelessWidget {
children: [
Expanded(
child: InputDecorator(
decoration: const InputDecoration(
labelText: 'End date & time (optional)',
decoration: InputDecoration(
labelText: 'pollEndDateOptional'.tr(),
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(16)),
),
@@ -711,7 +712,7 @@ class _EndDatePicker extends StatelessWidget {
Icon(Icons.event, color: Theme.of(context).colorScheme.primary),
Text(
value == null
? 'Not set'
? 'notSet'.tr()
: MaterialLocalizations.of(
context,
).formatFullDate(value!),
@@ -759,12 +760,12 @@ class _EndDatePicker extends StatelessWidget {
);
onChanged(dt);
},
child: const Text('Pick'),
child: Text('pick'.tr()),
),
if (value != null)
TextButton(
onPressed: () => onChanged(null),
child: const Text('Clear'),
child: Text('clear'.tr()),
),
],
),
@@ -799,7 +800,7 @@ class _QuestionHeader extends StatelessWidget {
child: const Icon(Icons.drag_handle),
),
title: Text(
question.title.isEmpty ? 'Untitled question' : question.title,
question.title.isEmpty ? 'pollUntitledQuestion'.tr() : question.title,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
@@ -808,17 +809,17 @@ class _QuestionHeader extends StatelessWidget {
spacing: 4,
children: [
IconButton(
tooltip: 'Move up',
tooltip: 'moveUp'.tr(),
onPressed: onMoveUp,
icon: const Icon(Icons.arrow_upward),
),
IconButton(
tooltip: 'Move down',
tooltip: 'moveDown'.tr(),
onPressed: onMoveDown,
icon: const Icon(Icons.arrow_downward),
),
IconButton(
tooltip: 'Delete',
tooltip: 'delete'.tr(),
onPressed: onDelete,
icon: const Icon(Icons.delete_outline),
color: Theme.of(context).colorScheme.error,
@@ -853,7 +854,7 @@ class _QuestionEditor extends ConsumerWidget {
onChanged: (t) => notifier.setQuestionType(index, t),
),
FilterChip(
label: const Text('Required'),
label: Text('required'.tr()),
selected: question.isRequired,
onSelected: (v) => notifier.setQuestionRequired(index, v),
avatar: Icon(
@@ -867,8 +868,8 @@ class _QuestionEditor extends ConsumerWidget {
const Gap(12),
TextFormField(
initialValue: question.title,
decoration: const InputDecoration(
labelText: 'Question title',
decoration: InputDecoration(
labelText: 'pollQuestionTitle'.tr(),
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(16)),
),
@@ -879,7 +880,7 @@ class _QuestionEditor extends ConsumerWidget {
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
validator: (v) {
if (v == null || v.trim().isEmpty) {
return 'Question title is required';
return 'pollQuestionTitleRequired'.tr();
}
return null;
},
@@ -887,8 +888,8 @@ class _QuestionEditor extends ConsumerWidget {
const Gap(12),
TextFormField(
initialValue: question.description ?? '',
decoration: const InputDecoration(
labelText: 'Question description (optional)',
decoration: InputDecoration(
labelText: 'pollQuestionDescriptionOptional'.tr(),
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(16)),
),
@@ -902,7 +903,7 @@ class _QuestionEditor extends ConsumerWidget {
),
if (question.options != null) ...[
const Gap(16),
Text('Options', style: Theme.of(context).textTheme.titleMedium),
Text('options'.tr(), style: Theme.of(context).textTheme.titleMedium),
const Gap(8),
_OptionsEditor(index: index, options: question.options!),
const Gap(4),
@@ -911,7 +912,7 @@ class _QuestionEditor extends ConsumerWidget {
child: OutlinedButton.icon(
onPressed: () => notifier.addOption(index),
icon: const Icon(Icons.add),
label: const Text('Add option'),
label: Text('pollAddOption'.tr()),
),
),
],
@@ -937,8 +938,8 @@ class _QuestionTypePicker extends StatelessWidget {
Widget build(BuildContext context) {
return DropdownButtonFormField<SnPollQuestionType>(
value: value,
decoration: const InputDecoration(
labelText: 'Type',
decoration: InputDecoration(
labelText: 'Type'.tr(),
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(16)),
),
@@ -987,8 +988,8 @@ class _OptionsEditor extends ConsumerWidget {
child: TextFormField(
key: ValueKey(options[i].id),
initialValue: options[i].label,
decoration: const InputDecoration(
labelText: 'Option label',
decoration: InputDecoration(
labelText: 'pollOptionLabel'.tr(),
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(16)),
),
@@ -1003,7 +1004,7 @@ class _OptionsEditor extends ConsumerWidget {
SizedBox(
width: 40,
child: IconButton(
tooltip: 'Move up',
tooltip: 'moveUp'.tr(),
onPressed:
i > 0 ? () => notifier.moveOptionUp(index, i) : null,
icon: const Icon(Icons.arrow_upward),
@@ -1012,7 +1013,7 @@ class _OptionsEditor extends ConsumerWidget {
SizedBox(
width: 40,
child: IconButton(
tooltip: 'Move down',
tooltip: 'moveDown'.tr(),
onPressed:
i < options.length - 1
? () => notifier.moveOptionDown(index, i)
@@ -1023,7 +1024,7 @@ class _OptionsEditor extends ConsumerWidget {
SizedBox(
width: 40,
child: IconButton(
tooltip: 'Delete',
tooltip: 'delete'.tr(),
onPressed: () => notifier.removeOption(index, i),
icon: const Icon(Icons.close),
),
@@ -1048,7 +1049,7 @@ class _TextAnswerPreview extends StatelessWidget {
maxLines: long ? 4 : 1,
decoration: InputDecoration(
labelText:
long ? 'Long text answer (preview)' : 'Short text answer (preview)',
long ? 'pollLongTextAnswerPreview'.tr() : 'pollShortTextAnswerPreview'.tr(),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(16)),
),
@@ -1082,9 +1083,9 @@ class _EmptyState extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title, style: Theme.of(context).textTheme.titleMedium),
Text('pollNoQuestionsYet'.tr(), style: Theme.of(context).textTheme.titleMedium),
const Gap(4),
Text(subtitle, style: Theme.of(context).textTheme.bodyMedium),
Text('pollNoQuestionsHint'.tr(), style: Theme.of(context).textTheme.bodyMedium),
],
),
),

View File

@@ -89,6 +89,9 @@ class ArticleComposeScreen extends HookConsumerWidget {
}, [state]);
final showPreview = useState(false);
final isAttachmentsExpanded = useState(
true,
); // New state for attachments section
// Initialize publisher once when data is available
useEffect(() {
@@ -297,28 +300,52 @@ class ArticleComposeScreen extends HookConsumerWidget {
valueListenable: state.attachments,
builder: (context, attachments, _) {
if (attachments.isEmpty) return const SizedBox.shrink();
return Column(
return Theme(
data: Theme.of(
context,
).copyWith(dividerColor: Colors.transparent),
child: ExpansionTile(
initiallyExpanded: isAttachmentsExpanded.value,
onExpansionChanged: (expanded) {
isAttachmentsExpanded.value = expanded;
},
collapsedBackgroundColor:
Theme.of(context).colorScheme.surfaceContainer,
title: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Gap(16),
Text('attachments').tr(),
Text(
'articleAttachmentHint'.tr(),
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
style: Theme.of(
context,
).textTheme.bodySmall?.copyWith(
color:
Theme.of(
context,
).colorScheme.onSurfaceVariant,
),
).padding(bottom: 8),
),
],
),
children: [
ValueListenableBuilder<Map<int, double>>(
valueListenable: state.attachmentProgress,
builder: (context, progressMap, _) {
return Wrap(
spacing: 8,
runSpacing: 8,
spacing: 8,
children: [
for (var idx = 0; idx < attachments.length; idx++)
for (
var idx = 0;
idx < attachments.length;
idx++
)
SizedBox(
width: 280,
height: 280,
width: 180,
height: 180,
child: AttachmentPreview(
isCompact: true,
item: attachments[idx],
progress: progressMap[idx],
onRequestUpload:
@@ -340,15 +367,6 @@ class ArticleComposeScreen extends HookConsumerWidget {
state,
idx,
),
onMove: (delta) {
state
.attachments
.value = ComposeLogic.moveAttachment(
state.attachments.value,
idx,
delta,
);
},
onInsert:
() => ComposeLogic.insertAttachment(
ref,
@@ -361,7 +379,9 @@ class ArticleComposeScreen extends HookConsumerWidget {
);
},
),
Gap(16),
],
),
);
},
),

View File

@@ -51,12 +51,12 @@ class PostSearchNotifier
final offset = cursor == null ? 0 : int.parse(cursor);
final response = await client.get(
'/sphere/posts/search',
'/sphere/posts',
queryParameters: {
'query': _currentQuery,
'offset': offset,
'take': _pageSize,
'useVector': false,
'vector': false,
},
);

View File

@@ -7,7 +7,7 @@ import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/models/post.dart';
import 'package:island/models/publisher.dart';
import 'package:island/models/user.dart';
import 'package:island/models/account.dart';
import 'package:island/pods/config.dart';
import 'package:island/pods/network.dart';
import 'package:island/services/color.dart';
@@ -147,7 +147,11 @@ class PublisherProfileScreen extends HookConsumerWidget {
),
backgroundColor: Theme.of(context).colorScheme.primary,
offset: Offset(0, 48),
child: ProfilePictureWidget(file: data.picture, radius: 32),
child: ProfilePictureWidget(
file: data.picture,
radius: 32,
borderRadius: data.type == 0 ? null : 12,
),
),
onTap: () {
Navigator.pop(context, true);

View File

@@ -4,6 +4,9 @@ import 'package:island/screens/chat/chat.dart';
import 'package:flutter/material.dart';
import 'package:island/models/chat.dart';
import 'package:island/services/color.dart';
import 'package:island/services/responsive.dart';
import 'package:island/widgets/account/status.dart';
import 'package:island/widgets/post/post_list.dart';
import 'package:palette_generator/palette_generator.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:gap/gap.dart';
@@ -78,39 +81,131 @@ class RealmDetailScreen extends HookConsumerWidget {
offset: Offset(1.0, 1.0),
);
final realmIdentity = ref.watch(realmIdentityProvider(slug));
final realmChatRooms = ref.watch(realmChatRoomsProvider(slug));
Widget realmDescriptionWidget(SnRealm realm) => Card(
margin: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
child: Theme(
data: Theme.of(context).copyWith(dividerColor: Colors.transparent),
child: ExpansionTile(
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(8)),
),
collapsedShape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(8)),
),
title: const Text('description').tr(),
initiallyExpanded:
realmIdentity.hasValue && realmIdentity.value == null,
tilePadding: EdgeInsets.only(left: 24, right: 20),
expandedCrossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
realm.description,
style: const TextStyle(fontSize: 16),
).padding(horizontal: 20, bottom: 16, top: 8),
],
),
),
);
Widget realmActionWidget(SnRealm realm) => Card(
margin: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
child: FilledButton.tonalIcon(
onPressed: () async {
try {
final apiClient = ref.read(apiClientProvider);
await apiClient.post('/sphere/realms/$slug/members/me');
ref.invalidate(realmIdentityProvider(slug));
ref.invalidate(realmsJoinedProvider);
showSnackBar('realmJoinSuccess'.tr());
} catch (err) {
showErrorAlert(err);
}
},
icon: const Icon(Symbols.add),
label: const Text('realmJoin').tr(),
).padding(all: 16),
);
Widget realmChatRoomListWidget(SnRealm realm) => Card(
margin: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
'chatTabGroup',
).tr().bold().padding(horizontal: 24, top: 12, bottom: 4),
realmChatRooms.when(
loading: () => Center(child: CircularProgressIndicator()),
error: (error, _) => Center(child: Text('Error: $error')),
data: (rooms) {
if (rooms.isEmpty) {
return const SliverToBoxAdapter(child: SizedBox.shrink());
}
return Column(
children: [
for (final room in rooms)
ChatRoomListTile(
room: room,
onTap: () {
context.pushNamed(
'chatRoom',
pathParameters: {'id': room.id},
);
},
),
],
);
},
),
],
),
);
return AppScaffold(
isNoBackground: false,
body: realmState.when(
loading: () => const Center(child: CircularProgressIndicator()),
error: (error, _) => Center(child: Text('Error: $error')),
appBar:
isWideScreen(context)
? realmState.when(
data:
(realm) => CustomScrollView(
slivers: [
SliverAppBar(
expandedHeight: 180,
pinned: true,
(realm) => AppBar(
foregroundColor: appbarColor.value,
leading: PageBackButton(
color: appbarColor.value,
shadows: [iconShadow],
),
flexibleSpace: FlexibleSpaceBar(
background:
flexibleSpace: Stack(
children: [
Positioned.fill(
child:
realm!.background?.id != null
? CloudImageWidget(fileId: realm.background!.id)
? CloudImageWidget(
fileId: realm.background!.id,
)
: Container(
color:
Theme.of(context).appBarTheme.backgroundColor,
Theme.of(
context,
).appBarTheme.backgroundColor,
),
),
FlexibleSpaceBar(
title: Text(
realm.name,
style: TextStyle(
color:
appbarColor.value ??
Theme.of(context).appBarTheme.foregroundColor,
Theme.of(
context,
).appBarTheme.foregroundColor,
shadows: [iconShadow],
),
),
background: Container(),
),
],
),
actions: [
IconButton(
@@ -125,106 +220,144 @@ class RealmDetailScreen extends HookConsumerWidget {
);
},
),
_RealmActionMenu(realmSlug: slug, iconShadow: iconShadow),
_RealmActionMenu(
realmSlug: slug,
iconShadow: iconShadow,
),
const Gap(8),
],
),
SliverToBoxAdapter(
child: ref
.watch(realmIdentityProvider(slug))
.when(
error: (_, _) => AppBar(leading: PageBackButton()),
loading: () => AppBar(leading: PageBackButton()),
)
: null,
body: realmState.when(
loading: () => const Center(child: CircularProgressIndicator()),
error: (error, _) => Center(child: Text('Error: $error')),
data:
(realm) =>
isWideScreen(context)
? Row(
children: [
Flexible(
flex: 3,
child: CustomScrollView(
slivers: [SliverPostList(realm: slug)],
),
),
Flexible(
flex: 2,
child: Column(
children: [
realmIdentity.when(
loading: () => const SizedBox.shrink(),
error: (_, _) => const SizedBox.shrink(),
data:
(identity) => Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
ExpansionTile(
title: const Text('description').tr(),
initiallyExpanded: identity == null,
tilePadding: EdgeInsets.symmetric(
horizontal: 20,
),
expandedCrossAxisAlignment:
crossAxisAlignment:
CrossAxisAlignment.stretch,
children: [
Text(
realm.description,
style: const TextStyle(fontSize: 16),
).padding(
horizontal: 20,
bottom: 16,
top: 8,
realmDescriptionWidget(realm!),
if (identity == null &&
realm.isCommunity)
realmActionWidget(realm)
else
const SizedBox.shrink(),
],
),
),
realmChatRoomListWidget(realm!),
],
),
),
],
).padding(horizontal: 8, top: 8)
: CustomScrollView(
slivers: [
SliverAppBar(
expandedHeight: 180,
pinned: true,
foregroundColor: appbarColor.value,
leading: PageBackButton(
color: appbarColor.value,
shadows: [iconShadow],
),
flexibleSpace: Stack(
children: [
Positioned.fill(
child:
realm!.background?.id != null
? CloudImageWidget(
fileId: realm.background!.id,
)
: Container(
color:
Theme.of(
context,
).appBarTheme.backgroundColor,
),
),
FlexibleSpaceBar(
title: Text(
realm.name,
style: TextStyle(
color:
appbarColor.value ??
Theme.of(
context,
).appBarTheme.foregroundColor,
shadows: [iconShadow],
),
),
background:
Container(), // Empty container since background is handled by Stack
),
],
),
if (identity == null && realm.isCommunity)
FilledButton.tonalIcon(
onPressed: () async {
try {
final apiClient = ref.read(
apiClientProvider,
actions: [
IconButton(
icon: Icon(Icons.people, shadows: [iconShadow]),
onPressed: () {
showModalBottomSheet(
isScrollControlled: true,
context: context,
builder:
(context) => _RealmMemberListSheet(
realmSlug: slug,
),
);
await apiClient.post(
'/sphere/realms/$slug/members/me',
);
ref.invalidate(
realmIdentityProvider(slug),
);
ref.invalidate(realmsJoinedProvider);
showSnackBar('realmJoinSuccess'.tr());
} catch (err) {
showErrorAlert(err);
}
},
icon: const Icon(Symbols.add),
label: const Text('realmJoin').tr(),
).padding(horizontal: 16, vertical: 16)
),
_RealmActionMenu(
realmSlug: slug,
iconShadow: iconShadow,
),
const Gap(8),
],
),
SliverGap(4),
SliverToBoxAdapter(
child: realmIdentity.when(
loading: () => const SizedBox.shrink(),
error: (_, _) => const SizedBox.shrink(),
data:
(identity) => Column(
crossAxisAlignment:
CrossAxisAlignment.stretch,
children: [
realmDescriptionWidget(realm),
if (identity == null && realm.isCommunity)
realmActionWidget(realm)
else
const SizedBox.shrink(),
],
),
),
),
const SliverToBoxAdapter(child: Divider(height: 1)),
Consumer(
builder: (context, ref, _) {
final chatRooms = ref.watch(realmChatRoomsProvider(slug));
return chatRooms.when(
loading:
() => const SliverToBoxAdapter(
child: Center(child: CircularProgressIndicator()),
),
error:
(error, _) => SliverToBoxAdapter(
child: Center(child: Text('Error: $error')),
),
data: (rooms) {
if (rooms.isEmpty) {
return const SliverToBoxAdapter(
child: SizedBox.shrink(),
);
}
return SliverList(
delegate: SliverChildBuilderDelegate((
context,
index,
) {
return ChatRoomListTile(
room: rooms[index],
onTap: () {
context.pushNamed(
'chatRoom',
pathParameters: {'id': rooms[index].id},
);
},
);
}, childCount: rooms.length),
);
},
);
},
SliverToBoxAdapter(
child: realmChatRoomListWidget(realm),
),
SliverPostList(realm: slug),
],
),
),
@@ -398,7 +531,11 @@ class RealmMemberListNotifier extends _$RealmMemberListNotifier
final response = await apiClient.get(
'/sphere/realms/$realmSlug/members',
queryParameters: {'offset': offset, 'take': _pageSize},
queryParameters: {
'offset': offset,
'take': _pageSize,
'withStatus': true,
},
);
final total = int.parse(response.headers.value('X-Total') ?? '0');
@@ -441,7 +578,7 @@ class RealmMemberNotifier extends StateNotifier<RealmMemberState> {
try {
final response = await _apiClient.get(
'/sphere/realms/$realmSlug/members',
queryParameters: {'offset': offset, 'take': take},
queryParameters: {'offset': offset, 'take': take, 'withStatus': true},
);
final total = int.parse(response.headers.value('X-Total') ?? '0');
@@ -488,6 +625,7 @@ class _RealmMemberListSheet extends HookConsumerWidget {
Future<void> invitePerson() async {
final result = await showModalBottomSheet(
isScrollControlled: true,
useRootNavigator: true,
context: context,
builder: (context) => const AccountPickerSheet(),
);
@@ -507,13 +645,8 @@ class _RealmMemberListSheet extends HookConsumerWidget {
}
}
return Container(
constraints: BoxConstraints(
maxHeight: MediaQuery.of(context).size.height * 0.8,
),
child: Column(
children: [
Padding(
Widget buildMemberListHeader() {
return Padding(
padding: EdgeInsets.only(top: 16, left: 20, right: 16, bottom: 12),
child: Row(
children: [
@@ -546,9 +679,11 @@ class _RealmMemberListSheet extends HookConsumerWidget {
),
],
),
),
const Divider(height: 1),
Expanded(
);
}
Widget buildMemberListContent() {
return Expanded(
child: PagingHelperView(
provider: memberListProvider,
futureRefreshable: memberListProvider.future,
@@ -571,6 +706,8 @@ class _RealmMemberListSheet extends HookConsumerWidget {
spacing: 6,
children: [
Flexible(child: Text(member.account!.nick)),
if (member.status != null)
AccountStatusLabel(status: member.status!),
if (member.joinedAt == null)
const Icon(Symbols.pending_actions, size: 20),
],
@@ -623,9 +760,7 @@ class _RealmMemberListSheet extends HookConsumerWidget {
).then((confirm) async {
if (confirm != true) return;
try {
final apiClient = ref.watch(
apiClientProvider,
);
final apiClient = ref.watch(apiClientProvider);
await apiClient.delete(
'/sphere/realms/$realmSlug/members/${member.accountId}',
);
@@ -646,7 +781,18 @@ class _RealmMemberListSheet extends HookConsumerWidget {
);
},
),
);
}
return Container(
constraints: BoxConstraints(
maxHeight: MediaQuery.of(context).size.height * 0.8,
),
child: Column(
children: [
buildMemberListHeader(),
const Divider(height: 1),
buildMemberListContent(),
],
),
);

View File

@@ -22,6 +22,7 @@ import 'package:island/screens/tabs.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:styled_widget/styled_widget.dart';
import 'package:island/widgets/realm/realm_list_tile.dart';
part 'realms.g.dart';
@@ -95,32 +96,19 @@ class RealmListScreen extends HookConsumerWidget {
(value) => Column(
children: [
Expanded(
child: ListView.builder(
padding: getTabbedPadding(context),
child: ListView.separated(
padding: EdgeInsets.only(
top: 8,
bottom: getTabbedPadding(context).bottom + 8,
),
itemCount: value.length,
itemBuilder: (context, item) {
return ListTile(
isThreeLine: true,
leading: ProfilePictureWidget(
fileId: value[item].picture?.id,
fallbackIcon: Symbols.group,
),
title: Text(value[item].name),
subtitle: Text(value[item].description),
onTap: () {
context.pushNamed(
'realmDetail',
pathParameters: {'slug': value[item].slug},
);
},
contentPadding: const EdgeInsets.only(
left: 16,
right: 14,
top: 8,
bottom: 8,
),
);
return ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 540),
child: RealmListTile(realm: value[item]),
).center();
},
separatorBuilder: (_, _) => const Gap(8),
),
),
],

View File

@@ -10,7 +10,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:island/main.dart';
import 'package:island/route.dart';
import 'package:island/models/user.dart';
import 'package:island/models/account.dart';
import 'package:island/pods/websocket.dart';
import 'package:island/widgets/app_notification.dart';
import 'package:top_snackbar_flutter/top_snack_bar.dart';
@@ -26,7 +26,12 @@ StreamSubscription<WebSocketPacket> setupNotificationListener(
final notification = SnNotification.fromJson(pkt.data!);
showTopSnackBar(
globalOverlay.currentState!,
NotificationCard(notification: notification),
Center(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 480),
child: NotificationCard(notification: notification),
),
),
onTap: () {
if (notification.meta['action_uri'] != null) {
var uri = notification.meta['action_uri'] as String;
@@ -53,9 +58,9 @@ StreamSubscription<WebSocketPacket> setupNotificationListener(
(Platform.isMacOS ||
Platform.isWindows ||
Platform.isLinux))
? 24
? 28
// ignore: use_build_context_synchronously
: MediaQuery.of(context).padding.top + 8,
: MediaQuery.of(context).padding.top + 16,
bottom: 16,
),
);
@@ -67,6 +72,9 @@ Future<void> subscribePushNotification(
Dio apiClient, {
bool detailedErrors = false,
}) async {
if (Platform.isLinux) {
return;
}
await FirebaseMessaging.instance.requestPermission(
alert: true,
badge: true,

View File

@@ -3,33 +3,34 @@ 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/models/account.dart';
import 'package:island/pods/network.dart';
import 'package:island/services/responsive.dart';
import 'package:island/services/udid.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';
part 'account_devices.g.dart';
@riverpod
Future<List<SnAuthDevice>> authDevices(Ref ref) async {
Future<List<SnAuthDeviceWithChallenge>> authDevices(Ref ref) async {
final resp = await ref
.watch(apiClientProvider)
.get('/id/accounts/me/devices');
final sessionId = resp.headers.value('x-auth-session');
final currentId = await getUdid();
final data =
resp.data.map<SnAuthDevice>((e) {
final ele = SnAuthDevice.fromJson(e);
return ele.copyWith(isCurrent: ele.sessions.first.id == sessionId);
resp.data.map<SnAuthDeviceWithChallenge>((e) {
final ele = SnAuthDeviceWithChallenge.fromJson(e);
return ele.copyWith(isCurrent: ele.deviceId == currentId);
}).toList();
return data;
}
class _DeviceListTile extends StatelessWidget {
final SnAuthDevice device;
final SnAuthDeviceWithChallenge device;
final Function(String) updateDeviceLabel;
final Function(String) logoutDevice;
@@ -57,17 +58,16 @@ class _DeviceListTile extends StatelessWidget {
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text('authSessionsCount'.plural(device.sessions.length)),
Text(
'lastActiveAt'.tr(
args: [
DateFormat().format(
device.sessions.first.lastGrantedAt.toLocal(),
device.challenges.first.createdAt.toLocal(),
),
],
),
),
Text(device.sessions.first.challenge.ipAddress),
Text(device.challenges.first.ipAddress),
if (device.isCurrent)
Row(
children: [
@@ -84,7 +84,7 @@ class _DeviceListTile extends StatelessWidget {
).padding(top: 4),
],
),
title: Text(device.label ?? device.sessions.first.challenge.userAgent),
title: Text(device.deviceLabel ?? device.deviceName),
trailing:
isWideScreen(context)
? Row(
@@ -93,14 +93,13 @@ class _DeviceListTile extends StatelessWidget {
IconButton(
icon: Icon(Icons.edit),
tooltip: 'authDeviceEditLabel'.tr(),
onPressed:
() => updateDeviceLabel(device.sessions.first.id),
onPressed: () => updateDeviceLabel(device.deviceId),
),
if (!device.isCurrent)
IconButton(
icon: Icon(Icons.logout),
tooltip: 'authDeviceLogout'.tr(),
onPressed: () => logoutDevice(device.sessions.first.id),
onPressed: () => logoutDevice(device.deviceId),
),
],
)
@@ -124,7 +123,7 @@ class AccountSessionSheet extends HookConsumerWidget {
if (!confirm || !context.mounted) return;
try {
final apiClient = ref.watch(apiClientProvider);
await apiClient.delete('/id/accounts/me/sessions/$sessionId');
await apiClient.delete('/id/accounts/me/devices/$sessionId');
ref.invalidate(authDevicesProvider);
} catch (err) {
showErrorAlert(err);
@@ -163,7 +162,7 @@ class AccountSessionSheet extends HookConsumerWidget {
try {
final apiClient = ref.watch(apiClientProvider);
await apiClient.patch(
'/accounts/me/sessions/$sessionId/label',
'/id/accounts/me/devices/$sessionId/label',
data: jsonEncode(label),
);
ref.invalidate(authDevicesProvider);
@@ -194,7 +193,7 @@ class AccountSessionSheet extends HookConsumerWidget {
);
} else {
return Dismissible(
key: Key('device-${device.sessions.first.id}'),
key: Key('device-${device.id}'),
direction:
device.isCurrent
? DismissDirection.startToEnd
@@ -213,7 +212,7 @@ class AccountSessionSheet extends HookConsumerWidget {
),
confirmDismiss: (direction) async {
if (direction == DismissDirection.startToEnd) {
updateDeviceLabel(device.sessions.first.id);
updateDeviceLabel(device.deviceId);
return false;
} else {
final confirm = await showConfirmAlert(
@@ -221,7 +220,7 @@ class AccountSessionSheet extends HookConsumerWidget {
'authDeviceLogout'.tr(),
);
if (confirm && context.mounted) {
logoutDevice(device.sessions.first.id);
logoutDevice(device.deviceId);
}
return false; // Don't dismiss
}

View File

@@ -1,17 +1,17 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'account_session_sheet.dart';
part of 'account_devices.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$authDevicesHash() => r'8bc41a1ffc37df8e757c977b4ddae11db8faaeb5';
String _$authDevicesHash() => r'feb19238f759921e51c888f8b443a3d7761e68da';
/// See also [authDevices].
@ProviderFor(authDevices)
final authDevicesProvider =
AutoDisposeFutureProvider<List<SnAuthDevice>>.internal(
AutoDisposeFutureProvider<List<SnAuthDeviceWithChallenge>>.internal(
authDevices,
name: r'authDevicesProvider',
debugGetCreateSourceHash:
@@ -24,6 +24,7 @@ final authDevicesProvider =
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
typedef AuthDevicesRef = AutoDisposeFutureProviderRef<List<SnAuthDevice>>;
typedef AuthDevicesRef =
AutoDisposeFutureProviderRef<List<SnAuthDeviceWithChallenge>>;
// 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,7 +1,7 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:island/models/user.dart';
import 'package:island/models/account.dart';
import 'package:island/models/wallet.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:styled_widget/styled_widget.dart';

View File

@@ -1,9 +1,10 @@
import 'dart:async';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/models/user.dart';
import 'package:island/models/account.dart';
import 'package:island/pods/network.dart';
import 'package:island/widgets/content/cloud_files.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
@@ -44,9 +45,8 @@ class AccountPickerSheet extends HookConsumerWidget {
}
return Container(
constraints: BoxConstraints(
maxHeight: MediaQuery.of(context).size.height * 0.4,
),
padding: MediaQuery.of(context).viewInsets,
height: MediaQuery.of(context).size.height * 0.6,
child: Column(
children: [
Padding(
@@ -54,8 +54,8 @@ class AccountPickerSheet extends HookConsumerWidget {
child: TextField(
controller: searchController,
onChanged: onSearchChanged,
decoration: const InputDecoration(
hintText: 'Search accounts...',
decoration: InputDecoration(
hintText: 'searchAccounts'.tr(),
contentPadding: EdgeInsets.symmetric(
horizontal: 18,
vertical: 16,

View File

@@ -1,6 +1,6 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:island/models/user.dart';
import 'package:island/models/account.dart';
import 'package:island/models/badge.dart';
class BadgeList extends StatelessWidget {

View File

@@ -2,7 +2,7 @@ import 'package:dio/dio.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/models/user.dart';
import 'package:island/models/account.dart';
import 'package:island/pods/network.dart';
import 'package:island/screens/account/profile.dart';
import 'package:island/services/time.dart';
@@ -158,3 +158,42 @@ class AccountStatusWidget extends HookConsumerWidget {
).opacity((status.value?.isCustomized ?? false) ? 1 : 0.85);
}
}
class AccountStatusLabel extends StatelessWidget {
final SnAccountStatus status;
final TextStyle? style;
final int maxLines;
final TextOverflow overflow;
const AccountStatusLabel({
super.key,
required this.status,
this.style,
this.maxLines = 1,
this.overflow = TextOverflow.ellipsis,
});
@override
Widget build(BuildContext context) {
return Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Icon(
Symbols.circle,
fill: 1,
color: status.isOnline ? Colors.green : Colors.grey,
size: 14,
).padding(right: 4),
Flexible(
child: Text(
status.label,
style: style,
maxLines: maxLines,
overflow: overflow,
).fontSize(13),
),
],
);
}
}

View File

@@ -4,7 +4,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/models/user.dart';
import 'package:island/models/account.dart';
import 'package:island/pods/network.dart';
import 'package:island/pods/userinfo.dart';
import 'package:island/widgets/account/status.dart';

View File

@@ -11,7 +11,12 @@ export 'content/alert.native.dart'
void showSnackBar(String message, {SnackBarAction? action}) {
showTopSnackBar(
globalOverlay.currentState!,
Card(child: Text(message).padding(horizontal: 20, vertical: 16)),
ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 480),
child: Center(
child: Card(child: Text(message).padding(horizontal: 20, vertical: 16)),
),
),
snackBarPosition: SnackBarPosition.bottom,
);
}
@@ -69,7 +74,7 @@ void showLoadingModal(BuildContext context) {
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
CircularProgressIndicator(year2023: true),
CircularProgressIndicator(year2023: false),
const Gap(24),
Text('loading'.tr()),
],

View File

@@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/models/user.dart';
import 'package:island/models/account.dart';
import 'package:island/widgets/content/cloud_files.dart';
import 'package:material_symbols_icons/material_symbols_icons.dart';
import 'package:styled_widget/styled_widget.dart';

View File

@@ -546,6 +546,26 @@ class _MessageItemContent extends StatelessWidget {
@override
Widget build(BuildContext context) {
switch (item.type) {
case 'deleted':
return Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Icon(
Symbols.delete,
size: 14,
color: Theme.of(context).colorScheme.onSurfaceVariant.withOpacity(0.6),
),
const Gap(4),
Text(
item.content!,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant.withOpacity(0.6),
fontStyle: FontStyle.italic,
),
),
],
);
case 'call.start':
case 'call.ended':
return _MessageContentCall(

View File

@@ -88,6 +88,7 @@ class AttachmentPreview extends HookConsumerWidget {
final Function? onInsert;
final Function(UniversalFile)? onUpdate;
final Function? onRequestUpload;
final bool isCompact;
const AttachmentPreview({
super.key,
@@ -98,6 +99,7 @@ class AttachmentPreview extends HookConsumerWidget {
this.onDelete,
this.onUpdate,
this.onInsert,
this.isCompact = false,
});
// GlobalKey for selector
@@ -361,7 +363,7 @@ class AttachmentPreview extends HookConsumerWidget {
),
],
),
),
).center(),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
@@ -458,9 +460,10 @@ class AttachmentPreview extends HookConsumerWidget {
size: 16,
color: Colors.white,
),
const Gap(8),
if (!isCompact) const Gap(8),
if (!isCompact)
Text(
'On-cloud',
'attachmentOnCloud'.tr(),
style: TextStyle(color: Colors.white),
),
],
@@ -473,9 +476,10 @@ class AttachmentPreview extends HookConsumerWidget {
size: 16,
color: Colors.white,
),
const Gap(8),
if (!isCompact) const Gap(8),
if (!isCompact)
Text(
'On-device',
'attachmentOnDevice'.tr(),
style: TextStyle(color: Colors.white),
),
],

View File

@@ -31,6 +31,7 @@ class CloudFileList extends HookConsumerWidget {
final bool disableZoomIn;
final bool disableConstraint;
final EdgeInsets? padding;
final bool isColumn;
const CloudFileList({
super.key,
required this.files,
@@ -40,6 +41,7 @@ class CloudFileList extends HookConsumerWidget {
this.disableZoomIn = false,
this.disableConstraint = false,
this.padding,
this.isColumn = false,
});
double calculateAspectRatio() {
@@ -63,6 +65,74 @@ class CloudFileList extends HookConsumerWidget {
);
if (files.isEmpty) return const SizedBox.shrink();
if (isColumn) {
final children = <Widget>[];
const maxFiles = 2;
final filesToShow = files.take(maxFiles).toList();
for (var i = 0; i < filesToShow.length; i++) {
final file = filesToShow[i];
final isImage = file.mimeType?.startsWith('image') ?? false;
final isAudio = file.mimeType?.startsWith('audio') ?? false;
final widgetItem = ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(8)),
child: _CloudFileListEntry(
file: file,
heroTag: heroTags[i],
isImage: isImage,
disableZoomIn: disableZoomIn,
onTap: () {
if (!isImage) {
return;
}
if (!disableZoomIn) {
context.pushTransparentRoute(
CloudFileZoomIn(item: file, heroTag: heroTags[i]),
rootNavigator: true,
);
}
},
),
);
Widget item;
if (isAudio) {
item = SizedBox(height: 120, child: widgetItem);
} else {
item = AspectRatio(
aspectRatio: file.fileMeta?['ratio'] as double? ?? 1.0,
child: widgetItem,
);
}
children.add(item);
if (i < filesToShow.length - 1) {
children.add(const Gap(8));
}
}
if (files.length > maxFiles) {
children.add(const Gap(8));
children.add(
Text(
'filesListAdditional'.plural(files.length - filesToShow.length),
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.7),
),
),
);
}
return Padding(
padding: padding ?? EdgeInsets.zero,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: children,
),
);
}
if (files.length == 1) {
final isImage = files.first.mimeType?.startsWith('image') ?? false;
final isAudio = files.first.mimeType?.startsWith('audio') ?? false;

View File

@@ -256,6 +256,7 @@ class ProfilePictureWidget extends ConsumerWidget {
final String? fileId;
final SnCloudFile? file;
final double radius;
final double? borderRadius;
final IconData? fallbackIcon;
final Color? fallbackColor;
const ProfilePictureWidget({
@@ -263,6 +264,7 @@ class ProfilePictureWidget extends ConsumerWidget {
this.fileId,
this.file,
this.radius = 20,
this.borderRadius,
this.fallbackIcon,
this.fallbackColor,
});
@@ -273,7 +275,10 @@ class ProfilePictureWidget extends ConsumerWidget {
final uri = '$serverUrl/drive/files/${file?.id ?? fileId}';
return ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(radius)),
borderRadius:
borderRadius == null
? BorderRadius.all(Radius.circular(radius))
: BorderRadius.all(Radius.circular(borderRadius!)),
child: Container(
width: radius * 2,
height: radius * 2,

View File

@@ -57,11 +57,11 @@ class EmbedLinkWidget extends StatelessWidget {
Row(
children: [
// Favicon
if (link.faviconUrl.isNotEmpty) ...[
if (link.faviconUrl?.isNotEmpty ?? false) ...[
ClipRRect(
borderRadius: BorderRadius.circular(4),
child: UniversalImage(
uri: link.faviconUrl,
uri: link.faviconUrl!,
width: 16,
height: 16,
fit: BoxFit.cover,
@@ -80,8 +80,8 @@ class EmbedLinkWidget extends StatelessWidget {
// Site name
Expanded(
child: Text(
link.siteName.isNotEmpty
? link.siteName
(link.siteName?.isNotEmpty ?? false)
? link.siteName!
: Uri.parse(link.url).host,
style: theme.textTheme.bodySmall?.copyWith(
color: colorScheme.onSurfaceVariant,

View File

@@ -51,9 +51,13 @@ class UniversalImage extends StatelessWidget {
);
},
errorWidget: (context, url, error) {
return const Center(
child: Icon(Icons.broken_image, color: Colors.white, size: 16),
return Image.asset(
'assets/images/media-offline.png',
fit: BoxFit.cover,
);
// return const Center(
// child: Icon(Icons.broken_image, color: Colors.white, size: 16),
// );
},
),
],

View File

@@ -183,9 +183,15 @@ class MarkdownTextContent extends HookConsumerWidget {
);
}
}
final content = ConstrainedBox(
final content = ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(8)),
child: ConstrainedBox(
constraints: BoxConstraints(maxHeight: 360),
child: UniversalImage(uri: uri.toString(), fit: BoxFit.contain),
child: UniversalImage(
uri: uri.toString(),
fit: BoxFit.contain,
),
),
);
return content;
},

View File

@@ -73,6 +73,7 @@ class _UniversalVideoState extends ConsumerState<UniversalVideo> {
return Video(
controller: _videoController!,
aspectRatio: widget.aspectRatio != 1 ? widget.aspectRatio : null,
fit: BoxFit.contain,
controls:
!kIsWeb && (Platform.isAndroid || Platform.isIOS)
? MaterialVideoControls

View File

@@ -1,9 +1,14 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/models/poll.dart';
import 'package:island/pods/network.dart';
import 'package:island/screens/creators/poll/poll_list.dart';
import 'package:island/services/time.dart';
import 'package:island/widgets/content/sheet.dart';
import 'package:island/widgets/poll/poll_stats_widget.dart';
import 'package:island/widgets/response.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
import 'package:styled_widget/styled_widget.dart';
@@ -52,78 +57,93 @@ class PollFeedbackNotifier extends _$PollFeedbackNotifier
class PollFeedbackSheet extends HookConsumerWidget {
final String pollId;
final String? title;
final SnPoll poll;
final Map<String, dynamic>? stats; // stats object similar to PollSubmit
const PollFeedbackSheet({
super.key,
required this.pollId,
required this.poll,
this.title,
this.stats,
});
const PollFeedbackSheet({super.key, required this.pollId, this.title});
@override
Widget build(BuildContext context, WidgetRef ref) {
final poll = ref.watch(pollWithStatsProvider(pollId));
return SheetScaffold(
titleText: title ?? 'Poll feedback',
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_PollHeader(poll: poll, stats: stats),
const Divider(height: 1),
Expanded(
child: PagingHelperView(
child: poll.when(
data:
(data) => CustomScrollView(
slivers: [
SliverToBoxAdapter(child: _PollHeader(poll: data)),
SliverToBoxAdapter(child: const Divider(height: 1)),
SliverGap(4),
PagingHelperSliverView(
provider: pollFeedbackNotifierProvider(pollId),
futureRefreshable: pollFeedbackNotifierProvider(pollId).future,
futureRefreshable:
pollFeedbackNotifierProvider(pollId).future,
notifierRefreshable:
pollFeedbackNotifierProvider(pollId).notifier,
contentBuilder:
(data, widgetCount, endItemView) => ListView.separated(
padding: const EdgeInsets.symmetric(vertical: 4),
(val, widgetCount, endItemView) => SliverList.separated(
itemCount: widgetCount,
itemBuilder: (context, index) {
if (index == widgetCount - 1) {
// Provided by PagingHelperView to indicate end/loading
return endItemView;
}
final answer = data.items[index];
return _PollAnswerTile(answer: answer, poll: poll);
final answer = val.items[index];
return _PollAnswerTile(answer: answer, poll: data);
},
separatorBuilder:
(context, index) =>
const Divider(height: 1).padding(vertical: 4),
),
),
),
SliverGap(4 + MediaQuery.of(context).padding.bottom),
],
),
error:
(err, _) => ResponseErrorWidget(
error: err,
onRetry: () => ref.invalidate(pollWithStatsProvider(pollId)),
),
loading: () => ResponseLoadingWidget(),
),
);
}
}
class _PollHeader extends StatelessWidget {
const _PollHeader({required this.poll, this.stats});
final SnPoll poll;
final Map<String, dynamic>? stats;
const _PollHeader({required this.poll});
final SnPollWithStats poll;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 12,
children: [
if (poll.title != null || (poll.description?.isNotEmpty ?? false))
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (poll.title != null)
Text(poll.title!, style: theme.textTheme.titleLarge),
if (poll.description != null)
Padding(
padding: const EdgeInsets.only(top: 2),
child: Text(
if (poll.description?.isNotEmpty ?? false)
Text(
poll.description!,
style: theme.textTheme.bodyMedium?.copyWith(
color: theme.textTheme.bodyMedium?.color?.withOpacity(0.7),
),
),
],
),
Text('pollQuestions').tr().fontSize(17).bold(),
for (final q in poll.questions)
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (q.title.isNotEmpty) Text(q.title).bold(),
if (q.description?.isNotEmpty ?? false) Text(q.description!),
PollStatsWidget(question: q, stats: poll.stats),
],
),
],
).padding(horizontal: 20, vertical: 16);
@@ -132,7 +152,7 @@ class _PollHeader extends StatelessWidget {
class _PollAnswerTile extends StatelessWidget {
final SnPollAnswer answer;
final SnPoll poll;
final SnPollWithStats poll;
const _PollAnswerTile({required this.answer, required this.poll});
String _formatPerQuestionAnswer(

View File

@@ -0,0 +1,233 @@
import 'package:flutter/material.dart';
import 'package:island/models/poll.dart';
class PollStatsWidget extends StatelessWidget {
const PollStatsWidget({
super.key,
required this.question,
required this.stats,
});
final SnPollQuestion question;
final Map<String, dynamic>? stats;
@override
Widget build(BuildContext context) {
if (stats == null) return const SizedBox.shrink();
final raw = stats![question.id];
if (raw == null) return const SizedBox.shrink();
Widget? body;
switch (question.type) {
case SnPollQuestionType.rating:
// rating: avg score (double or int)
final avg = (raw['rating'] as num?)?.toDouble();
if (avg == null) break;
final theme = Theme.of(context);
body = Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Icon(Icons.star, color: Colors.amber.shade600, size: 18),
const SizedBox(width: 6),
Text(
avg.toStringAsFixed(1),
style: theme.textTheme.labelMedium?.copyWith(
color: theme.colorScheme.onSurfaceVariant,
),
),
],
);
break;
case SnPollQuestionType.yesNo:
// yes/no: map {true: count, false: count}
if (raw is Map) {
final int yes =
(raw[true] is int)
? raw[true] as int
: int.tryParse('${raw[true]}') ?? 0;
final int no =
(raw[false] is int)
? raw[false] as int
: int.tryParse('${raw[false]}') ?? 0;
final total = (yes + no).clamp(0, 1 << 31);
final yesPct = total == 0 ? 0.0 : yes / total;
final noPct = total == 0 ? 0.0 : no / total;
final theme = Theme.of(context);
body = Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_BarStatRow(
label: 'Yes',
count: yes,
fraction: yesPct,
color: Colors.green.shade600,
),
const SizedBox(height: 6),
_BarStatRow(
label: 'No',
count: no,
fraction: noPct,
color: Colors.red.shade600,
),
const SizedBox(height: 4),
Text(
'Total: $total',
style: theme.textTheme.labelSmall?.copyWith(
color: theme.colorScheme.onSurfaceVariant,
),
),
],
);
}
break;
case SnPollQuestionType.singleChoice:
case SnPollQuestionType.multipleChoice:
// map optionId -> count
if (raw is Map) {
final options = [...?question.options]
..sort((a, b) => a.order.compareTo(b.order));
final List<_OptionCount> items = [];
int total = 0;
for (final opt in options) {
final dynamic v = raw[opt.id];
final int count = v is int ? v : int.tryParse('$v') ?? 0;
total += count;
items.add(_OptionCount(id: opt.id, label: opt.label, count: count));
}
if (items.isNotEmpty) {
items.sort(
(a, b) => b.count.compareTo(a.count),
); // show highest first
}
body = Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
for (final it in items)
Padding(
padding: const EdgeInsets.only(bottom: 6),
child: _BarStatRow(
label: it.label,
count: it.count,
fraction: total == 0 ? 0 : it.count / total,
),
),
if (items.isNotEmpty)
Text(
'Total: $total',
style: Theme.of(context).textTheme.labelSmall?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
],
);
}
break;
case SnPollQuestionType.freeText:
// No stats
break;
}
if (body == null) return Text('No stats available');
return Padding(
padding: const EdgeInsets.only(top: 8),
child: DecoratedBox(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceVariant.withOpacity(0.35),
borderRadius: BorderRadius.circular(8),
),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Stats',
style: Theme.of(context).textTheme.labelLarge?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
const SizedBox(height: 8),
body,
],
),
),
),
);
}
}
class _OptionCount {
final String id;
final String label;
final int count;
const _OptionCount({
required this.id,
required this.label,
required this.count,
});
}
class _BarStatRow extends StatelessWidget {
const _BarStatRow({
required this.label,
required this.count,
required this.fraction,
this.color,
});
final String label;
final int count;
final double fraction;
final Color? color;
@override
Widget build(BuildContext context) {
final barColor = color ?? Theme.of(context).colorScheme.primary;
final bgColor = Theme.of(
context,
).colorScheme.surfaceVariant.withOpacity(0.6);
final fg =
(fraction.isNaN || fraction.isInfinite)
? 0.0
: fraction.clamp(0.0, 1.0);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('$label · $count', style: Theme.of(context).textTheme.labelMedium),
const SizedBox(height: 4),
LayoutBuilder(
builder: (context, constraints) {
final width = constraints.maxWidth;
final filled = width * fg;
return Stack(
children: [
Container(
height: 8,
width: width,
decoration: BoxDecoration(
color: bgColor,
borderRadius: BorderRadius.circular(999),
),
),
Container(
height: 8,
width: filled,
decoration: BoxDecoration(
color: barColor,
borderRadius: BorderRadius.circular(999),
),
),
],
);
},
),
],
);
}
}

View File

@@ -1,9 +1,11 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:island/models/poll.dart';
import 'package:island/pods/network.dart';
import 'package:island/widgets/alert.dart';
import 'package:island/widgets/poll/poll_stats_widget.dart';
class PollSubmit extends ConsumerStatefulWidget {
const PollSubmit({
@@ -14,6 +16,7 @@ class PollSubmit extends ConsumerStatefulWidget {
this.initialAnswers,
this.onCancel,
this.showProgress = true,
this.isReadonly = false,
});
final SnPollWithStats poll;
@@ -31,6 +34,8 @@ class PollSubmit extends ConsumerStatefulWidget {
/// Whether to show a progress indicator (e.g., "2 / N").
final bool showProgress;
final bool isReadonly;
@override
ConsumerState<PollSubmit> createState() => _PollSubmitState();
}
@@ -39,6 +44,7 @@ class _PollSubmitState extends ConsumerState<PollSubmit> {
late final List<SnPollQuestion> _questions;
int _index = 0;
bool _submitting = false;
bool _isModifying = false; // New state to track if user is modifying answers
/// Collected answers, keyed by questionId
late Map<String, dynamic> _answers;
@@ -59,7 +65,14 @@ class _PollSubmitState extends ConsumerState<PollSubmit> {
_questions = [...widget.poll.questions]
..sort((a, b) => a.order.compareTo(b.order));
_answers = Map<String, dynamic>.from(widget.initialAnswers ?? {});
if (!widget.isReadonly) {
_loadCurrentIntoLocalState();
// If initial answers are provided, set _isModifying to false initially
// so the "Modify" button is shown.
if (widget.initialAnswers != null && widget.initialAnswers!.isNotEmpty) {
_isModifying = false;
}
}
}
@override
@@ -74,7 +87,11 @@ class _PollSubmitState extends ConsumerState<PollSubmit> {
[...widget.poll.questions]
..sort((a, b) => a.order.compareTo(b.order)),
);
if (!widget.isReadonly) {
_loadCurrentIntoLocalState();
// If poll ID changes, reset modification state
_isModifying = false;
}
}
}
@@ -196,7 +213,7 @@ class _PollSubmitState extends ConsumerState<PollSubmit> {
// Only call onSubmit after server accepts
widget.onSubmit(Map<String, dynamic>.unmodifiable(_answers));
showSnackBar('Poll answer has been submitted.');
showSnackBar('pollAnswerSubmitted'.tr());
HapticFeedback.heavyImpact();
} catch (e) {
showErrorAlert(e);
@@ -268,7 +285,8 @@ class _PollSubmitState extends ConsumerState<PollSubmit> {
],
),
),
if (widget.showProgress)
if (widget.showProgress &&
_isModifying) // Only show progress when modifying
Text(
'${_index + 1} / ${_questions.length}',
style: Theme.of(context).textTheme.labelMedium,
@@ -310,154 +328,13 @@ class _PollSubmitState extends ConsumerState<PollSubmit> {
}
Widget _buildStats(BuildContext context, SnPollQuestion q) {
if (widget.stats == null) return const SizedBox.shrink();
final raw = widget.stats![q.id];
if (raw == null) return const SizedBox.shrink();
Widget? body;
switch (q.type) {
case SnPollQuestionType.rating:
// rating: avg score (double or int)
final avg = (raw['rating'] as num?)?.toDouble();
if (avg == null) break;
final theme = Theme.of(context);
body = Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Icon(Icons.star, color: Colors.amber.shade600, size: 18),
const SizedBox(width: 6),
Text(
avg.toStringAsFixed(1),
style: theme.textTheme.labelMedium?.copyWith(
color: theme.colorScheme.onSurfaceVariant,
),
),
],
);
break;
case SnPollQuestionType.yesNo:
// yes/no: map {true: count, false: count}
if (raw is Map) {
final int yes =
(raw[true] is int)
? raw[true] as int
: int.tryParse('${raw[true]}') ?? 0;
final int no =
(raw[false] is int)
? raw[false] as int
: int.tryParse('${raw[false]}') ?? 0;
final total = (yes + no).clamp(0, 1 << 31);
final yesPct = total == 0 ? 0.0 : yes / total;
final noPct = total == 0 ? 0.0 : no / total;
final theme = Theme.of(context);
body = Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_BarStatRow(
label: 'Yes',
count: yes,
fraction: yesPct,
color: Colors.green.shade600,
),
const SizedBox(height: 6),
_BarStatRow(
label: 'No',
count: no,
fraction: noPct,
color: Colors.red.shade600,
),
const SizedBox(height: 4),
Text(
'Total: $total',
style: theme.textTheme.labelSmall?.copyWith(
color: theme.colorScheme.onSurfaceVariant,
),
),
],
);
}
break;
case SnPollQuestionType.singleChoice:
case SnPollQuestionType.multipleChoice:
// map optionId -> count
if (raw is Map) {
final options = [...?q.options]
..sort((a, b) => a.order.compareTo(b.order));
final List<_OptionCount> items = [];
int total = 0;
for (final opt in options) {
final dynamic v = raw[opt.id];
final int count = v is int ? v : int.tryParse('$v') ?? 0;
total += count;
items.add(_OptionCount(id: opt.id, label: opt.label, count: count));
}
if (items.isNotEmpty) {
items.sort(
(a, b) => b.count.compareTo(a.count),
); // show highest first
}
body = Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
for (final it in items)
Padding(
padding: const EdgeInsets.only(bottom: 6),
child: _BarStatRow(
label: it.label,
count: it.count,
fraction: total == 0 ? 0 : it.count / total,
),
),
if (items.isNotEmpty)
Text(
'Total: $total',
style: Theme.of(context).textTheme.labelSmall?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
],
);
}
break;
case SnPollQuestionType.freeText:
// No stats
break;
}
if (body == null) return const SizedBox.shrink();
return Padding(
padding: const EdgeInsets.only(top: 8),
child: DecoratedBox(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceVariant.withOpacity(0.35),
borderRadius: BorderRadius.circular(8),
),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Stats',
style: Theme.of(context).textTheme.labelLarge?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
const SizedBox(height: 8),
body,
],
),
),
),
);
return PollStatsWidget(question: q, stats: widget.stats);
}
Widget _buildBody(BuildContext context) {
if (widget.initialAnswers != null && !widget.isReadonly && !_isModifying) {
return const SizedBox.shrink(); // Collapse input fields if already submitted and not modifying
}
final q = _current;
switch (q.type) {
case SnPollQuestionType.singleChoice:
@@ -517,9 +394,9 @@ class _PollSubmitState extends ConsumerState<PollSubmit> {
children: [
Expanded(
child: SegmentedButton<bool>(
segments: const [
ButtonSegment(value: true, label: Text('Yes')),
ButtonSegment(value: false, label: Text('No')),
segments: [
ButtonSegment(value: true, label: Text('yes'.tr())),
ButtonSegment(value: false, label: Text('no'.tr())),
],
selected: _yesNoSelected == null ? {} : {_yesNoSelected!},
onSelectionChanged: (sel) {
@@ -568,12 +445,39 @@ class _PollSubmitState extends ConsumerState<PollSubmit> {
final isLast = _index == _questions.length - 1;
final canProceed = _isCurrentAnswered() && !_submitting;
if (widget.initialAnswers != null && !_isModifying && !widget.isReadonly) {
// If poll is submitted and not in modification mode, show "Modify" button
return FilledButton.icon(
icon: const Icon(Icons.edit),
label: Text('modifyAnswers'.tr()),
onPressed: () {
setState(() {
_isModifying = true;
_index = 0; // Reset to first question for modification
_loadCurrentIntoLocalState();
});
},
);
}
return Row(
children: [
OutlinedButton.icon(
icon: const Icon(Icons.arrow_back),
label: Text(_index == 0 ? 'Cancel' : 'Back'),
onPressed: _submitting ? null : _back,
label: Text(_index == 0 ? 'cancel'.tr() : 'back'.tr()),
onPressed:
_submitting
? null
: () {
if (_index == 0 && _isModifying) {
// If at first question and in modification mode, go back to submitted view
setState(() {
_isModifying = false;
});
} else {
_back();
}
},
),
const Spacer(),
FilledButton.icon(
@@ -585,19 +489,188 @@ class _PollSubmitState extends ConsumerState<PollSubmit> {
child: CircularProgressIndicator(strokeWidth: 2),
)
: Icon(isLast ? Icons.check : Icons.arrow_forward),
label: Text(isLast ? 'Submit' : 'Next'),
label: Text(isLast ? 'submit'.tr() : 'next'.tr()),
onPressed: canProceed ? _next : null,
),
],
);
}
Widget _buildSubmittedView(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (widget.poll.title != null || widget.poll.description != null)
Padding(
padding: const EdgeInsets.only(bottom: 12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (widget.poll.title?.isNotEmpty ?? false)
Text(
widget.poll.title!,
style: Theme.of(context).textTheme.titleLarge,
),
if (widget.poll.description?.isNotEmpty ?? false)
Padding(
padding: const EdgeInsets.only(top: 4),
child: Text(
widget.poll.description!,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(
context,
).textTheme.bodyMedium?.color?.withOpacity(0.7),
),
),
),
],
),
),
for (final q in _questions)
Padding(
padding: const EdgeInsets.only(bottom: 16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: Text(
q.title,
style: Theme.of(context).textTheme.titleMedium,
),
),
if (q.isRequired)
Padding(
padding: const EdgeInsets.only(left: 8),
child: Text(
'*',
style: Theme.of(
context,
).textTheme.titleMedium?.copyWith(
color: Theme.of(context).colorScheme.error,
),
),
),
],
),
if (q.description != null)
Padding(
padding: const EdgeInsets.only(top: 4),
child: Text(
q.description!,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(
context,
).textTheme.bodySmall?.color?.withOpacity(0.7),
),
),
),
_buildStats(context, q),
],
),
),
],
);
}
Widget _buildReadonlyView(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (widget.poll.title != null || widget.poll.description != null)
Padding(
padding: const EdgeInsets.only(bottom: 12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (widget.poll.title != null)
Text(
widget.poll.title!,
style: Theme.of(context).textTheme.titleLarge,
),
if (widget.poll.description != null)
Padding(
padding: const EdgeInsets.only(top: 4),
child: Text(
widget.poll.description!,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(
context,
).textTheme.bodyMedium?.color?.withOpacity(0.7),
),
),
),
],
),
),
for (final q in _questions)
Padding(
padding: const EdgeInsets.only(bottom: 16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: Text(
q.title,
style: Theme.of(context).textTheme.titleMedium,
),
),
if (q.isRequired)
Padding(
padding: const EdgeInsets.only(left: 8),
child: Text(
'*',
style: Theme.of(
context,
).textTheme.titleMedium?.copyWith(
color: Theme.of(context).colorScheme.error,
),
),
),
],
),
if (q.description != null)
Padding(
padding: const EdgeInsets.only(top: 4),
child: Text(
q.description!,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(
context,
).textTheme.bodySmall?.color?.withOpacity(0.7),
),
),
),
_buildStats(context, q),
],
),
),
],
);
}
@override
Widget build(BuildContext context) {
if (_questions.isEmpty) {
return const SizedBox.shrink();
}
// If poll is already submitted and not in readonly mode, and not in modification mode, show submitted view
if (widget.initialAnswers != null && !widget.isReadonly && !_isModifying) {
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [_buildSubmittedView(context), _buildNavBar(context)],
);
}
// If poll is in readonly mode, show readonly view
if (widget.isReadonly) {
return _buildReadonlyView(context);
}
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
@@ -617,77 +690,6 @@ class _PollSubmitState extends ConsumerState<PollSubmit> {
}
}
class _OptionCount {
final String id;
final String label;
final int count;
const _OptionCount({
required this.id,
required this.label,
required this.count,
});
}
class _BarStatRow extends StatelessWidget {
const _BarStatRow({
required this.label,
required this.count,
required this.fraction,
this.color,
});
final String label;
final int count;
final double fraction;
final Color? color;
@override
Widget build(BuildContext context) {
final barColor = color ?? Theme.of(context).colorScheme.primary;
final bgColor = Theme.of(
context,
).colorScheme.surfaceVariant.withOpacity(0.6);
final fg =
(fraction.isNaN || fraction.isInfinite)
? 0.0
: fraction.clamp(0.0, 1.0);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('$label · $count', style: Theme.of(context).textTheme.labelMedium),
const SizedBox(height: 4),
LayoutBuilder(
builder: (context, constraints) {
final width = constraints.maxWidth;
final filled = width * fg;
return Stack(
children: [
Container(
height: 8,
width: width,
decoration: BoxDecoration(
color: bgColor,
borderRadius: BorderRadius.circular(999),
),
),
Container(
height: 8,
width: filled,
decoration: BoxDecoration(
color: barColor,
borderRadius: BorderRadius.circular(999),
),
),
],
);
},
),
],
);
}
}
/// Simple fade/slide transition between questions.
class _AnimatedStep extends StatelessWidget {
const _AnimatedStep({super.key, required this.child});

View File

@@ -186,10 +186,9 @@ class ComposePollSheet extends HookConsumerWidget {
);
}
Widget? _buildPollSubtitle(SnPoll poll) {
Widget? _buildPollSubtitle(SnPollWithStats poll) {
try {
final SnPoll dyn = poll;
final List<SnPollQuestion> options = dyn.questions;
final List<SnPollQuestion> options = poll.questions;
if (options.isEmpty) return null;
final preview = options.take(3).map((e) => e.title).join(' · ');
if (preview.trim().isEmpty) return null;

View File

@@ -5,11 +5,15 @@ import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/models/post_category.dart';
import 'package:island/models/realm.dart';
import 'package:island/pods/network.dart';
import 'package:island/screens/realm/realms.dart';
import 'package:island/widgets/content/cloud_files.dart';
import 'package:island/widgets/content/sheet.dart';
import 'package:island/widgets/post/compose_shared.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:styled_widget/styled_widget.dart';
import 'package:textfield_tags/textfield_tags.dart';
part 'compose_settings_sheet.g.dart';
@@ -129,7 +133,9 @@ class ComposeSettingsSheet extends HookConsumerWidget {
// Listen to visibility changes to trigger rebuilds
final currentVisibility = useValueListenable(state.visibility);
final currentCategories = useValueListenable(state.categories);
final currentRealm = useValueListenable(state.realm);
final postCategories = ref.watch(postCategoriesProvider);
final userRealms = ref.watch(realmsJoinedProvider);
IconData getVisibilityIcon(int visibilityValue) {
switch (visibilityValue) {
@@ -223,6 +229,24 @@ class ComposeSettingsSheet extends HookConsumerWidget {
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 16,
children: [
// Slug field
TextField(
controller: state.slugController,
decoration: InputDecoration(
labelText: 'postSlug'.tr(),
hintText: 'postSlugHint'.tr(),
contentPadding: const EdgeInsets.symmetric(
vertical: 9,
horizontal: 16,
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
),
onTapOutside:
(_) => FocusManager.instance.primaryFocus?.unfocus(),
),
// Tags field
TextFieldTags(
textfieldTagsController: state.tagsController,
@@ -244,7 +268,6 @@ class ComposeSettingsSheet extends HookConsumerWidget {
),
// Categories field
// FIXME: Sometimes the entire dropdown crashes: 'package:flutter/src/rendering/stack.dart': Failed assertion: line 799 pos 12: 'firstChild == null || child != null': is not true.
DropdownButtonFormField2<SnPostCategory>(
isExpanded: true,
decoration: InputDecoration(
@@ -306,7 +329,7 @@ class ComposeSettingsSheet extends HookConsumerWidget {
value: currentCategories.isEmpty ? null : currentCategories.last,
onChanged: (_) {},
selectedItemBuilder: (context) {
return currentCategories.map((item) {
return (postCategories.value ?? []).map((item) {
return SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
@@ -335,12 +358,89 @@ class ComposeSettingsSheet extends HookConsumerWidget {
);
}).toList();
},
buttonStyleData: const ButtonStyleData(
padding: EdgeInsets.only(left: 16, right: 8),
height: 38,
),
menuItemStyleData: const MenuItemStyleData(
height: 38,
padding: EdgeInsets.zero,
),
),
// Realm selection
DropdownButtonFormField2<SnRealm?>(
isExpanded: true,
decoration: InputDecoration(
contentPadding: const EdgeInsets.symmetric(vertical: 9),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
),
hint: Text('realm'.tr(), style: const TextStyle(fontSize: 15)),
items: [
DropdownMenuItem<SnRealm?>(
value: null,
child: Row(
children: [
const CircleAvatar(
radius: 16,
child: Icon(Symbols.link_off, fill: 1),
),
const SizedBox(width: 12),
Text('postUnlinkRealm').tr(),
],
).padding(left: 16, right: 8),
),
if (userRealms.hasValue)
...(userRealms.value ?? []).map(
(realm) => DropdownMenuItem<SnRealm?>(
value: realm,
child: Row(
children: [
ProfilePictureWidget(
fileId: realm.picture?.id,
fallbackIcon: Symbols.workspaces,
radius: 16,
),
const SizedBox(width: 12),
Text(realm.name),
],
).padding(left: 16, right: 8),
),
),
],
value: currentRealm,
onChanged: (value) {
state.realm.value = value;
},
selectedItemBuilder: (context) {
return (userRealms.value ?? []).map((_) {
return Row(
children: [
if (currentRealm == null)
const CircleAvatar(
radius: 16,
child: Icon(Symbols.link_off, fill: 1),
)
else
ProfilePictureWidget(
fileId: currentRealm.picture?.id,
fallbackIcon: Symbols.workspaces,
radius: 16,
),
const SizedBox(width: 12),
Text(currentRealm?.name ?? 'postUnlinkRealm'.tr()),
],
);
}).toList();
},
buttonStyleData: const ButtonStyleData(
padding: EdgeInsets.only(left: 16, right: 8),
height: 40,
),
menuItemStyleData: const MenuItemStyleData(
height: 40,
height: 56,
padding: EdgeInsets.zero,
),
),

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