Compare commits
86 Commits
3.1.0+115
...
1def3e1895
| Author | SHA1 | Date | |
|---|---|---|---|
| 1def3e1895 | |||
| 550c74e544 | |||
| a39565f012 | |||
| aa9755e6a7 | |||
| b25e8d661a | |||
| 4b253ac3ec | |||
| 5d1b875d3c | |||
| e2e103fa67 | |||
| 43c90da4e3 | |||
| fa210dd98f | |||
| 43d9ca92bf | |||
| 5e592c143f | |||
| 0c59816f26 | |||
| 19c2457895 | |||
| af8d87857e | |||
| d05f63a36a | |||
| e2dc520012 | |||
| cff9c15e31 | |||
| f00135c4bf | |||
| 30b8a6c30f | |||
| b9c4ee31b1 | |||
| 87870af866 | |||
| b83cb0fb0b | |||
| 7fd1fe34e5 | |||
| 1c18330891 | |||
| d320879ad0 | |||
| 950150e119 | |||
| 3c4a9767e1 | |||
| 5df2445f3f | |||
| 56543d7b4c | |||
| 4c6fea1242 | |||
| fff43de9e3 | |||
| b31a915544 | |||
| 8956723ac5 | |||
| ccc3ac415e | |||
| 8c47a59b80 | |||
| a6d869ebf6 | |||
| f3a8699389 | |||
| d345c00e84 | |||
| a706f127b6 | |||
| 680ece0b6a | |||
| b976c6ed37 | |||
| 6ae6b132de | |||
| 95aec7c95b | |||
| edd760fbcb | |||
| ba269dbbb8 | |||
| 1aa45dd9f1 | |||
| 92685d7410 | |||
| c8e351514d | |||
| f3900825e3 | |||
| 2cc6652f75 | |||
| 4d4409de2e | |||
| e1286c797f | |||
| bec037622f | |||
| a0d8c1a9b3 | |||
| 26135d2116 | |||
| 71b67fd22d | |||
| 855072dfea | |||
| b39e2e2d64 | |||
| 84b1d6a346 | |||
| 28335dd548 | |||
| 7253e2d3ef | |||
| 4d489425fa | |||
| 890a8a44cf | |||
| 8e3583f57a | |||
| d0ff14659f | |||
| 1f7caaeaac | |||
| 9f9f42071a | |||
| 6bd6e994cb | |||
| 02e68d76ee | |||
| d04b06089c | |||
| 9be6fea2e0 | |||
| 6b1214a06f | |||
| 4597373ac9 | |||
| 047c8d93aa | |||
| 715f95ca22 | |||
| ba709012d7 | |||
| fd186f8391 | |||
| 262d36cd2d | |||
| f320855348 | |||
| ed90152462 | |||
| 6e5c5f1690 | |||
| 7c92dee097 | |||
| e4bb031138 | |||
| 97226ae96b | |||
| d8cd33e79a |
@@ -59,7 +59,6 @@ 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.google.firebase:firebase-messaging-ktx")
|
||||
}
|
||||
|
||||
flutter {
|
||||
|
||||
@@ -12,7 +12,12 @@
|
||||
"package_name": "dev.solsynth.solian"
|
||||
}
|
||||
},
|
||||
"oauth_client": [],
|
||||
"oauth_client": [
|
||||
{
|
||||
"client_id": "961776991058-963m1qin2vtp8fv693b5fdrab5hmpl89.apps.googleusercontent.com",
|
||||
"client_type": 3
|
||||
}
|
||||
],
|
||||
"api_key": [
|
||||
{
|
||||
"current_key": "AIzaSyDvFNudXYs29uDtcCv6pFR8h5tXBs90FYk"
|
||||
@@ -20,7 +25,20 @@
|
||||
],
|
||||
"services": {
|
||||
"appinvite_service": {
|
||||
"other_platform_oauth_client": []
|
||||
"other_platform_oauth_client": [
|
||||
{
|
||||
"client_id": "961776991058-963m1qin2vtp8fv693b5fdrab5hmpl89.apps.googleusercontent.com",
|
||||
"client_type": 3
|
||||
},
|
||||
{
|
||||
"client_id": "961776991058-stt7et4qvn3cpscl4r61gl1hnlatqkig.apps.googleusercontent.com",
|
||||
"client_type": 2,
|
||||
"ios_info": {
|
||||
"bundle_id": "dev.solsynth.solian",
|
||||
"app_store_id": "6499032345"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
|
||||
@@ -89,6 +90,13 @@
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<!-- Livekit Screenshare -->
|
||||
<service
|
||||
android:name="de.julianassmann.flutter_background.IsolateHolderService"
|
||||
android:enabled="true"
|
||||
android:exported="false"
|
||||
android:foregroundServiceType="mediaProjection" />
|
||||
|
||||
<!-- Sign in with Apple -->
|
||||
<activity
|
||||
android:name="com.aboutyou.dart_packages.sign_in_with_apple.SignInWithAppleCallback"
|
||||
@@ -109,14 +117,6 @@
|
||||
android:enabled="true"
|
||||
android:exported="true" />
|
||||
|
||||
<service
|
||||
android:name=".service.MessagingService"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="com.google.firebase.MESSAGING_EVENT" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="dev.solsynth.solian.provider"
|
||||
@@ -143,4 +143,4 @@
|
||||
<data android:mimeType="text/plain" />
|
||||
</intent>
|
||||
</queries>
|
||||
</manifest>
|
||||
</manifest>
|
||||
|
||||
@@ -1,102 +0,0 @@
|
||||
package dev.solsynth.solian.service
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.content.Intent
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.os.Build
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.core.app.RemoteInput
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.request.target.CustomTarget
|
||||
import com.bumptech.glide.request.transition.Transition
|
||||
import com.google.firebase.messaging.FirebaseMessagingService
|
||||
import com.google.firebase.messaging.RemoteMessage
|
||||
import dev.solsynth.solian.MainActivity
|
||||
import dev.solsynth.solian.receiver.ReplyReceiver
|
||||
import org.json.JSONObject
|
||||
|
||||
class MessagingService: FirebaseMessagingService() {
|
||||
override fun onMessageReceived(remoteMessage: RemoteMessage) {
|
||||
val type = remoteMessage.data["type"]
|
||||
if (type == "messages.new") {
|
||||
handleMessageNotification(remoteMessage)
|
||||
} else {
|
||||
// Handle other notification types
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleMessageNotification(remoteMessage: RemoteMessage) {
|
||||
val data = remoteMessage.data
|
||||
val metaString = data["meta"] ?: return
|
||||
val meta = JSONObject(metaString)
|
||||
|
||||
val pfp = meta.optString("pfp", null)
|
||||
val roomId = meta.optString("room_id", null)
|
||||
val messageId = meta.optString("message_id", null)
|
||||
|
||||
val notificationId = System.currentTimeMillis().toInt()
|
||||
|
||||
val replyLabel = "Reply"
|
||||
val remoteInput = RemoteInput.Builder("key_text_reply")
|
||||
.setLabel(replyLabel)
|
||||
.build()
|
||||
|
||||
val replyIntent = Intent(this, ReplyReceiver::class.java).apply {
|
||||
putExtra("room_id", roomId)
|
||||
putExtra("message_id", messageId)
|
||||
putExtra("notification_id", notificationId)
|
||||
}
|
||||
|
||||
val pendingIntentFlags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||
} else {
|
||||
PendingIntent.FLAG_UPDATE_CURRENT
|
||||
}
|
||||
|
||||
val replyPendingIntent = PendingIntent.getBroadcast(
|
||||
applicationContext,
|
||||
notificationId,
|
||||
replyIntent,
|
||||
pendingIntentFlags
|
||||
)
|
||||
|
||||
val action = NotificationCompat.Action.Builder(
|
||||
android.R.drawable.ic_menu_send,
|
||||
replyLabel,
|
||||
replyPendingIntent
|
||||
)
|
||||
.addRemoteInput(remoteInput)
|
||||
.build()
|
||||
|
||||
val intent = Intent(this, MainActivity::class.java)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||
intent.putExtra("room_id", roomId)
|
||||
val pendingIntent = PendingIntent.getActivity(this, 0, intent, pendingIntentFlags)
|
||||
|
||||
val notificationBuilder = NotificationCompat.Builder(this, "messages")
|
||||
.setSmallIcon(android.R.drawable.ic_dialog_info)
|
||||
.setContentTitle(remoteMessage.notification?.title)
|
||||
.setContentText(remoteMessage.notification?.body)
|
||||
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||
.setContentIntent(pendingIntent)
|
||||
.addAction(action)
|
||||
|
||||
if (pfp != null) {
|
||||
Glide.with(applicationContext)
|
||||
.asBitmap()
|
||||
.load(pfp)
|
||||
.into(object : CustomTarget<Bitmap>() {
|
||||
override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap>?) {
|
||||
notificationBuilder.setLargeIcon(resource)
|
||||
NotificationManagerCompat.from(applicationContext).notify(notificationId, notificationBuilder.build())
|
||||
}
|
||||
|
||||
override fun onLoadCleared(placeholder: Drawable?) {}
|
||||
})
|
||||
} else {
|
||||
NotificationManagerCompat.from(this).notify(notificationId, notificationBuilder.build())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -144,9 +144,16 @@
|
||||
"other": "{} attachments"
|
||||
},
|
||||
"edited": "Edited",
|
||||
"editedAt": "Edited at {}",
|
||||
"addVideo": "Add video",
|
||||
"addPhoto": "Add photo",
|
||||
"addAudio": "Add audio",
|
||||
"addFile": "Add file",
|
||||
"recordAudio": "Record Audio",
|
||||
"linkAttachment": "Link Attachment",
|
||||
"fileIdCannotBeEmpty": "File ID cannot be empty",
|
||||
"fileIdLinkHint": "Haven't upload to the Solar Network? Tap here to open Solar Network Drive to customize your uploads.",
|
||||
"failedToFetchFile": "Failed to fetch file: {}",
|
||||
"createDirectMessage": "Send new DM",
|
||||
"gotoDirectMessage": "Go to DM",
|
||||
"react": "React",
|
||||
@@ -352,6 +359,8 @@
|
||||
"postTitle": "Title",
|
||||
"postDescription": "Description",
|
||||
"call": "Call",
|
||||
"callLeave": "Leave",
|
||||
"callEnd": "End this call",
|
||||
"done": "Done",
|
||||
"loginResetPasswordSent": "Password reset link sent, please check your email inbox.",
|
||||
"accountDeletion": "Delete Account",
|
||||
@@ -622,8 +631,8 @@
|
||||
"chatJoin": "Join the Chat",
|
||||
"realmJoin": "Join the Realm",
|
||||
"realmJoinSuccess": "Successfully joined the realm.",
|
||||
"discoverRealms": "Discover Realms",
|
||||
"discoverPublishers": "Discover Publishers",
|
||||
"discoverRealms": "Discover realms",
|
||||
"discoverPublishers": "Discover publishers",
|
||||
"search": "Search",
|
||||
"publisherMembers": "Collaborators",
|
||||
"developerHub": "Developer Hub",
|
||||
@@ -702,5 +711,76 @@
|
||||
"aboutDeviceName": "Device Name",
|
||||
"aboutDeviceIdentifier": "Device Identifier",
|
||||
"donate": "Donate",
|
||||
"donateDescription": "Support us to continue developing the Solar Network and keep the server up and running."
|
||||
}
|
||||
"donateDescription": "Support us to continue developing the Solar Network and keep the server up and running.",
|
||||
"fileId": "File ID",
|
||||
"fileIdHint": "The file ID is the ID you get after upload the file via the Solar Network Drive.",
|
||||
"translate": "Translate",
|
||||
"translating": "Translating",
|
||||
"translated": "Translated",
|
||||
"reactionThumbUp": "Thumbs Up",
|
||||
"reactionThumbDown": "Thumbs Down",
|
||||
"reactionJustOkay": "Just Okay",
|
||||
"reactionCry": "Cry",
|
||||
"reactionConfuse": "Confused",
|
||||
"reactionClap": "Clap",
|
||||
"reactionLaugh": "Laugh",
|
||||
"reactionAngry": "Angry",
|
||||
"reactionParty": "Party",
|
||||
"reactionPray": "Pray",
|
||||
"reactionHeart": "Heart",
|
||||
"selectMicrophone": "Select Microphone",
|
||||
"selectCamera": "Select Camera",
|
||||
"switchedTo": "Switched to {}",
|
||||
"connecting": "Connecting",
|
||||
"reconnecting": "Reconnecting",
|
||||
"disconnected": "Disconnected",
|
||||
"connected": "Connected",
|
||||
"repliesLoadMore": "Load more replies",
|
||||
"attachmentsRecentUploads": "Recent Uploads",
|
||||
"attachmentsManualInput": "Manual Input",
|
||||
"crop": "Crop",
|
||||
"rename": "Rename",
|
||||
"markAsSensitive": "Mark as Sensitive",
|
||||
"fileName": "File name",
|
||||
"sensitiveCategories.language": "Language",
|
||||
"sensitiveCategories.sexualContent": "Sexual Content",
|
||||
"sensitiveCategories.violence": "Violence",
|
||||
"sensitiveCategories.profanity": "Profanity",
|
||||
"sensitiveCategories.hateSpeech": "Hate Speech",
|
||||
"sensitiveCategories.racism": "Racism",
|
||||
"sensitiveCategories.adultContent": "Adult Content",
|
||||
"sensitiveCategories.drugAbuse": "Drug Abuse",
|
||||
"sensitiveCategories.alcoholAbuse": "Alcohol Abuse",
|
||||
"sensitiveCategories.gambling": "Gambling",
|
||||
"sensitiveCategories.selfHarm": "Self-harm",
|
||||
"sensitiveCategories.childAbuse": "Child Abuse",
|
||||
"sensitiveCategories.other": "Other",
|
||||
"poll": "Poll",
|
||||
"pollsRecent": "Recent Polls",
|
||||
"pollCreateNew": "Create New",
|
||||
"pollCreateNewHint": "Create a new poll for your post. Pick a publisher and continue.",
|
||||
"publisher": "Publisher",
|
||||
"publisherHint": "Enter the publisher name",
|
||||
"publisherCannotBeEmpty": "Publisher cannot be empty",
|
||||
"operationFailed": "Operation failed: {}",
|
||||
"stickerMarketplace": "Sticker Marketplace",
|
||||
"stickerPackAdded": "Sticker pack added to your collection",
|
||||
"stickerPackRemoved": "Sticker pack removed from your collection",
|
||||
"addPack": "Add Pack",
|
||||
"removePack": "Remove Pack",
|
||||
"browseAndAddStickers": "Browse and add sticker packs",
|
||||
"stickerPack": "Sticker Pack",
|
||||
"postCategoryTechnology": "Technology",
|
||||
"postCategoryTravel": "Travel",
|
||||
"postCategoryFood": "Food",
|
||||
"postCategoryHealth": "Health",
|
||||
"postCategoryScience": "Science",
|
||||
"postCategorySports": "Sports",
|
||||
"postCategoryFinance": "Finance",
|
||||
"postCategoryLife": "Life",
|
||||
"postCategoryArt": "Art",
|
||||
"postCategoryStudy": "Study",
|
||||
"postCategoryGaming": "Gaming",
|
||||
"postCategoryProgramming": "Programming",
|
||||
"postCategoryMusic": "Music"
|
||||
}
|
||||
|
||||
@@ -120,9 +120,14 @@
|
||||
"other": "{}个附件"
|
||||
},
|
||||
"edited": "已编辑",
|
||||
"editedAt": "编辑于 {}",
|
||||
"addVideo": "添加视频",
|
||||
"addPhoto": "添加照片",
|
||||
"addFile": "添加文件",
|
||||
"addAttachmentById": "通过 ID 添加附件",
|
||||
"enterFileId": "输入文件 ID",
|
||||
"fileIdCannotBeEmpty": "文件 ID 不能为空",
|
||||
"failedToFetchFile": "获取文件失败: {}",
|
||||
"createDirectMessage": "创建新私人消息",
|
||||
"gotoDirectMessage": "前往私信",
|
||||
"react": "反应",
|
||||
|
||||
@@ -123,6 +123,10 @@
|
||||
"addVideo": "新增影片",
|
||||
"addPhoto": "新增照片",
|
||||
"addFile": "新增檔案",
|
||||
"addAttachmentById": "透過 ID 新增附件",
|
||||
"enterFileId": "輸入檔案 ID",
|
||||
"fileIdCannotBeEmpty": "檔案 ID 不能為空",
|
||||
"failedToFetchFile": "無法取得檔案: {}",
|
||||
"createDirectMessage": "建立新私人訊息",
|
||||
"gotoDirectMessage": "Go to DM",
|
||||
"react": "反應",
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"flutter":{"platforms":{"android":{"default":{"projectId":"solian-0x001","appId":"1:961776991058:android:a8d3f7995b0b8e86f4188b","fileOutput":"android/app/google-services.json"}},"ios":{"default":{"projectId":"solian-0x001","appId":"1:961776991058:ios:727229d368cc47e1f4188b","uploadDebugSymbols":false,"fileOutput":"ios/Runner/GoogleService-Info.plist"}},"macos":{"default":{"projectId":"solian-0x001","appId":"1:961776991058:ios:727229d368cc47e1f4188b","uploadDebugSymbols":false,"fileOutput":"macos/Runner/GoogleService-Info.plist"}},"dart":{"lib/firebase_options.dart":{"projectId":"solian-0x001","configurations":{"android":"1:961776991058:android:a8d3f7995b0b8e86f4188b","ios":"1:961776991058:ios:727229d368cc47e1f4188b","macos":"1:961776991058:ios:727229d368cc47e1f4188b","web":"1:961776991058:web:b91d12f2892a5609f4188b","windows":"1:961776991058:web:3a912c0eb14028e5f4188b"}}}}}}
|
||||
{"flutter":{"platforms":{"android":{"default":{"projectId":"solian-0x001","appId":"1:961776991058:android:a8d3f7995b0b8e86f4188b","fileOutput":"android/app/google-services.json"}},"ios":{"default":{"projectId":"solian-0x001","appId":"1:961776991058:ios:727229d368cc47e1f4188b","uploadDebugSymbols":false,"fileOutput":"ios/Runner/GoogleService-Info.plist"}},"macos":{"default":{"projectId":"solian-0x001","appId":"1:961776991058:ios:727229d368cc47e1f4188b","uploadDebugSymbols":false,"fileOutput":"macos/Runner/GoogleService-Info.plist"}},"dart":{"lib/firebase_options.dart":{"projectId":"solian-0x001","configurations":{"android":"1:961776991058:android:a8d3f7995b0b8e86f4188b","ios":"1:961776991058:ios:727229d368cc47e1f4188b","macos":"1:961776991058:ios:727229d368cc47e1f4188b","web":"1:961776991058:web:3a912c0eb14028e5f4188b","windows":"1:961776991058:web:3a912c0eb14028e5f4188b"}}}}}}
|
||||
@@ -1,5 +1,5 @@
|
||||
# Uncomment this line to define a global platform for your project
|
||||
platform :ios, '13.0'
|
||||
platform :ios, '15.0'
|
||||
|
||||
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||
|
||||
@@ -40,33 +40,33 @@ PODS:
|
||||
- file_picker (0.0.1):
|
||||
- DKImagePickerController/PhotoGallery
|
||||
- Flutter
|
||||
- Firebase/CoreOnly (11.15.0):
|
||||
- FirebaseCore (~> 11.15.0)
|
||||
- Firebase/Messaging (11.15.0):
|
||||
- Firebase/CoreOnly (12.0.0):
|
||||
- FirebaseCore (~> 12.0.0)
|
||||
- Firebase/Messaging (12.0.0):
|
||||
- Firebase/CoreOnly
|
||||
- FirebaseMessaging (~> 11.15.0)
|
||||
- firebase_core (3.15.2):
|
||||
- Firebase/CoreOnly (= 11.15.0)
|
||||
- FirebaseMessaging (~> 12.0.0)
|
||||
- firebase_core (4.0.0):
|
||||
- Firebase/CoreOnly (= 12.0.0)
|
||||
- Flutter
|
||||
- firebase_messaging (15.2.10):
|
||||
- Firebase/Messaging (= 11.15.0)
|
||||
- firebase_messaging (16.0.0):
|
||||
- Firebase/Messaging (= 12.0.0)
|
||||
- firebase_core
|
||||
- Flutter
|
||||
- FirebaseCore (11.15.0):
|
||||
- FirebaseCoreInternal (~> 11.15.0)
|
||||
- FirebaseCore (12.0.0):
|
||||
- FirebaseCoreInternal (~> 12.0.0)
|
||||
- GoogleUtilities/Environment (~> 8.1)
|
||||
- GoogleUtilities/Logger (~> 8.1)
|
||||
- FirebaseCoreInternal (11.15.0):
|
||||
- FirebaseCoreInternal (12.0.0):
|
||||
- "GoogleUtilities/NSData+zlib (~> 8.1)"
|
||||
- FirebaseInstallations (11.15.0):
|
||||
- FirebaseCore (~> 11.15.0)
|
||||
- FirebaseInstallations (12.0.0):
|
||||
- FirebaseCore (~> 12.0.0)
|
||||
- GoogleUtilities/Environment (~> 8.1)
|
||||
- GoogleUtilities/UserDefaults (~> 8.1)
|
||||
- PromisesObjC (~> 2.4)
|
||||
- FirebaseMessaging (11.15.0):
|
||||
- FirebaseCore (~> 11.15.0)
|
||||
- FirebaseInstallations (~> 11.0)
|
||||
- GoogleDataTransport (~> 10.0)
|
||||
- FirebaseMessaging (12.0.0):
|
||||
- FirebaseCore (~> 12.0.0)
|
||||
- FirebaseInstallations (~> 12.0.0)
|
||||
- GoogleDataTransport (~> 10.1)
|
||||
- GoogleUtilities/AppDelegateSwizzler (~> 8.1)
|
||||
- GoogleUtilities/Environment (~> 8.1)
|
||||
- GoogleUtilities/Reachability (~> 8.1)
|
||||
@@ -93,9 +93,9 @@ PODS:
|
||||
- flutter_udid (0.0.1):
|
||||
- Flutter
|
||||
- SAMKeychain
|
||||
- flutter_webrtc (0.14.0):
|
||||
- flutter_webrtc (1.0.0):
|
||||
- Flutter
|
||||
- WebRTC-SDK (= 125.6422.07)
|
||||
- WebRTC-SDK (= 137.7151.02)
|
||||
- gal (1.0.0):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
@@ -131,10 +131,10 @@ PODS:
|
||||
- irondash_engine_context (0.0.1):
|
||||
- Flutter
|
||||
- Kingfisher (8.5.0)
|
||||
- livekit_client (2.4.9):
|
||||
- livekit_client (2.5.0):
|
||||
- Flutter
|
||||
- flutter_webrtc
|
||||
- WebRTC-SDK (= 125.6422.07)
|
||||
- WebRTC-SDK (= 137.7151.02)
|
||||
- local_auth_darwin (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
@@ -191,6 +191,8 @@ PODS:
|
||||
- sqlite3/common
|
||||
- sqlite3/rtree (3.50.3):
|
||||
- sqlite3/common
|
||||
- sqlite3/session (3.50.3):
|
||||
- sqlite3/common
|
||||
- sqlite3_flutter_libs (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
@@ -200,6 +202,7 @@ PODS:
|
||||
- sqlite3/math
|
||||
- sqlite3/perf-threadsafe
|
||||
- sqlite3/rtree
|
||||
- sqlite3/session
|
||||
- super_native_extensions (0.0.1):
|
||||
- Flutter
|
||||
- SwiftyGif (5.4.5)
|
||||
@@ -209,7 +212,7 @@ PODS:
|
||||
- Flutter
|
||||
- wakelock_plus (0.0.1):
|
||||
- Flutter
|
||||
- WebRTC-SDK (125.6422.07)
|
||||
- WebRTC-SDK (137.7151.02)
|
||||
|
||||
DEPENDENCIES:
|
||||
- Alamofire
|
||||
@@ -361,13 +364,13 @@ SPEC CHECKSUMS:
|
||||
DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
|
||||
DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
|
||||
file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be
|
||||
Firebase: d99ac19b909cd2c548339c2241ecd0d1599ab02e
|
||||
firebase_core: 995454a784ff288be5689b796deb9e9fa3601818
|
||||
firebase_messaging: f4a41dd102ac18b840eba3f39d67e77922d3f707
|
||||
FirebaseCore: efb3893e5b94f32b86e331e3bd6dadf18b66568e
|
||||
FirebaseCoreInternal: 9afa45b1159304c963da48addb78275ef701c6b4
|
||||
FirebaseInstallations: 317270fec08a5d418fdbc8429282238cab3ac843
|
||||
FirebaseMessaging: 3b26e2cee503815e01c3701236b020aa9b576f09
|
||||
Firebase: 800d487043c0557d9faed71477a38d9aafb08a41
|
||||
firebase_core: 633e1851ffe1b9ab875f6467a4f574c79cef02e4
|
||||
firebase_messaging: d17feef781edc84ebefe62624fb384358ad96361
|
||||
FirebaseCore: 055f4ab117d5964158c833f3d5e7ec6d91648d4a
|
||||
FirebaseCoreInternal: dedc28e569a4be85f38f3d6af1070a2e12018d55
|
||||
FirebaseInstallations: d4c7c958f99c8860d7fcece786314ae790e2f988
|
||||
FirebaseMessaging: af49f8d7c0a3d2a017d9302c80946f45a7777dde
|
||||
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
||||
flutter_inappwebview_ios: b89ba3482b96fb25e00c967aae065701b66e9b99
|
||||
flutter_keyboard_visibility: 4625131e43015dbbe759d9b20daaf77e0e3f6619
|
||||
@@ -376,14 +379,14 @@ SPEC CHECKSUMS:
|
||||
flutter_secure_storage: 1ed9476fba7e7a782b22888f956cce43e2c62f13
|
||||
flutter_timezone: 7c838e17ffd4645d261e87037e5bebf6d38fe544
|
||||
flutter_udid: f7c3884e6ec2951efe4f9de082257fc77c4d15e9
|
||||
flutter_webrtc: fd0d3bdef8766a0736dbbe2e5b7e85f1f3c52117
|
||||
flutter_webrtc: 6f7da106613d52ade777d5b4875a43f48c28b457
|
||||
gal: baecd024ebfd13c441269ca7404792a7152fde89
|
||||
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
|
||||
GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
|
||||
image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a
|
||||
irondash_engine_context: 8e58ca8e0212ee9d1c7dc6a42121849986c88486
|
||||
Kingfisher: ff0d31a1f07bdff6a1ebb3ba08b8e6e567b6500c
|
||||
livekit_client: 3f79d79233a5bd13d5b541732624ef959d7c538e
|
||||
livekit_client: e3b79b99405428aac439b6b76a254cd9a11dbbfb
|
||||
local_auth_darwin: d2e8c53ef0c4f43c646462e3415432c4dab3ae19
|
||||
media_kit_libs_ios_video: 5a18affdb97d1f5d466dc79988b13eff6c5e2854
|
||||
media_kit_video: 1746e198cb697d1ffb734b1d05ec429d1fcd1474
|
||||
@@ -404,14 +407,14 @@ SPEC CHECKSUMS:
|
||||
sign_in_with_apple: c5dcc141574c8c54d5ac99dd2163c0c72ad22418
|
||||
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
|
||||
sqlite3: 83105acd294c9137c026e2da1931c30b4588ab81
|
||||
sqlite3_flutter_libs: ce0522d143cee6ef5e16587acfce8f476316e005
|
||||
sqlite3_flutter_libs: 616267f2fca40e9c6af8c5d82324e05667040b6e
|
||||
super_native_extensions: b763c02dc3a8fd078389f410bf15149179020cb4
|
||||
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
|
||||
url_launcher_ios: 694010445543906933d732453a59da0a173ae33d
|
||||
volume_controller: 3657a1f65bedb98fa41ff7dc5793537919f31b12
|
||||
wakelock_plus: e29112ab3ef0b318e58cfa5c32326458be66b556
|
||||
WebRTC-SDK: dff00a3892bc570b6014e046297782084071657e
|
||||
WebRTC-SDK: d20de357dcbf7c9696b124b39f3ff62125107e4b
|
||||
|
||||
PODFILE CHECKSUM: f6df17c2a0cbd7af89692fd3877231eaea40230f
|
||||
PODFILE CHECKSUM: c818292390b02fa379036ea099713a332bd7193f
|
||||
|
||||
COCOAPODS: 1.16.2
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
|
||||
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
|
||||
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
|
||||
73ACDFAD2E3D0E6100B63535 /* ReplayKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 73ACDFAC2E3D0E6100B63535 /* ReplayKit.framework */; };
|
||||
73ACDFC32E3D0E6100B63535 /* SolianBroadcastExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 73ACDFAB2E3D0E6100B63535 /* SolianBroadcastExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
73C305D82E0BE878009035B9 /* SolianShareExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 73C305CE2E0BE878009035B9 /* SolianShareExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
73CDD6812DEC00480059D95D /* SolianNotificationService.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 73CDD67A2DEC00480059D95D /* SolianNotificationService.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
73D4264B2DEB815D006C0AAE /* NotifyDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73D4264A2DEB815D006C0AAE /* NotifyDelegate.swift */; };
|
||||
@@ -32,6 +34,13 @@
|
||||
remoteGlobalIDString = 97C146ED1CF9000F007C117D;
|
||||
remoteInfo = Runner;
|
||||
};
|
||||
73ACDFC12E3D0E6100B63535 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 73ACDFAA2E3D0E6100B63535;
|
||||
remoteInfo = SolianBroadcastExtension;
|
||||
};
|
||||
73C305D62E0BE878009035B9 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
|
||||
@@ -55,6 +64,7 @@
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 13;
|
||||
files = (
|
||||
73ACDFC32E3D0E6100B63535 /* SolianBroadcastExtension.appex in Embed Foundation Extensions */,
|
||||
73C305D82E0BE878009035B9 /* SolianShareExtension.appex in Embed Foundation Extensions */,
|
||||
73CDD6812DEC00480059D95D /* SolianNotificationService.appex in Embed Foundation Extensions */,
|
||||
);
|
||||
@@ -91,6 +101,9 @@
|
||||
3A1C47BD29CC6AC2587D4DBE /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
|
||||
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
|
||||
737E920B2DB6A9FF00BE9CDB /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = "<group>"; };
|
||||
73ACDFAB2E3D0E6100B63535 /* SolianBroadcastExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = SolianBroadcastExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
73ACDFAC2E3D0E6100B63535 /* ReplayKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ReplayKit.framework; path = System/Library/Frameworks/ReplayKit.framework; sourceTree = SDKROOT; };
|
||||
73ACDFB82E3D0E6100B63535 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
|
||||
73C305CE2E0BE878009035B9 /* SolianShareExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = SolianShareExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
73CDD67A2DEC00480059D95D /* SolianNotificationService.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = SolianNotificationService.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
73D4264A2DEB815D006C0AAE /* NotifyDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotifyDelegate.swift; sourceTree = "<group>"; };
|
||||
@@ -117,6 +130,13 @@
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
||||
73ACDFCA2E3D0E6100B63535 /* Exceptions for "SolianBroadcastExtension" folder in "SolianBroadcastExtension" target */ = {
|
||||
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
|
||||
membershipExceptions = (
|
||||
Info.plist,
|
||||
);
|
||||
target = 73ACDFAA2E3D0E6100B63535 /* SolianBroadcastExtension */;
|
||||
};
|
||||
73C305DC2E0BE878009035B9 /* Exceptions for "SolianShareExtension" folder in "SolianShareExtension" target */ = {
|
||||
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
|
||||
membershipExceptions = (
|
||||
@@ -150,6 +170,14 @@
|
||||
path = Services;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
73ACDFAE2E3D0E6100B63535 /* SolianBroadcastExtension */ = {
|
||||
isa = PBXFileSystemSynchronizedRootGroup;
|
||||
exceptions = (
|
||||
73ACDFCA2E3D0E6100B63535 /* Exceptions for "SolianBroadcastExtension" folder in "SolianBroadcastExtension" target */,
|
||||
);
|
||||
path = SolianBroadcastExtension;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
73C305CF2E0BE878009035B9 /* SolianShareExtension */ = {
|
||||
isa = PBXFileSystemSynchronizedRootGroup;
|
||||
exceptions = (
|
||||
@@ -177,6 +205,14 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
73ACDFA82E3D0E6100B63535 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
73ACDFAD2E3D0E6100B63535 /* ReplayKit.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
73C305CB2E0BE878009035B9 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@@ -220,6 +256,8 @@
|
||||
AA0CA8A3E15DEE023BB27438 /* Pods_NotificationService.framework */,
|
||||
39FE4CC6223F0D3C0E1FFD04 /* Pods_SolianNotificationService.framework */,
|
||||
7B40764A2C4CC0E7DC70A0D3 /* Pods_SolianShareExtension.framework */,
|
||||
73ACDFAC2E3D0E6100B63535 /* ReplayKit.framework */,
|
||||
73ACDFB82E3D0E6100B63535 /* UIKit.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
@@ -264,6 +302,7 @@
|
||||
97C146F01CF9000F007C117D /* Runner */,
|
||||
73CDD67B2DEC00480059D95D /* SolianNotificationService */,
|
||||
73C305CF2E0BE878009035B9 /* SolianShareExtension */,
|
||||
73ACDFAE2E3D0E6100B63535 /* SolianBroadcastExtension */,
|
||||
97C146EF1CF9000F007C117D /* Products */,
|
||||
331C8082294A63A400263BE5 /* RunnerTests */,
|
||||
91E124CE95BCB4DCD890160D /* Pods */,
|
||||
@@ -279,6 +318,7 @@
|
||||
331C8081294A63A400263BE5 /* RunnerTests.xctest */,
|
||||
73CDD67A2DEC00480059D95D /* SolianNotificationService.appex */,
|
||||
73C305CE2E0BE878009035B9 /* SolianShareExtension.appex */,
|
||||
73ACDFAB2E3D0E6100B63535 /* SolianBroadcastExtension.appex */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
@@ -323,6 +363,26 @@
|
||||
productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */;
|
||||
productType = "com.apple.product-type.bundle.unit-test";
|
||||
};
|
||||
73ACDFAA2E3D0E6100B63535 /* SolianBroadcastExtension */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 73ACDFCB2E3D0E6100B63535 /* Build configuration list for PBXNativeTarget "SolianBroadcastExtension" */;
|
||||
buildPhases = (
|
||||
73ACDFA72E3D0E6100B63535 /* Sources */,
|
||||
73ACDFA82E3D0E6100B63535 /* Frameworks */,
|
||||
73ACDFA92E3D0E6100B63535 /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
fileSystemSynchronizedGroups = (
|
||||
73ACDFAE2E3D0E6100B63535 /* SolianBroadcastExtension */,
|
||||
);
|
||||
name = SolianBroadcastExtension;
|
||||
productName = SolianBroadcastExtension;
|
||||
productReference = 73ACDFAB2E3D0E6100B63535 /* SolianBroadcastExtension.appex */;
|
||||
productType = "com.apple.product-type.app-extension";
|
||||
};
|
||||
73C305CD2E0BE878009035B9 /* SolianShareExtension */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 73C305DD2E0BE878009035B9 /* Build configuration list for PBXNativeTarget "SolianShareExtension" */;
|
||||
@@ -385,6 +445,7 @@
|
||||
dependencies = (
|
||||
73CDD6802DEC00480059D95D /* PBXTargetDependency */,
|
||||
73C305D72E0BE878009035B9 /* PBXTargetDependency */,
|
||||
73ACDFC22E3D0E6100B63535 /* PBXTargetDependency */,
|
||||
);
|
||||
fileSystemSynchronizedGroups = (
|
||||
73268D272DEB012A0076E970 /* Services */,
|
||||
@@ -409,6 +470,9 @@
|
||||
CreatedOnToolsVersion = 14.0;
|
||||
TestTargetID = 97C146ED1CF9000F007C117D;
|
||||
};
|
||||
73ACDFAA2E3D0E6100B63535 = {
|
||||
CreatedOnToolsVersion = 16.4;
|
||||
};
|
||||
73C305CD2E0BE878009035B9 = {
|
||||
CreatedOnToolsVersion = 16.4;
|
||||
};
|
||||
@@ -438,6 +502,7 @@
|
||||
331C8080294A63A400263BE5 /* RunnerTests */,
|
||||
73CDD6792DEC00480059D95D /* SolianNotificationService */,
|
||||
73C305CD2E0BE878009035B9 /* SolianShareExtension */,
|
||||
73ACDFAA2E3D0E6100B63535 /* SolianBroadcastExtension */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
@@ -450,6 +515,13 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
73ACDFA92E3D0E6100B63535 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
73C305CC2E0BE878009035B9 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@@ -643,6 +715,13 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
73ACDFA72E3D0E6100B63535 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
73C305CA2E0BE878009035B9 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@@ -675,6 +754,11 @@
|
||||
target = 97C146ED1CF9000F007C117D /* Runner */;
|
||||
targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */;
|
||||
};
|
||||
73ACDFC22E3D0E6100B63535 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 73ACDFAA2E3D0E6100B63535 /* SolianBroadcastExtension */;
|
||||
targetProxy = 73ACDFC12E3D0E6100B63535 /* PBXContainerItemProxy */;
|
||||
};
|
||||
73C305D72E0BE878009035B9 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 73C305CD2E0BE878009035B9 /* SolianShareExtension */;
|
||||
@@ -773,7 +857,7 @@
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = Solian;
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
@@ -836,6 +920,123 @@
|
||||
};
|
||||
name = Profile;
|
||||
};
|
||||
73ACDFC42E3D0E6100B63535 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_ENTITLEMENTS = SolianBroadcastExtension/SolianBroadcastExtension.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = W7HPZ53V6B;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = SolianBroadcastExtension/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = SolianBroadcastExtension;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = dev.solsynth.solian.SolianBroadcastExtension;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
73ACDFC52E3D0E6100B63535 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_ENTITLEMENTS = SolianBroadcastExtension/SolianBroadcastExtension.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = W7HPZ53V6B;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = SolianBroadcastExtension/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = SolianBroadcastExtension;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = dev.solsynth.solian.SolianBroadcastExtension;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
73ACDFC62E3D0E6100B63535 /* Profile */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_ENTITLEMENTS = SolianBroadcastExtension/SolianBroadcastExtension.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = W7HPZ53V6B;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = SolianBroadcastExtension/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = SolianBroadcastExtension;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = dev.solsynth.solian.SolianBroadcastExtension;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Profile;
|
||||
};
|
||||
73C305D92E0BE878009035B9 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 17FAB080A9C53193ABD9C15B /* Pods-SolianShareExtension.debug.xcconfig */;
|
||||
@@ -1204,7 +1405,7 @@
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = Solian;
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
@@ -1232,7 +1433,7 @@
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = Solian;
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
@@ -1258,6 +1459,16 @@
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
73ACDFCB2E3D0E6100B63535 /* Build configuration list for PBXNativeTarget "SolianBroadcastExtension" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
73ACDFC42E3D0E6100B63535 /* Debug */,
|
||||
73ACDFC52E3D0E6100B63535 /* Release */,
|
||||
73ACDFC62E3D0E6100B63535 /* Profile */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
73C305DD2E0BE878009035B9 /* Build configuration list for PBXNativeTarget "SolianShareExtension" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
|
||||
@@ -2,6 +2,12 @@
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CLIENT_ID</key>
|
||||
<string>961776991058-stt7et4qvn3cpscl4r61gl1hnlatqkig.apps.googleusercontent.com</string>
|
||||
<key>REVERSED_CLIENT_ID</key>
|
||||
<string>com.googleusercontent.apps.961776991058-stt7et4qvn3cpscl4r61gl1hnlatqkig</string>
|
||||
<key>ANDROID_CLIENT_ID</key>
|
||||
<string>961776991058-r4iv9qoio57ul7utbfpgfrda2etvtch8.apps.googleusercontent.com</string>
|
||||
<key>API_KEY</key>
|
||||
<string>AIzaSyCzQIyiYKoYHTpGXhN-IjgMML8z797WVD8</string>
|
||||
<key>GCM_SENDER_ID</key>
|
||||
|
||||
37
ios/SolianBroadcastExtension/Atomic.swift
Normal file
37
ios/SolianBroadcastExtension/Atomic.swift
Normal file
@@ -0,0 +1,37 @@
|
||||
//
|
||||
// Atomic.swift
|
||||
// Broadcast Extension
|
||||
//
|
||||
// Created by Maksym Shcheglov.
|
||||
// https://www.onswiftwings.com/posts/atomic-property-wrapper/
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
@propertyWrapper
|
||||
struct Atomic<Value> {
|
||||
|
||||
private var value: Value
|
||||
private let lock = NSLock()
|
||||
|
||||
init(wrappedValue value: Value) {
|
||||
self.value = value
|
||||
}
|
||||
|
||||
var wrappedValue: Value {
|
||||
get { load() }
|
||||
set { store(newValue: newValue) }
|
||||
}
|
||||
|
||||
func load() -> Value {
|
||||
lock.lock()
|
||||
defer { lock.unlock() }
|
||||
return value
|
||||
}
|
||||
|
||||
mutating func store(newValue: Value) {
|
||||
lock.lock()
|
||||
defer { lock.unlock() }
|
||||
value = newValue
|
||||
}
|
||||
}
|
||||
29
ios/SolianBroadcastExtension/DarwinNotification.swift
Normal file
29
ios/SolianBroadcastExtension/DarwinNotification.swift
Normal file
@@ -0,0 +1,29 @@
|
||||
//
|
||||
// DarwinNotificationCenter.swift
|
||||
// Broadcast Extension
|
||||
//
|
||||
// Created by Alex-Dan Bumbu on 23/03/2021.
|
||||
// Copyright © 2021 8x8, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
enum DarwinNotification: String {
|
||||
case broadcastStarted = "iOS_BroadcastStarted"
|
||||
case broadcastStopped = "iOS_BroadcastStopped"
|
||||
}
|
||||
|
||||
class DarwinNotificationCenter {
|
||||
|
||||
static let shared = DarwinNotificationCenter()
|
||||
|
||||
private let notificationCenter: CFNotificationCenter
|
||||
|
||||
init() {
|
||||
notificationCenter = CFNotificationCenterGetDarwinNotifyCenter()
|
||||
}
|
||||
|
||||
func postNotification(_ name: DarwinNotification) {
|
||||
CFNotificationCenterPostNotification(notificationCenter, CFNotificationName(rawValue: name.rawValue as CFString), nil, nil, true)
|
||||
}
|
||||
}
|
||||
15
ios/SolianBroadcastExtension/Info.plist
Normal file
15
ios/SolianBroadcastExtension/Info.plist
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionPointIdentifier</key>
|
||||
<string>com.apple.broadcast-services-upload</string>
|
||||
<key>NSExtensionPrincipalClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).SampleHandler</string>
|
||||
<key>RPBroadcastProcessMode</key>
|
||||
<string>RPBroadcastProcessModeSampleBuffer</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
103
ios/SolianBroadcastExtension/SampleHandler.swift
Normal file
103
ios/SolianBroadcastExtension/SampleHandler.swift
Normal file
@@ -0,0 +1,103 @@
|
||||
//
|
||||
// SampleHandler.swift
|
||||
// Broadcast Extension
|
||||
//
|
||||
// Created by Alex-Dan Bumbu on 04.06.2021.
|
||||
//
|
||||
|
||||
import ReplayKit
|
||||
import OSLog
|
||||
|
||||
let broadcastLogger = OSLog(subsystem: "dev.solsynth.solian", category: "Broadcast")
|
||||
private enum Constants {
|
||||
// the App Group ID value that the app and the broadcast extension targets are setup with. It differs for each app.
|
||||
static let appGroupIdentifier = "group.solsynth.solian"
|
||||
}
|
||||
|
||||
class SampleHandler: RPBroadcastSampleHandler {
|
||||
|
||||
private var clientConnection: SocketConnection?
|
||||
private var uploader: SampleUploader?
|
||||
|
||||
private var frameCount: Int = 0
|
||||
|
||||
var socketFilePath: String {
|
||||
let sharedContainer = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: Constants.appGroupIdentifier)
|
||||
return sharedContainer?.appendingPathComponent("rtc_SSFD").path ?? ""
|
||||
}
|
||||
|
||||
override init() {
|
||||
super.init()
|
||||
if let connection = SocketConnection(filePath: socketFilePath) {
|
||||
clientConnection = connection
|
||||
setupConnection()
|
||||
|
||||
uploader = SampleUploader(connection: connection)
|
||||
}
|
||||
os_log(.debug, log: broadcastLogger, "%{public}s", socketFilePath)
|
||||
}
|
||||
|
||||
override func broadcastStarted(withSetupInfo setupInfo: [String: NSObject]?) {
|
||||
// User has requested to start the broadcast. Setup info from the UI extension can be supplied but optional.
|
||||
frameCount = 0
|
||||
|
||||
DarwinNotificationCenter.shared.postNotification(.broadcastStarted)
|
||||
openConnection()
|
||||
}
|
||||
|
||||
override func broadcastPaused() {
|
||||
// User has requested to pause the broadcast. Samples will stop being delivered.
|
||||
}
|
||||
|
||||
override func broadcastResumed() {
|
||||
// User has requested to resume the broadcast. Samples delivery will resume.
|
||||
}
|
||||
|
||||
override func broadcastFinished() {
|
||||
// User has requested to finish the broadcast.
|
||||
DarwinNotificationCenter.shared.postNotification(.broadcastStopped)
|
||||
clientConnection?.close()
|
||||
}
|
||||
|
||||
override func processSampleBuffer(_ sampleBuffer: CMSampleBuffer, with sampleBufferType: RPSampleBufferType) {
|
||||
switch sampleBufferType {
|
||||
case RPSampleBufferType.video:
|
||||
uploader?.send(sample: sampleBuffer)
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension SampleHandler {
|
||||
|
||||
func setupConnection() {
|
||||
clientConnection?.didClose = { [weak self] error in
|
||||
os_log(.debug, log: broadcastLogger, "client connection did close \(String(describing: error))")
|
||||
|
||||
if let error = error {
|
||||
self?.finishBroadcastWithError(error)
|
||||
} else {
|
||||
// the displayed failure message is more user friendly when using NSError instead of Error
|
||||
let JMScreenSharingStopped = 10001
|
||||
let customError = NSError(domain: RPRecordingErrorDomain, code: JMScreenSharingStopped, userInfo: [NSLocalizedDescriptionKey: "Screen sharing stopped"])
|
||||
self?.finishBroadcastWithError(customError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func openConnection() {
|
||||
let queue = DispatchQueue(label: "broadcast.connectTimer")
|
||||
let timer = DispatchSource.makeTimerSource(queue: queue)
|
||||
timer.schedule(deadline: .now(), repeating: .milliseconds(100), leeway: .milliseconds(500))
|
||||
timer.setEventHandler { [weak self] in
|
||||
guard self?.clientConnection?.open() == true else {
|
||||
return
|
||||
}
|
||||
|
||||
timer.cancel()
|
||||
}
|
||||
|
||||
timer.resume()
|
||||
}
|
||||
}
|
||||
147
ios/SolianBroadcastExtension/SampleUploader.swift
Normal file
147
ios/SolianBroadcastExtension/SampleUploader.swift
Normal file
@@ -0,0 +1,147 @@
|
||||
//
|
||||
// SampleUploader.swift
|
||||
// Broadcast Extension
|
||||
//
|
||||
// Created by Alex-Dan Bumbu on 22/03/2021.
|
||||
// Copyright © 2021 8x8, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import ReplayKit
|
||||
import OSLog
|
||||
|
||||
private enum Constants {
|
||||
static let bufferMaxLength = 10240
|
||||
}
|
||||
|
||||
class SampleUploader {
|
||||
|
||||
private static var imageContext = CIContext(options: nil)
|
||||
|
||||
@Atomic private var isReady = false
|
||||
private var connection: SocketConnection
|
||||
|
||||
private var dataToSend: Data?
|
||||
private var byteIndex = 0
|
||||
|
||||
private let serialQueue: DispatchQueue
|
||||
|
||||
init(connection: SocketConnection) {
|
||||
self.connection = connection
|
||||
self.serialQueue = DispatchQueue(label: "org.jitsi.meet.broadcast.sampleUploader")
|
||||
|
||||
setupConnection()
|
||||
}
|
||||
|
||||
@discardableResult func send(sample buffer: CMSampleBuffer) -> Bool {
|
||||
guard isReady else {
|
||||
return false
|
||||
}
|
||||
|
||||
isReady = false
|
||||
|
||||
dataToSend = prepare(sample: buffer)
|
||||
byteIndex = 0
|
||||
|
||||
serialQueue.async { [weak self] in
|
||||
self?.sendDataChunk()
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
private extension SampleUploader {
|
||||
|
||||
func setupConnection() {
|
||||
connection.didOpen = { [weak self] in
|
||||
self?.isReady = true
|
||||
}
|
||||
connection.streamHasSpaceAvailable = { [weak self] in
|
||||
self?.serialQueue.async {
|
||||
if let success = self?.sendDataChunk() {
|
||||
self?.isReady = !success
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@discardableResult func sendDataChunk() -> Bool {
|
||||
guard let dataToSend = dataToSend else {
|
||||
return false
|
||||
}
|
||||
|
||||
var bytesLeft = dataToSend.count - byteIndex
|
||||
var length = bytesLeft > Constants.bufferMaxLength ? Constants.bufferMaxLength : bytesLeft
|
||||
|
||||
length = dataToSend[byteIndex..<(byteIndex + length)].withUnsafeBytes {
|
||||
guard let ptr = $0.bindMemory(to: UInt8.self).baseAddress else {
|
||||
return 0
|
||||
}
|
||||
|
||||
return connection.writeToStream(buffer: ptr, maxLength: length)
|
||||
}
|
||||
|
||||
if length > 0 {
|
||||
byteIndex += length
|
||||
bytesLeft -= length
|
||||
|
||||
if bytesLeft == 0 {
|
||||
self.dataToSend = nil
|
||||
byteIndex = 0
|
||||
}
|
||||
} else {
|
||||
os_log(.debug, log: broadcastLogger, "writeBufferToStream failure")
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func prepare(sample buffer: CMSampleBuffer) -> Data? {
|
||||
guard let imageBuffer = CMSampleBufferGetImageBuffer(buffer) else {
|
||||
os_log(.debug, log: broadcastLogger, "image buffer not available")
|
||||
return nil
|
||||
}
|
||||
|
||||
CVPixelBufferLockBaseAddress(imageBuffer, .readOnly)
|
||||
|
||||
let scaleFactor = 1.0
|
||||
let width = CVPixelBufferGetWidth(imageBuffer)/Int(scaleFactor)
|
||||
let height = CVPixelBufferGetHeight(imageBuffer)/Int(scaleFactor)
|
||||
let orientation = CMGetAttachment(buffer, key: RPVideoSampleOrientationKey as CFString, attachmentModeOut: nil)?.uintValue ?? 0
|
||||
|
||||
let scaleTransform = CGAffineTransform(scaleX: CGFloat(1.0/scaleFactor), y: CGFloat(1.0/scaleFactor))
|
||||
let bufferData = self.jpegData(from: imageBuffer, scale: scaleTransform)
|
||||
|
||||
CVPixelBufferUnlockBaseAddress(imageBuffer, .readOnly)
|
||||
|
||||
guard let messageData = bufferData else {
|
||||
os_log(.debug, log: broadcastLogger, "corrupted image buffer")
|
||||
return nil
|
||||
}
|
||||
|
||||
let httpResponse = CFHTTPMessageCreateResponse(nil, 200, nil, kCFHTTPVersion1_1).takeRetainedValue()
|
||||
CFHTTPMessageSetHeaderFieldValue(httpResponse, "Content-Length" as CFString, String(messageData.count) as CFString)
|
||||
CFHTTPMessageSetHeaderFieldValue(httpResponse, "Buffer-Width" as CFString, String(width) as CFString)
|
||||
CFHTTPMessageSetHeaderFieldValue(httpResponse, "Buffer-Height" as CFString, String(height) as CFString)
|
||||
CFHTTPMessageSetHeaderFieldValue(httpResponse, "Buffer-Orientation" as CFString, String(orientation) as CFString)
|
||||
|
||||
CFHTTPMessageSetBody(httpResponse, messageData as CFData)
|
||||
|
||||
let serializedMessage = CFHTTPMessageCopySerializedMessage(httpResponse)?.takeRetainedValue() as Data?
|
||||
|
||||
return serializedMessage
|
||||
}
|
||||
|
||||
func jpegData(from buffer: CVPixelBuffer, scale scaleTransform: CGAffineTransform) -> Data? {
|
||||
let image = CIImage(cvPixelBuffer: buffer).transformed(by: scaleTransform)
|
||||
|
||||
guard let colorSpace = image.colorSpace else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let options: [CIImageRepresentationOption: Float] = [kCGImageDestinationLossyCompressionQuality as CIImageRepresentationOption: 1.0]
|
||||
|
||||
return SampleUploader.imageContext.jpegRepresentation(of: image, colorSpace: colorSpace, options: options)
|
||||
}
|
||||
}
|
||||
199
ios/SolianBroadcastExtension/SocketConnection.swift
Normal file
199
ios/SolianBroadcastExtension/SocketConnection.swift
Normal file
@@ -0,0 +1,199 @@
|
||||
//
|
||||
// SocketConnection.swift
|
||||
// Broadcast Extension
|
||||
//
|
||||
// Created by Alex-Dan Bumbu on 22/03/2021.
|
||||
// Copyright © 2021 Atlassian Inc. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import OSLog
|
||||
|
||||
class SocketConnection: NSObject {
|
||||
var didOpen: (() -> Void)?
|
||||
var didClose: ((Error?) -> Void)?
|
||||
var streamHasSpaceAvailable: (() -> Void)?
|
||||
|
||||
private let filePath: String
|
||||
private var socketHandle: Int32 = -1
|
||||
private var address: sockaddr_un?
|
||||
|
||||
private var inputStream: InputStream?
|
||||
private var outputStream: OutputStream?
|
||||
|
||||
private var networkQueue: DispatchQueue?
|
||||
private var shouldKeepRunning = false
|
||||
|
||||
init?(filePath path: String) {
|
||||
filePath = path
|
||||
socketHandle = Darwin.socket(AF_UNIX, SOCK_STREAM, 0)
|
||||
|
||||
guard socketHandle != -1 else {
|
||||
os_log(.debug, log: broadcastLogger, "failure: create socket")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func open() -> Bool {
|
||||
os_log(.debug, log: broadcastLogger, "open socket connection")
|
||||
|
||||
guard FileManager.default.fileExists(atPath: filePath) else {
|
||||
os_log(.debug, log: broadcastLogger, "failure: socket file missing")
|
||||
return false
|
||||
}
|
||||
|
||||
guard setupAddress() == true else {
|
||||
return false
|
||||
}
|
||||
|
||||
guard connectSocket() == true else {
|
||||
return false
|
||||
}
|
||||
|
||||
setupStreams()
|
||||
|
||||
inputStream?.open()
|
||||
outputStream?.open()
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func close() {
|
||||
unscheduleStreams()
|
||||
|
||||
inputStream?.delegate = nil
|
||||
outputStream?.delegate = nil
|
||||
|
||||
inputStream?.close()
|
||||
outputStream?.close()
|
||||
|
||||
inputStream = nil
|
||||
outputStream = nil
|
||||
}
|
||||
|
||||
func writeToStream(buffer: UnsafePointer<UInt8>, maxLength length: Int) -> Int {
|
||||
outputStream?.write(buffer, maxLength: length) ?? 0
|
||||
}
|
||||
}
|
||||
|
||||
extension SocketConnection: StreamDelegate {
|
||||
|
||||
func stream(_ aStream: Stream, handle eventCode: Stream.Event) {
|
||||
switch eventCode {
|
||||
case .openCompleted:
|
||||
os_log(.debug, log: broadcastLogger, "client stream open completed")
|
||||
if aStream == outputStream {
|
||||
didOpen?()
|
||||
}
|
||||
case .hasBytesAvailable:
|
||||
if aStream == inputStream {
|
||||
var buffer: UInt8 = 0
|
||||
let numberOfBytesRead = inputStream?.read(&buffer, maxLength: 1)
|
||||
if numberOfBytesRead == 0 && aStream.streamStatus == .atEnd {
|
||||
os_log(.debug, log: broadcastLogger, "server socket closed")
|
||||
close()
|
||||
notifyDidClose(error: nil)
|
||||
}
|
||||
}
|
||||
case .hasSpaceAvailable:
|
||||
if aStream == outputStream {
|
||||
streamHasSpaceAvailable?()
|
||||
}
|
||||
case .errorOccurred:
|
||||
os_log(.debug, log: broadcastLogger, "client stream error occured: \(String(describing: aStream.streamError))")
|
||||
close()
|
||||
notifyDidClose(error: aStream.streamError)
|
||||
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension SocketConnection {
|
||||
|
||||
func setupAddress() -> Bool {
|
||||
var addr = sockaddr_un()
|
||||
guard filePath.count < MemoryLayout.size(ofValue: addr.sun_path) else {
|
||||
os_log(.debug, log: broadcastLogger, "failure: fd path is too long")
|
||||
return false
|
||||
}
|
||||
|
||||
_ = withUnsafeMutablePointer(to: &addr.sun_path.0) { ptr in
|
||||
filePath.withCString {
|
||||
strncpy(ptr, $0, filePath.count)
|
||||
}
|
||||
}
|
||||
|
||||
address = addr
|
||||
return true
|
||||
}
|
||||
|
||||
func connectSocket() -> Bool {
|
||||
guard var addr = address else {
|
||||
return false
|
||||
}
|
||||
|
||||
let status = withUnsafePointer(to: &addr) { ptr in
|
||||
ptr.withMemoryRebound(to: sockaddr.self, capacity: 1) {
|
||||
Darwin.connect(socketHandle, $0, socklen_t(MemoryLayout<sockaddr_un>.size))
|
||||
}
|
||||
}
|
||||
|
||||
guard status == noErr else {
|
||||
os_log(.debug, log: broadcastLogger, "failure: \(status)")
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func setupStreams() {
|
||||
var readStream: Unmanaged<CFReadStream>?
|
||||
var writeStream: Unmanaged<CFWriteStream>?
|
||||
|
||||
CFStreamCreatePairWithSocket(kCFAllocatorDefault, socketHandle, &readStream, &writeStream)
|
||||
|
||||
inputStream = readStream?.takeRetainedValue()
|
||||
inputStream?.delegate = self
|
||||
inputStream?.setProperty(kCFBooleanTrue, forKey: Stream.PropertyKey(kCFStreamPropertyShouldCloseNativeSocket as String))
|
||||
|
||||
outputStream = writeStream?.takeRetainedValue()
|
||||
outputStream?.delegate = self
|
||||
outputStream?.setProperty(kCFBooleanTrue, forKey: Stream.PropertyKey(kCFStreamPropertyShouldCloseNativeSocket as String))
|
||||
|
||||
scheduleStreams()
|
||||
}
|
||||
|
||||
func scheduleStreams() {
|
||||
shouldKeepRunning = true
|
||||
|
||||
networkQueue = DispatchQueue.global(qos: .userInitiated)
|
||||
networkQueue?.async { [weak self] in
|
||||
self?.inputStream?.schedule(in: .current, forMode: .common)
|
||||
self?.outputStream?.schedule(in: .current, forMode: .common)
|
||||
RunLoop.current.run()
|
||||
|
||||
var isRunning = false
|
||||
|
||||
repeat {
|
||||
isRunning = self?.shouldKeepRunning ?? false && RunLoop.current.run(mode: .default, before: .distantFuture)
|
||||
} while (isRunning)
|
||||
}
|
||||
}
|
||||
|
||||
func unscheduleStreams() {
|
||||
networkQueue?.sync { [weak self] in
|
||||
self?.inputStream?.remove(from: .current, forMode: .common)
|
||||
self?.outputStream?.remove(from: .current, forMode: .common)
|
||||
}
|
||||
|
||||
shouldKeepRunning = false
|
||||
}
|
||||
|
||||
func notifyDidClose(error: Error?) {
|
||||
if didClose != nil {
|
||||
didClose?(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>group.solsynth.solian</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -49,8 +49,7 @@ class AppDatabase extends _$AppDatabase {
|
||||
}
|
||||
|
||||
Future<int> updateMessage(ChatMessagesCompanion message) {
|
||||
return (update(chatMessages)
|
||||
..where((m) => m.id.equals(message.id.value))).write(message);
|
||||
return into(chatMessages).insert(message, mode: InsertMode.insertOrReplace);
|
||||
}
|
||||
|
||||
Future<int> updateMessageStatus(String id, MessageStatus status) {
|
||||
|
||||
@@ -29,10 +29,7 @@ class DefaultFirebaseOptions {
|
||||
case TargetPlatform.windows:
|
||||
return windows;
|
||||
case TargetPlatform.linux:
|
||||
throw UnsupportedError(
|
||||
'DefaultFirebaseOptions have not been configured for linux - '
|
||||
'you can reconfigure this by running the FlutterFire CLI again.',
|
||||
);
|
||||
return windows;
|
||||
default:
|
||||
throw UnsupportedError(
|
||||
'DefaultFirebaseOptions are not supported for this platform.',
|
||||
@@ -41,13 +38,13 @@ class DefaultFirebaseOptions {
|
||||
}
|
||||
|
||||
static const FirebaseOptions web = FirebaseOptions(
|
||||
apiKey: 'AIzaSyBKfIQpTouj5rXnlzkEieSlbAzepm4mgJE',
|
||||
appId: '1:961776991058:web:b91d12f2892a5609f4188b',
|
||||
apiKey: 'AIzaSyCfgOdlcr7h8x8j0WKx_S2wXnGkOopq320',
|
||||
appId: '1:961776991058:web:3a912c0eb14028e5f4188b',
|
||||
messagingSenderId: '961776991058',
|
||||
projectId: 'solian-0x001',
|
||||
authDomain: 'solian-0x001.firebaseapp.com',
|
||||
storageBucket: 'solian-0x001.firebasestorage.app',
|
||||
measurementId: 'G-XY3HHKG0PE',
|
||||
measurementId: 'G-JD1YEG9D6F',
|
||||
);
|
||||
|
||||
static const FirebaseOptions android = FirebaseOptions(
|
||||
@@ -64,6 +61,10 @@ class DefaultFirebaseOptions {
|
||||
messagingSenderId: '961776991058',
|
||||
projectId: 'solian-0x001',
|
||||
storageBucket: 'solian-0x001.firebasestorage.app',
|
||||
androidClientId:
|
||||
'961776991058-r4iv9qoio57ul7utbfpgfrda2etvtch8.apps.googleusercontent.com',
|
||||
iosClientId:
|
||||
'961776991058-stt7et4qvn3cpscl4r61gl1hnlatqkig.apps.googleusercontent.com',
|
||||
iosBundleId: 'dev.solsynth.solian',
|
||||
);
|
||||
|
||||
@@ -73,6 +74,10 @@ class DefaultFirebaseOptions {
|
||||
messagingSenderId: '961776991058',
|
||||
projectId: 'solian-0x001',
|
||||
storageBucket: 'solian-0x001.firebasestorage.app',
|
||||
androidClientId:
|
||||
'961776991058-r4iv9qoio57ul7utbfpgfrda2etvtch8.apps.googleusercontent.com',
|
||||
iosClientId:
|
||||
'961776991058-stt7et4qvn3cpscl4r61gl1hnlatqkig.apps.googleusercontent.com',
|
||||
iosBundleId: 'dev.solsynth.solian',
|
||||
);
|
||||
|
||||
@@ -85,5 +90,4 @@ class DefaultFirebaseOptions {
|
||||
storageBucket: 'solian-0x001.firebasestorage.app',
|
||||
measurementId: 'G-JD1YEG9D6F',
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ import 'package:bitsdojo_window/bitsdojo_window.dart';
|
||||
import 'package:island/pods/userinfo.dart';
|
||||
import 'package:island/pods/websocket.dart';
|
||||
import 'package:island/route.dart';
|
||||
|
||||
import 'package:island/services/notify.dart';
|
||||
import 'package:island/services/timezone.dart';
|
||||
import 'package:island/widgets/alert.dart';
|
||||
@@ -30,6 +29,8 @@ import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:image_picker_platform_interface/image_picker_platform_interface.dart';
|
||||
import 'package:flutter_native_splash/flutter_native_splash.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
import 'package:flutter_langdetect/flutter_langdetect.dart' as langdetect;
|
||||
import 'package:island/services/update_service.dart';
|
||||
|
||||
@pragma('vm:entry-point')
|
||||
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
|
||||
@@ -51,11 +52,18 @@ void main() async {
|
||||
}
|
||||
|
||||
try {
|
||||
await langdetect.initLangDetect();
|
||||
await EasyLocalization.ensureInitialized();
|
||||
await Firebase.initializeApp(
|
||||
options: DefaultFirebaseOptions.currentPlatform,
|
||||
);
|
||||
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
|
||||
|
||||
if (kIsWeb || !Platform.isLinux) {
|
||||
await Firebase.initializeApp(
|
||||
options: DefaultFirebaseOptions.currentPlatform,
|
||||
);
|
||||
FirebaseMessaging.onBackgroundMessage(
|
||||
_firebaseMessagingBackgroundHandler,
|
||||
);
|
||||
}
|
||||
|
||||
log("[SplashScreen] Firebase is ready!");
|
||||
} catch (err) {
|
||||
showErrorAlert(err);
|
||||
@@ -136,6 +144,15 @@ void main() async {
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Schedule update check shortly after startup, when a context is available.
|
||||
// Uses the global overlay key to obtain a BuildContext safely.
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
final ctx = globalOverlay.currentContext;
|
||||
if (ctx != null) {
|
||||
UpdateService().checkForUpdates(ctx);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Router will be provided through Riverpod
|
||||
|
||||
@@ -162,8 +162,6 @@ sealed class CallParticipant with _$CallParticipant {
|
||||
required String identity,
|
||||
required String name,
|
||||
required DateTime joinedAt,
|
||||
required String? accountId,
|
||||
required SnChatMember? profile,
|
||||
}) = _CallParticipant;
|
||||
|
||||
factory CallParticipant.fromJson(Map<String, dynamic> json) =>
|
||||
|
||||
@@ -2498,7 +2498,7 @@ as List<CallParticipant>,
|
||||
/// @nodoc
|
||||
mixin _$CallParticipant {
|
||||
|
||||
String get identity; String get name; DateTime get joinedAt; String? get accountId; SnChatMember? get profile;
|
||||
String get identity; String get name; DateTime get joinedAt;
|
||||
/// Create a copy of CallParticipant
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@@ -2511,16 +2511,16 @@ $CallParticipantCopyWith<CallParticipant> get copyWith => _$CallParticipantCopyW
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is CallParticipant&&(identical(other.identity, identity) || other.identity == identity)&&(identical(other.name, name) || other.name == name)&&(identical(other.joinedAt, joinedAt) || other.joinedAt == joinedAt)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.profile, profile) || other.profile == profile));
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is CallParticipant&&(identical(other.identity, identity) || other.identity == identity)&&(identical(other.name, name) || other.name == name)&&(identical(other.joinedAt, joinedAt) || other.joinedAt == joinedAt));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,identity,name,joinedAt,accountId,profile);
|
||||
int get hashCode => Object.hash(runtimeType,identity,name,joinedAt);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'CallParticipant(identity: $identity, name: $name, joinedAt: $joinedAt, accountId: $accountId, profile: $profile)';
|
||||
return 'CallParticipant(identity: $identity, name: $name, joinedAt: $joinedAt)';
|
||||
}
|
||||
|
||||
|
||||
@@ -2531,11 +2531,11 @@ abstract mixin class $CallParticipantCopyWith<$Res> {
|
||||
factory $CallParticipantCopyWith(CallParticipant value, $Res Function(CallParticipant) _then) = _$CallParticipantCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
String identity, String name, DateTime joinedAt, String? accountId, SnChatMember? profile
|
||||
String identity, String name, DateTime joinedAt
|
||||
});
|
||||
|
||||
|
||||
$SnChatMemberCopyWith<$Res>? get profile;
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
@@ -2548,29 +2548,15 @@ class _$CallParticipantCopyWithImpl<$Res>
|
||||
|
||||
/// Create a copy of CallParticipant
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? identity = null,Object? name = null,Object? joinedAt = null,Object? accountId = freezed,Object? profile = freezed,}) {
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? identity = null,Object? name = null,Object? joinedAt = null,}) {
|
||||
return _then(_self.copyWith(
|
||||
identity: null == identity ? _self.identity : identity // ignore: cast_nullable_to_non_nullable
|
||||
as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
||||
as String,joinedAt: null == joinedAt ? _self.joinedAt : joinedAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime,accountId: freezed == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable
|
||||
as String?,profile: freezed == profile ? _self.profile : profile // ignore: cast_nullable_to_non_nullable
|
||||
as SnChatMember?,
|
||||
as DateTime,
|
||||
));
|
||||
}
|
||||
/// Create a copy of CallParticipant
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$SnChatMemberCopyWith<$Res>? get profile {
|
||||
if (_self.profile == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $SnChatMemberCopyWith<$Res>(_self.profile!, (value) {
|
||||
return _then(_self.copyWith(profile: value));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2649,10 +2635,10 @@ return $default(_that);case _:
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String identity, String name, DateTime joinedAt, String? accountId, SnChatMember? profile)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String identity, String name, DateTime joinedAt)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _CallParticipant() when $default != null:
|
||||
return $default(_that.identity,_that.name,_that.joinedAt,_that.accountId,_that.profile);case _:
|
||||
return $default(_that.identity,_that.name,_that.joinedAt);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
@@ -2670,10 +2656,10 @@ return $default(_that.identity,_that.name,_that.joinedAt,_that.accountId,_that.p
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String identity, String name, DateTime joinedAt, String? accountId, SnChatMember? profile) $default,) {final _that = this;
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String identity, String name, DateTime joinedAt) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _CallParticipant():
|
||||
return $default(_that.identity,_that.name,_that.joinedAt,_that.accountId,_that.profile);}
|
||||
return $default(_that.identity,_that.name,_that.joinedAt);}
|
||||
}
|
||||
/// A variant of `when` that fallback to returning `null`
|
||||
///
|
||||
@@ -2687,10 +2673,10 @@ return $default(_that.identity,_that.name,_that.joinedAt,_that.accountId,_that.p
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String identity, String name, DateTime joinedAt, String? accountId, SnChatMember? profile)? $default,) {final _that = this;
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String identity, String name, DateTime joinedAt)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _CallParticipant() when $default != null:
|
||||
return $default(_that.identity,_that.name,_that.joinedAt,_that.accountId,_that.profile);case _:
|
||||
return $default(_that.identity,_that.name,_that.joinedAt);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
@@ -2702,14 +2688,12 @@ return $default(_that.identity,_that.name,_that.joinedAt,_that.accountId,_that.p
|
||||
@JsonSerializable()
|
||||
|
||||
class _CallParticipant implements CallParticipant {
|
||||
const _CallParticipant({required this.identity, required this.name, required this.joinedAt, required this.accountId, required this.profile});
|
||||
const _CallParticipant({required this.identity, required this.name, required this.joinedAt});
|
||||
factory _CallParticipant.fromJson(Map<String, dynamic> json) => _$CallParticipantFromJson(json);
|
||||
|
||||
@override final String identity;
|
||||
@override final String name;
|
||||
@override final DateTime joinedAt;
|
||||
@override final String? accountId;
|
||||
@override final SnChatMember? profile;
|
||||
|
||||
/// Create a copy of CallParticipant
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@@ -2724,16 +2708,16 @@ Map<String, dynamic> toJson() {
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _CallParticipant&&(identical(other.identity, identity) || other.identity == identity)&&(identical(other.name, name) || other.name == name)&&(identical(other.joinedAt, joinedAt) || other.joinedAt == joinedAt)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.profile, profile) || other.profile == profile));
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _CallParticipant&&(identical(other.identity, identity) || other.identity == identity)&&(identical(other.name, name) || other.name == name)&&(identical(other.joinedAt, joinedAt) || other.joinedAt == joinedAt));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,identity,name,joinedAt,accountId,profile);
|
||||
int get hashCode => Object.hash(runtimeType,identity,name,joinedAt);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'CallParticipant(identity: $identity, name: $name, joinedAt: $joinedAt, accountId: $accountId, profile: $profile)';
|
||||
return 'CallParticipant(identity: $identity, name: $name, joinedAt: $joinedAt)';
|
||||
}
|
||||
|
||||
|
||||
@@ -2744,11 +2728,11 @@ abstract mixin class _$CallParticipantCopyWith<$Res> implements $CallParticipant
|
||||
factory _$CallParticipantCopyWith(_CallParticipant value, $Res Function(_CallParticipant) _then) = __$CallParticipantCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
String identity, String name, DateTime joinedAt, String? accountId, SnChatMember? profile
|
||||
String identity, String name, DateTime joinedAt
|
||||
});
|
||||
|
||||
|
||||
@override $SnChatMemberCopyWith<$Res>? get profile;
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
@@ -2761,30 +2745,16 @@ class __$CallParticipantCopyWithImpl<$Res>
|
||||
|
||||
/// Create a copy of CallParticipant
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? identity = null,Object? name = null,Object? joinedAt = null,Object? accountId = freezed,Object? profile = freezed,}) {
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? identity = null,Object? name = null,Object? joinedAt = null,}) {
|
||||
return _then(_CallParticipant(
|
||||
identity: null == identity ? _self.identity : identity // ignore: cast_nullable_to_non_nullable
|
||||
as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
||||
as String,joinedAt: null == joinedAt ? _self.joinedAt : joinedAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime,accountId: freezed == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable
|
||||
as String?,profile: freezed == profile ? _self.profile : profile // ignore: cast_nullable_to_non_nullable
|
||||
as SnChatMember?,
|
||||
as DateTime,
|
||||
));
|
||||
}
|
||||
|
||||
/// Create a copy of CallParticipant
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$SnChatMemberCopyWith<$Res>? get profile {
|
||||
if (_self.profile == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $SnChatMemberCopyWith<$Res>(_self.profile!, (value) {
|
||||
return _then(_self.copyWith(profile: value));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -285,11 +285,6 @@ _CallParticipant _$CallParticipantFromJson(Map<String, dynamic> json) =>
|
||||
identity: json['identity'] as String,
|
||||
name: json['name'] as String,
|
||||
joinedAt: DateTime.parse(json['joined_at'] as String),
|
||||
accountId: json['account_id'] as String?,
|
||||
profile:
|
||||
json['profile'] == null
|
||||
? null
|
||||
: SnChatMember.fromJson(json['profile'] as Map<String, dynamic>),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$CallParticipantToJson(_CallParticipant instance) =>
|
||||
@@ -297,8 +292,6 @@ Map<String, dynamic> _$CallParticipantToJson(_CallParticipant instance) =>
|
||||
'identity': instance.identity,
|
||||
'name': instance.name,
|
||||
'joined_at': instance.joinedAt.toIso8601String(),
|
||||
'account_id': instance.accountId,
|
||||
'profile': instance.profile?.toJson(),
|
||||
};
|
||||
|
||||
_SnRealtimeCall _$SnRealtimeCallFromJson(Map<String, dynamic> json) =>
|
||||
|
||||
@@ -3,25 +3,6 @@ import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
part 'embed.freezed.dart';
|
||||
part 'embed.g.dart';
|
||||
|
||||
@freezed
|
||||
sealed class SnEmbedLink with _$SnEmbedLink {
|
||||
const factory SnEmbedLink({
|
||||
@JsonKey(name: 'Type') required String type,
|
||||
@JsonKey(name: 'Url') required String url,
|
||||
@JsonKey(name: 'Title') required String title,
|
||||
@JsonKey(name: 'Description') required String? description,
|
||||
@JsonKey(name: 'ImageUrl') required String? imageUrl,
|
||||
@JsonKey(name: 'FaviconUrl') required String faviconUrl,
|
||||
@JsonKey(name: 'SiteName') required String siteName,
|
||||
@JsonKey(name: 'ContentType') required String? contentType,
|
||||
@JsonKey(name: 'Author') required String? author,
|
||||
@JsonKey(name: 'PublishedDate') required DateTime? publishedDate,
|
||||
}) = _SnEmbedLink;
|
||||
|
||||
factory SnEmbedLink.fromJson(Map<String, dynamic> json) =>
|
||||
_$SnEmbedLinkFromJson(json);
|
||||
}
|
||||
|
||||
@freezed
|
||||
sealed class SnScrappedLink with _$SnScrappedLink {
|
||||
const factory SnScrappedLink({
|
||||
|
||||
@@ -12,290 +12,6 @@ part of 'embed.dart';
|
||||
// dart format off
|
||||
T _$identity<T>(T value) => value;
|
||||
|
||||
/// @nodoc
|
||||
mixin _$SnEmbedLink {
|
||||
|
||||
@JsonKey(name: 'Type') String get type;@JsonKey(name: 'Url') String get url;@JsonKey(name: 'Title') String get title;@JsonKey(name: 'Description') String? get description;@JsonKey(name: 'ImageUrl') String? get imageUrl;@JsonKey(name: 'FaviconUrl') String get faviconUrl;@JsonKey(name: 'SiteName') String get siteName;@JsonKey(name: 'ContentType') String? get contentType;@JsonKey(name: 'Author') String? get author;@JsonKey(name: 'PublishedDate') DateTime? get publishedDate;
|
||||
/// Create a copy of SnEmbedLink
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
$SnEmbedLinkCopyWith<SnEmbedLink> get copyWith => _$SnEmbedLinkCopyWithImpl<SnEmbedLink>(this as SnEmbedLink, _$identity);
|
||||
|
||||
/// Serializes this SnEmbedLink to a JSON map.
|
||||
Map<String, dynamic> toJson();
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnEmbedLink&&(identical(other.type, type) || other.type == type)&&(identical(other.url, url) || other.url == url)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)&&(identical(other.imageUrl, imageUrl) || other.imageUrl == imageUrl)&&(identical(other.faviconUrl, faviconUrl) || other.faviconUrl == faviconUrl)&&(identical(other.siteName, siteName) || other.siteName == siteName)&&(identical(other.contentType, contentType) || other.contentType == contentType)&&(identical(other.author, author) || other.author == author)&&(identical(other.publishedDate, publishedDate) || other.publishedDate == publishedDate));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,type,url,title,description,imageUrl,faviconUrl,siteName,contentType,author,publishedDate);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SnEmbedLink(type: $type, url: $url, title: $title, description: $description, imageUrl: $imageUrl, faviconUrl: $faviconUrl, siteName: $siteName, contentType: $contentType, author: $author, publishedDate: $publishedDate)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class $SnEmbedLinkCopyWith<$Res> {
|
||||
factory $SnEmbedLinkCopyWith(SnEmbedLink value, $Res Function(SnEmbedLink) _then) = _$SnEmbedLinkCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
@JsonKey(name: 'Type') String type,@JsonKey(name: 'Url') String url,@JsonKey(name: 'Title') String title,@JsonKey(name: 'Description') String? description,@JsonKey(name: 'ImageUrl') String? imageUrl,@JsonKey(name: 'FaviconUrl') String faviconUrl,@JsonKey(name: 'SiteName') String siteName,@JsonKey(name: 'ContentType') String? contentType,@JsonKey(name: 'Author') String? author,@JsonKey(name: 'PublishedDate') DateTime? publishedDate
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class _$SnEmbedLinkCopyWithImpl<$Res>
|
||||
implements $SnEmbedLinkCopyWith<$Res> {
|
||||
_$SnEmbedLinkCopyWithImpl(this._self, this._then);
|
||||
|
||||
final SnEmbedLink _self;
|
||||
final $Res Function(SnEmbedLink) _then;
|
||||
|
||||
/// Create a copy of SnEmbedLink
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? type = null,Object? url = null,Object? title = null,Object? description = freezed,Object? imageUrl = freezed,Object? faviconUrl = null,Object? siteName = null,Object? contentType = freezed,Object? author = freezed,Object? publishedDate = freezed,}) {
|
||||
return _then(_self.copyWith(
|
||||
type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
|
||||
as String,url: null == url ? _self.url : url // ignore: cast_nullable_to_non_nullable
|
||||
as String,title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable
|
||||
as String,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
|
||||
as String?,imageUrl: freezed == imageUrl ? _self.imageUrl : imageUrl // ignore: cast_nullable_to_non_nullable
|
||||
as String?,faviconUrl: null == faviconUrl ? _self.faviconUrl : faviconUrl // ignore: cast_nullable_to_non_nullable
|
||||
as String,siteName: null == siteName ? _self.siteName : siteName // ignore: cast_nullable_to_non_nullable
|
||||
as String,contentType: freezed == contentType ? _self.contentType : contentType // ignore: cast_nullable_to_non_nullable
|
||||
as String?,author: freezed == author ? _self.author : author // ignore: cast_nullable_to_non_nullable
|
||||
as String?,publishedDate: freezed == publishedDate ? _self.publishedDate : publishedDate // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime?,
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// Adds pattern-matching-related methods to [SnEmbedLink].
|
||||
extension SnEmbedLinkPatterns on SnEmbedLink {
|
||||
/// A variant of `map` that fallback to returning `orElse`.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return orElse();
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _SnEmbedLink value)? $default,{required TResult orElse(),}){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _SnEmbedLink() when $default != null:
|
||||
return $default(_that);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
}
|
||||
/// A `switch`-like method, using callbacks.
|
||||
///
|
||||
/// Callbacks receives the raw object, upcasted.
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case final Subclass2 value:
|
||||
/// return ...;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _SnEmbedLink value) $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _SnEmbedLink():
|
||||
return $default(_that);}
|
||||
}
|
||||
/// A variant of `map` that fallback to returning `null`.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return null;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _SnEmbedLink value)? $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _SnEmbedLink() when $default != null:
|
||||
return $default(_that);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
/// A variant of `when` that fallback to an `orElse` callback.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return orElse();
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function(@JsonKey(name: 'Type') String type, @JsonKey(name: 'Url') String url, @JsonKey(name: 'Title') String title, @JsonKey(name: 'Description') String? description, @JsonKey(name: 'ImageUrl') String? imageUrl, @JsonKey(name: 'FaviconUrl') String faviconUrl, @JsonKey(name: 'SiteName') String siteName, @JsonKey(name: 'ContentType') String? contentType, @JsonKey(name: 'Author') String? author, @JsonKey(name: 'PublishedDate') DateTime? publishedDate)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _SnEmbedLink() when $default != null:
|
||||
return $default(_that.type,_that.url,_that.title,_that.description,_that.imageUrl,_that.faviconUrl,_that.siteName,_that.contentType,_that.author,_that.publishedDate);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
}
|
||||
/// A `switch`-like method, using callbacks.
|
||||
///
|
||||
/// As opposed to `map`, this offers destructuring.
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case Subclass2(:final field2):
|
||||
/// return ...;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function(@JsonKey(name: 'Type') String type, @JsonKey(name: 'Url') String url, @JsonKey(name: 'Title') String title, @JsonKey(name: 'Description') String? description, @JsonKey(name: 'ImageUrl') String? imageUrl, @JsonKey(name: 'FaviconUrl') String faviconUrl, @JsonKey(name: 'SiteName') String siteName, @JsonKey(name: 'ContentType') String? contentType, @JsonKey(name: 'Author') String? author, @JsonKey(name: 'PublishedDate') DateTime? publishedDate) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _SnEmbedLink():
|
||||
return $default(_that.type,_that.url,_that.title,_that.description,_that.imageUrl,_that.faviconUrl,_that.siteName,_that.contentType,_that.author,_that.publishedDate);}
|
||||
}
|
||||
/// A variant of `when` that fallback to returning `null`
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return null;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function(@JsonKey(name: 'Type') String type, @JsonKey(name: 'Url') String url, @JsonKey(name: 'Title') String title, @JsonKey(name: 'Description') String? description, @JsonKey(name: 'ImageUrl') String? imageUrl, @JsonKey(name: 'FaviconUrl') String faviconUrl, @JsonKey(name: 'SiteName') String siteName, @JsonKey(name: 'ContentType') String? contentType, @JsonKey(name: 'Author') String? author, @JsonKey(name: 'PublishedDate') DateTime? publishedDate)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _SnEmbedLink() when $default != null:
|
||||
return $default(_that.type,_that.url,_that.title,_that.description,_that.imageUrl,_that.faviconUrl,_that.siteName,_that.contentType,_that.author,_that.publishedDate);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
|
||||
class _SnEmbedLink implements SnEmbedLink {
|
||||
const _SnEmbedLink({@JsonKey(name: 'Type') required this.type, @JsonKey(name: 'Url') required this.url, @JsonKey(name: 'Title') required this.title, @JsonKey(name: 'Description') required this.description, @JsonKey(name: 'ImageUrl') required this.imageUrl, @JsonKey(name: 'FaviconUrl') required this.faviconUrl, @JsonKey(name: 'SiteName') required this.siteName, @JsonKey(name: 'ContentType') required this.contentType, @JsonKey(name: 'Author') required this.author, @JsonKey(name: 'PublishedDate') required this.publishedDate});
|
||||
factory _SnEmbedLink.fromJson(Map<String, dynamic> json) => _$SnEmbedLinkFromJson(json);
|
||||
|
||||
@override@JsonKey(name: 'Type') final String type;
|
||||
@override@JsonKey(name: 'Url') final String url;
|
||||
@override@JsonKey(name: 'Title') final String title;
|
||||
@override@JsonKey(name: 'Description') final String? description;
|
||||
@override@JsonKey(name: 'ImageUrl') final String? imageUrl;
|
||||
@override@JsonKey(name: 'FaviconUrl') final String faviconUrl;
|
||||
@override@JsonKey(name: 'SiteName') final String siteName;
|
||||
@override@JsonKey(name: 'ContentType') final String? contentType;
|
||||
@override@JsonKey(name: 'Author') final String? author;
|
||||
@override@JsonKey(name: 'PublishedDate') final DateTime? publishedDate;
|
||||
|
||||
/// Create a copy of SnEmbedLink
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
_$SnEmbedLinkCopyWith<_SnEmbedLink> get copyWith => __$SnEmbedLinkCopyWithImpl<_SnEmbedLink>(this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$SnEmbedLinkToJson(this, );
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnEmbedLink&&(identical(other.type, type) || other.type == type)&&(identical(other.url, url) || other.url == url)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)&&(identical(other.imageUrl, imageUrl) || other.imageUrl == imageUrl)&&(identical(other.faviconUrl, faviconUrl) || other.faviconUrl == faviconUrl)&&(identical(other.siteName, siteName) || other.siteName == siteName)&&(identical(other.contentType, contentType) || other.contentType == contentType)&&(identical(other.author, author) || other.author == author)&&(identical(other.publishedDate, publishedDate) || other.publishedDate == publishedDate));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,type,url,title,description,imageUrl,faviconUrl,siteName,contentType,author,publishedDate);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SnEmbedLink(type: $type, url: $url, title: $title, description: $description, imageUrl: $imageUrl, faviconUrl: $faviconUrl, siteName: $siteName, contentType: $contentType, author: $author, publishedDate: $publishedDate)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class _$SnEmbedLinkCopyWith<$Res> implements $SnEmbedLinkCopyWith<$Res> {
|
||||
factory _$SnEmbedLinkCopyWith(_SnEmbedLink value, $Res Function(_SnEmbedLink) _then) = __$SnEmbedLinkCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
@JsonKey(name: 'Type') String type,@JsonKey(name: 'Url') String url,@JsonKey(name: 'Title') String title,@JsonKey(name: 'Description') String? description,@JsonKey(name: 'ImageUrl') String? imageUrl,@JsonKey(name: 'FaviconUrl') String faviconUrl,@JsonKey(name: 'SiteName') String siteName,@JsonKey(name: 'ContentType') String? contentType,@JsonKey(name: 'Author') String? author,@JsonKey(name: 'PublishedDate') DateTime? publishedDate
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class __$SnEmbedLinkCopyWithImpl<$Res>
|
||||
implements _$SnEmbedLinkCopyWith<$Res> {
|
||||
__$SnEmbedLinkCopyWithImpl(this._self, this._then);
|
||||
|
||||
final _SnEmbedLink _self;
|
||||
final $Res Function(_SnEmbedLink) _then;
|
||||
|
||||
/// Create a copy of SnEmbedLink
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? type = null,Object? url = null,Object? title = null,Object? description = freezed,Object? imageUrl = freezed,Object? faviconUrl = null,Object? siteName = null,Object? contentType = freezed,Object? author = freezed,Object? publishedDate = freezed,}) {
|
||||
return _then(_SnEmbedLink(
|
||||
type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
|
||||
as String,url: null == url ? _self.url : url // ignore: cast_nullable_to_non_nullable
|
||||
as String,title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable
|
||||
as String,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
|
||||
as String?,imageUrl: freezed == imageUrl ? _self.imageUrl : imageUrl // ignore: cast_nullable_to_non_nullable
|
||||
as String?,faviconUrl: null == faviconUrl ? _self.faviconUrl : faviconUrl // ignore: cast_nullable_to_non_nullable
|
||||
as String,siteName: null == siteName ? _self.siteName : siteName // ignore: cast_nullable_to_non_nullable
|
||||
as String,contentType: freezed == contentType ? _self.contentType : contentType // ignore: cast_nullable_to_non_nullable
|
||||
as String?,author: freezed == author ? _self.author : author // ignore: cast_nullable_to_non_nullable
|
||||
as String?,publishedDate: freezed == publishedDate ? _self.publishedDate : publishedDate // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime?,
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// @nodoc
|
||||
mixin _$SnScrappedLink {
|
||||
|
||||
|
||||
@@ -6,36 +6,6 @@ part of 'embed.dart';
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
_SnEmbedLink _$SnEmbedLinkFromJson(Map<String, dynamic> json) => _SnEmbedLink(
|
||||
type: json['Type'] as String,
|
||||
url: json['Url'] as String,
|
||||
title: json['Title'] as String,
|
||||
description: json['Description'] as String?,
|
||||
imageUrl: json['ImageUrl'] as String?,
|
||||
faviconUrl: json['FaviconUrl'] as String,
|
||||
siteName: json['SiteName'] as String,
|
||||
contentType: json['ContentType'] as String?,
|
||||
author: json['Author'] as String?,
|
||||
publishedDate:
|
||||
json['PublishedDate'] == null
|
||||
? null
|
||||
: DateTime.parse(json['PublishedDate'] as String),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$SnEmbedLinkToJson(_SnEmbedLink instance) =>
|
||||
<String, dynamic>{
|
||||
'Type': instance.type,
|
||||
'Url': instance.url,
|
||||
'Title': instance.title,
|
||||
'Description': instance.description,
|
||||
'ImageUrl': instance.imageUrl,
|
||||
'FaviconUrl': instance.faviconUrl,
|
||||
'SiteName': instance.siteName,
|
||||
'ContentType': instance.contentType,
|
||||
'Author': instance.author,
|
||||
'PublishedDate': instance.publishedDate?.toIso8601String(),
|
||||
};
|
||||
|
||||
_SnScrappedLink _$SnScrappedLinkFromJson(Map<String, dynamic> json) =>
|
||||
_SnScrappedLink(
|
||||
type: json['type'] as String,
|
||||
|
||||
@@ -12,6 +12,7 @@ sealed class UniversalFile with _$UniversalFile {
|
||||
const factory UniversalFile({
|
||||
required dynamic data,
|
||||
required UniversalFileType type,
|
||||
@Default(false) bool isLink,
|
||||
}) = _UniversalFile;
|
||||
|
||||
factory UniversalFile.fromJson(Map<String, dynamic> json) =>
|
||||
@@ -41,6 +42,7 @@ sealed class SnCloudFile with _$SnCloudFile {
|
||||
required String? description,
|
||||
required Map<String, dynamic>? fileMeta,
|
||||
required Map<String, dynamic>? userMeta,
|
||||
@Default([]) List<int> sensitiveMarks,
|
||||
required String? mimeType,
|
||||
required String? hash,
|
||||
required int size,
|
||||
|
||||
@@ -15,7 +15,7 @@ T _$identity<T>(T value) => value;
|
||||
/// @nodoc
|
||||
mixin _$UniversalFile {
|
||||
|
||||
dynamic get data; UniversalFileType get type;
|
||||
dynamic get data; UniversalFileType get type; bool get isLink;
|
||||
/// Create a copy of UniversalFile
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@@ -28,16 +28,16 @@ $UniversalFileCopyWith<UniversalFile> get copyWith => _$UniversalFileCopyWithImp
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is UniversalFile&&const DeepCollectionEquality().equals(other.data, data)&&(identical(other.type, type) || other.type == type));
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is UniversalFile&&const DeepCollectionEquality().equals(other.data, data)&&(identical(other.type, type) || other.type == type)&&(identical(other.isLink, isLink) || other.isLink == isLink));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(data),type);
|
||||
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(data),type,isLink);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'UniversalFile(data: $data, type: $type)';
|
||||
return 'UniversalFile(data: $data, type: $type, isLink: $isLink)';
|
||||
}
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ abstract mixin class $UniversalFileCopyWith<$Res> {
|
||||
factory $UniversalFileCopyWith(UniversalFile value, $Res Function(UniversalFile) _then) = _$UniversalFileCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
dynamic data, UniversalFileType type
|
||||
dynamic data, UniversalFileType type, bool isLink
|
||||
});
|
||||
|
||||
|
||||
@@ -65,11 +65,12 @@ class _$UniversalFileCopyWithImpl<$Res>
|
||||
|
||||
/// Create a copy of UniversalFile
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? data = freezed,Object? type = null,}) {
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? data = freezed,Object? type = null,Object? isLink = null,}) {
|
||||
return _then(_self.copyWith(
|
||||
data: freezed == data ? _self.data : data // ignore: cast_nullable_to_non_nullable
|
||||
as dynamic,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
|
||||
as UniversalFileType,
|
||||
as UniversalFileType,isLink: null == isLink ? _self.isLink : isLink // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
));
|
||||
}
|
||||
|
||||
@@ -151,10 +152,10 @@ return $default(_that);case _:
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( dynamic data, UniversalFileType type)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( dynamic data, UniversalFileType type, bool isLink)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _UniversalFile() when $default != null:
|
||||
return $default(_that.data,_that.type);case _:
|
||||
return $default(_that.data,_that.type,_that.isLink);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
@@ -172,10 +173,10 @@ return $default(_that.data,_that.type);case _:
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( dynamic data, UniversalFileType type) $default,) {final _that = this;
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( dynamic data, UniversalFileType type, bool isLink) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _UniversalFile():
|
||||
return $default(_that.data,_that.type);}
|
||||
return $default(_that.data,_that.type,_that.isLink);}
|
||||
}
|
||||
/// A variant of `when` that fallback to returning `null`
|
||||
///
|
||||
@@ -189,10 +190,10 @@ return $default(_that.data,_that.type);}
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( dynamic data, UniversalFileType type)? $default,) {final _that = this;
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( dynamic data, UniversalFileType type, bool isLink)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _UniversalFile() when $default != null:
|
||||
return $default(_that.data,_that.type);case _:
|
||||
return $default(_that.data,_that.type,_that.isLink);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
@@ -204,11 +205,12 @@ return $default(_that.data,_that.type);case _:
|
||||
@JsonSerializable()
|
||||
|
||||
class _UniversalFile extends UniversalFile {
|
||||
const _UniversalFile({required this.data, required this.type}): super._();
|
||||
const _UniversalFile({required this.data, required this.type, this.isLink = false}): super._();
|
||||
factory _UniversalFile.fromJson(Map<String, dynamic> json) => _$UniversalFileFromJson(json);
|
||||
|
||||
@override final dynamic data;
|
||||
@override final UniversalFileType type;
|
||||
@override@JsonKey() final bool isLink;
|
||||
|
||||
/// Create a copy of UniversalFile
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@@ -223,16 +225,16 @@ Map<String, dynamic> toJson() {
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _UniversalFile&&const DeepCollectionEquality().equals(other.data, data)&&(identical(other.type, type) || other.type == type));
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _UniversalFile&&const DeepCollectionEquality().equals(other.data, data)&&(identical(other.type, type) || other.type == type)&&(identical(other.isLink, isLink) || other.isLink == isLink));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(data),type);
|
||||
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(data),type,isLink);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'UniversalFile(data: $data, type: $type)';
|
||||
return 'UniversalFile(data: $data, type: $type, isLink: $isLink)';
|
||||
}
|
||||
|
||||
|
||||
@@ -243,7 +245,7 @@ abstract mixin class _$UniversalFileCopyWith<$Res> implements $UniversalFileCopy
|
||||
factory _$UniversalFileCopyWith(_UniversalFile value, $Res Function(_UniversalFile) _then) = __$UniversalFileCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
dynamic data, UniversalFileType type
|
||||
dynamic data, UniversalFileType type, bool isLink
|
||||
});
|
||||
|
||||
|
||||
@@ -260,11 +262,12 @@ class __$UniversalFileCopyWithImpl<$Res>
|
||||
|
||||
/// Create a copy of UniversalFile
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? data = freezed,Object? type = null,}) {
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? data = freezed,Object? type = null,Object? isLink = null,}) {
|
||||
return _then(_UniversalFile(
|
||||
data: freezed == data ? _self.data : data // ignore: cast_nullable_to_non_nullable
|
||||
as dynamic,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
|
||||
as UniversalFileType,
|
||||
as UniversalFileType,isLink: null == isLink ? _self.isLink : isLink // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
));
|
||||
}
|
||||
|
||||
@@ -275,7 +278,7 @@ as UniversalFileType,
|
||||
/// @nodoc
|
||||
mixin _$SnCloudFile {
|
||||
|
||||
String get id; String get name; String? get description; Map<String, dynamic>? get fileMeta; Map<String, dynamic>? get userMeta; String? get mimeType; String? get hash; int get size; DateTime? get uploadedAt; String? get uploadedTo; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt;
|
||||
String get id; String get name; String? get description; Map<String, dynamic>? get fileMeta; Map<String, dynamic>? get userMeta; List<int> get sensitiveMarks; String? get mimeType; String? get hash; int get size; DateTime? get uploadedAt; String? get uploadedTo; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt;
|
||||
/// Create a copy of SnCloudFile
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@@ -288,16 +291,16 @@ $SnCloudFileCopyWith<SnCloudFile> get copyWith => _$SnCloudFileCopyWithImpl<SnCl
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnCloudFile&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&const DeepCollectionEquality().equals(other.fileMeta, fileMeta)&&const DeepCollectionEquality().equals(other.userMeta, userMeta)&&(identical(other.mimeType, mimeType) || other.mimeType == mimeType)&&(identical(other.hash, hash) || other.hash == hash)&&(identical(other.size, size) || other.size == size)&&(identical(other.uploadedAt, uploadedAt) || other.uploadedAt == uploadedAt)&&(identical(other.uploadedTo, uploadedTo) || other.uploadedTo == uploadedTo)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnCloudFile&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&const DeepCollectionEquality().equals(other.fileMeta, fileMeta)&&const DeepCollectionEquality().equals(other.userMeta, userMeta)&&const DeepCollectionEquality().equals(other.sensitiveMarks, sensitiveMarks)&&(identical(other.mimeType, mimeType) || other.mimeType == mimeType)&&(identical(other.hash, hash) || other.hash == hash)&&(identical(other.size, size) || other.size == size)&&(identical(other.uploadedAt, uploadedAt) || other.uploadedAt == uploadedAt)&&(identical(other.uploadedTo, uploadedTo) || other.uploadedTo == uploadedTo)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,id,name,description,const DeepCollectionEquality().hash(fileMeta),const DeepCollectionEquality().hash(userMeta),mimeType,hash,size,uploadedAt,uploadedTo,createdAt,updatedAt,deletedAt);
|
||||
int get hashCode => Object.hash(runtimeType,id,name,description,const DeepCollectionEquality().hash(fileMeta),const DeepCollectionEquality().hash(userMeta),const DeepCollectionEquality().hash(sensitiveMarks),mimeType,hash,size,uploadedAt,uploadedTo,createdAt,updatedAt,deletedAt);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SnCloudFile(id: $id, name: $name, description: $description, fileMeta: $fileMeta, userMeta: $userMeta, mimeType: $mimeType, hash: $hash, size: $size, uploadedAt: $uploadedAt, uploadedTo: $uploadedTo, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
|
||||
return 'SnCloudFile(id: $id, name: $name, description: $description, fileMeta: $fileMeta, userMeta: $userMeta, sensitiveMarks: $sensitiveMarks, mimeType: $mimeType, hash: $hash, size: $size, uploadedAt: $uploadedAt, uploadedTo: $uploadedTo, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
|
||||
}
|
||||
|
||||
|
||||
@@ -308,7 +311,7 @@ abstract mixin class $SnCloudFileCopyWith<$Res> {
|
||||
factory $SnCloudFileCopyWith(SnCloudFile value, $Res Function(SnCloudFile) _then) = _$SnCloudFileCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
String id, String name, String? description, Map<String, dynamic>? fileMeta, Map<String, dynamic>? userMeta, String? mimeType, String? hash, int size, DateTime? uploadedAt, String? uploadedTo, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
|
||||
String id, String name, String? description, Map<String, dynamic>? fileMeta, Map<String, dynamic>? userMeta, List<int> sensitiveMarks, String? mimeType, String? hash, int size, DateTime? uploadedAt, String? uploadedTo, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
|
||||
});
|
||||
|
||||
|
||||
@@ -325,14 +328,15 @@ class _$SnCloudFileCopyWithImpl<$Res>
|
||||
|
||||
/// Create a copy of SnCloudFile
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? name = null,Object? description = freezed,Object? fileMeta = freezed,Object? userMeta = freezed,Object? mimeType = freezed,Object? hash = freezed,Object? size = null,Object? uploadedAt = freezed,Object? uploadedTo = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? name = null,Object? description = freezed,Object? fileMeta = freezed,Object? userMeta = freezed,Object? sensitiveMarks = null,Object? mimeType = freezed,Object? hash = freezed,Object? size = null,Object? uploadedAt = freezed,Object? uploadedTo = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
|
||||
return _then(_self.copyWith(
|
||||
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||
as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
||||
as String,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
|
||||
as String?,fileMeta: freezed == fileMeta ? _self.fileMeta : fileMeta // ignore: cast_nullable_to_non_nullable
|
||||
as Map<String, dynamic>?,userMeta: freezed == userMeta ? _self.userMeta : userMeta // ignore: cast_nullable_to_non_nullable
|
||||
as Map<String, dynamic>?,mimeType: freezed == mimeType ? _self.mimeType : mimeType // ignore: cast_nullable_to_non_nullable
|
||||
as Map<String, dynamic>?,sensitiveMarks: null == sensitiveMarks ? _self.sensitiveMarks : sensitiveMarks // ignore: cast_nullable_to_non_nullable
|
||||
as List<int>,mimeType: freezed == mimeType ? _self.mimeType : mimeType // ignore: cast_nullable_to_non_nullable
|
||||
as String?,hash: freezed == hash ? _self.hash : hash // ignore: cast_nullable_to_non_nullable
|
||||
as String?,size: null == size ? _self.size : size // ignore: cast_nullable_to_non_nullable
|
||||
as int,uploadedAt: freezed == uploadedAt ? _self.uploadedAt : uploadedAt // ignore: cast_nullable_to_non_nullable
|
||||
@@ -422,10 +426,10 @@ return $default(_that);case _:
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, String name, String? description, Map<String, dynamic>? fileMeta, Map<String, dynamic>? userMeta, String? mimeType, String? hash, int size, DateTime? uploadedAt, String? uploadedTo, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, String name, String? description, Map<String, dynamic>? fileMeta, Map<String, dynamic>? userMeta, List<int> sensitiveMarks, String? mimeType, String? hash, int size, DateTime? uploadedAt, String? uploadedTo, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _SnCloudFile() when $default != null:
|
||||
return $default(_that.id,_that.name,_that.description,_that.fileMeta,_that.userMeta,_that.mimeType,_that.hash,_that.size,_that.uploadedAt,_that.uploadedTo,_that.createdAt,_that.updatedAt,_that.deletedAt);case _:
|
||||
return $default(_that.id,_that.name,_that.description,_that.fileMeta,_that.userMeta,_that.sensitiveMarks,_that.mimeType,_that.hash,_that.size,_that.uploadedAt,_that.uploadedTo,_that.createdAt,_that.updatedAt,_that.deletedAt);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
@@ -443,10 +447,10 @@ return $default(_that.id,_that.name,_that.description,_that.fileMeta,_that.userM
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, String name, String? description, Map<String, dynamic>? fileMeta, Map<String, dynamic>? userMeta, String? mimeType, String? hash, int size, DateTime? uploadedAt, String? uploadedTo, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt) $default,) {final _that = this;
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, String name, String? description, Map<String, dynamic>? fileMeta, Map<String, dynamic>? userMeta, List<int> sensitiveMarks, String? mimeType, String? hash, int size, DateTime? uploadedAt, String? uploadedTo, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _SnCloudFile():
|
||||
return $default(_that.id,_that.name,_that.description,_that.fileMeta,_that.userMeta,_that.mimeType,_that.hash,_that.size,_that.uploadedAt,_that.uploadedTo,_that.createdAt,_that.updatedAt,_that.deletedAt);}
|
||||
return $default(_that.id,_that.name,_that.description,_that.fileMeta,_that.userMeta,_that.sensitiveMarks,_that.mimeType,_that.hash,_that.size,_that.uploadedAt,_that.uploadedTo,_that.createdAt,_that.updatedAt,_that.deletedAt);}
|
||||
}
|
||||
/// A variant of `when` that fallback to returning `null`
|
||||
///
|
||||
@@ -460,10 +464,10 @@ return $default(_that.id,_that.name,_that.description,_that.fileMeta,_that.userM
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, String name, String? description, Map<String, dynamic>? fileMeta, Map<String, dynamic>? userMeta, String? mimeType, String? hash, int size, DateTime? uploadedAt, String? uploadedTo, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt)? $default,) {final _that = this;
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, String name, String? description, Map<String, dynamic>? fileMeta, Map<String, dynamic>? userMeta, List<int> sensitiveMarks, String? mimeType, String? hash, int size, DateTime? uploadedAt, String? uploadedTo, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _SnCloudFile() when $default != null:
|
||||
return $default(_that.id,_that.name,_that.description,_that.fileMeta,_that.userMeta,_that.mimeType,_that.hash,_that.size,_that.uploadedAt,_that.uploadedTo,_that.createdAt,_that.updatedAt,_that.deletedAt);case _:
|
||||
return $default(_that.id,_that.name,_that.description,_that.fileMeta,_that.userMeta,_that.sensitiveMarks,_that.mimeType,_that.hash,_that.size,_that.uploadedAt,_that.uploadedTo,_that.createdAt,_that.updatedAt,_that.deletedAt);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
@@ -475,7 +479,7 @@ return $default(_that.id,_that.name,_that.description,_that.fileMeta,_that.userM
|
||||
@JsonSerializable()
|
||||
|
||||
class _SnCloudFile implements SnCloudFile {
|
||||
const _SnCloudFile({required this.id, required this.name, required this.description, required final Map<String, dynamic>? fileMeta, required final Map<String, dynamic>? userMeta, required this.mimeType, required this.hash, required this.size, required this.uploadedAt, required this.uploadedTo, required this.createdAt, required this.updatedAt, required this.deletedAt}): _fileMeta = fileMeta,_userMeta = userMeta;
|
||||
const _SnCloudFile({required this.id, required this.name, required this.description, required final Map<String, dynamic>? fileMeta, required final Map<String, dynamic>? userMeta, final List<int> sensitiveMarks = const [], required this.mimeType, required this.hash, required this.size, required this.uploadedAt, required this.uploadedTo, required this.createdAt, required this.updatedAt, required this.deletedAt}): _fileMeta = fileMeta,_userMeta = userMeta,_sensitiveMarks = sensitiveMarks;
|
||||
factory _SnCloudFile.fromJson(Map<String, dynamic> json) => _$SnCloudFileFromJson(json);
|
||||
|
||||
@override final String id;
|
||||
@@ -499,6 +503,13 @@ class _SnCloudFile implements SnCloudFile {
|
||||
return EqualUnmodifiableMapView(value);
|
||||
}
|
||||
|
||||
final List<int> _sensitiveMarks;
|
||||
@override@JsonKey() List<int> get sensitiveMarks {
|
||||
if (_sensitiveMarks is EqualUnmodifiableListView) return _sensitiveMarks;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(_sensitiveMarks);
|
||||
}
|
||||
|
||||
@override final String? mimeType;
|
||||
@override final String? hash;
|
||||
@override final int size;
|
||||
@@ -521,16 +532,16 @@ Map<String, dynamic> toJson() {
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnCloudFile&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&const DeepCollectionEquality().equals(other._fileMeta, _fileMeta)&&const DeepCollectionEquality().equals(other._userMeta, _userMeta)&&(identical(other.mimeType, mimeType) || other.mimeType == mimeType)&&(identical(other.hash, hash) || other.hash == hash)&&(identical(other.size, size) || other.size == size)&&(identical(other.uploadedAt, uploadedAt) || other.uploadedAt == uploadedAt)&&(identical(other.uploadedTo, uploadedTo) || other.uploadedTo == uploadedTo)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnCloudFile&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&const DeepCollectionEquality().equals(other._fileMeta, _fileMeta)&&const DeepCollectionEquality().equals(other._userMeta, _userMeta)&&const DeepCollectionEquality().equals(other._sensitiveMarks, _sensitiveMarks)&&(identical(other.mimeType, mimeType) || other.mimeType == mimeType)&&(identical(other.hash, hash) || other.hash == hash)&&(identical(other.size, size) || other.size == size)&&(identical(other.uploadedAt, uploadedAt) || other.uploadedAt == uploadedAt)&&(identical(other.uploadedTo, uploadedTo) || other.uploadedTo == uploadedTo)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,id,name,description,const DeepCollectionEquality().hash(_fileMeta),const DeepCollectionEquality().hash(_userMeta),mimeType,hash,size,uploadedAt,uploadedTo,createdAt,updatedAt,deletedAt);
|
||||
int get hashCode => Object.hash(runtimeType,id,name,description,const DeepCollectionEquality().hash(_fileMeta),const DeepCollectionEquality().hash(_userMeta),const DeepCollectionEquality().hash(_sensitiveMarks),mimeType,hash,size,uploadedAt,uploadedTo,createdAt,updatedAt,deletedAt);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SnCloudFile(id: $id, name: $name, description: $description, fileMeta: $fileMeta, userMeta: $userMeta, mimeType: $mimeType, hash: $hash, size: $size, uploadedAt: $uploadedAt, uploadedTo: $uploadedTo, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
|
||||
return 'SnCloudFile(id: $id, name: $name, description: $description, fileMeta: $fileMeta, userMeta: $userMeta, sensitiveMarks: $sensitiveMarks, mimeType: $mimeType, hash: $hash, size: $size, uploadedAt: $uploadedAt, uploadedTo: $uploadedTo, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
|
||||
}
|
||||
|
||||
|
||||
@@ -541,7 +552,7 @@ abstract mixin class _$SnCloudFileCopyWith<$Res> implements $SnCloudFileCopyWith
|
||||
factory _$SnCloudFileCopyWith(_SnCloudFile value, $Res Function(_SnCloudFile) _then) = __$SnCloudFileCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
String id, String name, String? description, Map<String, dynamic>? fileMeta, Map<String, dynamic>? userMeta, String? mimeType, String? hash, int size, DateTime? uploadedAt, String? uploadedTo, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
|
||||
String id, String name, String? description, Map<String, dynamic>? fileMeta, Map<String, dynamic>? userMeta, List<int> sensitiveMarks, String? mimeType, String? hash, int size, DateTime? uploadedAt, String? uploadedTo, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
|
||||
});
|
||||
|
||||
|
||||
@@ -558,14 +569,15 @@ class __$SnCloudFileCopyWithImpl<$Res>
|
||||
|
||||
/// Create a copy of SnCloudFile
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? name = null,Object? description = freezed,Object? fileMeta = freezed,Object? userMeta = freezed,Object? mimeType = freezed,Object? hash = freezed,Object? size = null,Object? uploadedAt = freezed,Object? uploadedTo = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? name = null,Object? description = freezed,Object? fileMeta = freezed,Object? userMeta = freezed,Object? sensitiveMarks = null,Object? mimeType = freezed,Object? hash = freezed,Object? size = null,Object? uploadedAt = freezed,Object? uploadedTo = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
|
||||
return _then(_SnCloudFile(
|
||||
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||
as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
||||
as String,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
|
||||
as String?,fileMeta: freezed == fileMeta ? _self._fileMeta : fileMeta // ignore: cast_nullable_to_non_nullable
|
||||
as Map<String, dynamic>?,userMeta: freezed == userMeta ? _self._userMeta : userMeta // ignore: cast_nullable_to_non_nullable
|
||||
as Map<String, dynamic>?,mimeType: freezed == mimeType ? _self.mimeType : mimeType // ignore: cast_nullable_to_non_nullable
|
||||
as Map<String, dynamic>?,sensitiveMarks: null == sensitiveMarks ? _self._sensitiveMarks : sensitiveMarks // ignore: cast_nullable_to_non_nullable
|
||||
as List<int>,mimeType: freezed == mimeType ? _self.mimeType : mimeType // ignore: cast_nullable_to_non_nullable
|
||||
as String?,hash: freezed == hash ? _self.hash : hash // ignore: cast_nullable_to_non_nullable
|
||||
as String?,size: null == size ? _self.size : size // ignore: cast_nullable_to_non_nullable
|
||||
as int,uploadedAt: freezed == uploadedAt ? _self.uploadedAt : uploadedAt // ignore: cast_nullable_to_non_nullable
|
||||
|
||||
@@ -10,12 +10,14 @@ _UniversalFile _$UniversalFileFromJson(Map<String, dynamic> json) =>
|
||||
_UniversalFile(
|
||||
data: json['data'],
|
||||
type: $enumDecode(_$UniversalFileTypeEnumMap, json['type']),
|
||||
isLink: json['is_link'] as bool? ?? false,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$UniversalFileToJson(_UniversalFile instance) =>
|
||||
<String, dynamic>{
|
||||
'data': instance.data,
|
||||
'type': _$UniversalFileTypeEnumMap[instance.type]!,
|
||||
'is_link': instance.isLink,
|
||||
};
|
||||
|
||||
const _$UniversalFileTypeEnumMap = {
|
||||
@@ -31,6 +33,11 @@ _SnCloudFile _$SnCloudFileFromJson(Map<String, dynamic> json) => _SnCloudFile(
|
||||
description: json['description'] as String?,
|
||||
fileMeta: json['file_meta'] as Map<String, dynamic>?,
|
||||
userMeta: json['user_meta'] as Map<String, dynamic>?,
|
||||
sensitiveMarks:
|
||||
(json['sensitive_marks'] as List<dynamic>?)
|
||||
?.map((e) => (e as num).toInt())
|
||||
.toList() ??
|
||||
const [],
|
||||
mimeType: json['mime_type'] as String?,
|
||||
hash: json['hash'] as String?,
|
||||
size: (json['size'] as num).toInt(),
|
||||
@@ -54,6 +61,7 @@ Map<String, dynamic> _$SnCloudFileToJson(_SnCloudFile instance) =>
|
||||
'description': instance.description,
|
||||
'file_meta': instance.fileMeta,
|
||||
'user_meta': instance.userMeta,
|
||||
'sensitive_marks': instance.sensitiveMarks,
|
||||
'mime_type': instance.mimeType,
|
||||
'hash': instance.hash,
|
||||
'size': instance.size,
|
||||
|
||||
108
lib/models/poll.dart
Normal file
108
lib/models/poll.dart
Normal file
@@ -0,0 +1,108 @@
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:island/models/publisher.dart';
|
||||
|
||||
part 'poll.freezed.dart';
|
||||
part 'poll.g.dart';
|
||||
|
||||
@freezed
|
||||
sealed class SnPollWithStats with _$SnPollWithStats {
|
||||
const factory SnPollWithStats({
|
||||
required Map<String, dynamic>? userAnswer,
|
||||
required Map<String, dynamic> stats,
|
||||
required String id,
|
||||
required List<SnPollQuestion> questions,
|
||||
String? title,
|
||||
String? description,
|
||||
DateTime? endedAt,
|
||||
required String publisherId,
|
||||
required DateTime createdAt,
|
||||
required DateTime updatedAt,
|
||||
DateTime? deletedAt,
|
||||
}) = _SnPollWithStats;
|
||||
|
||||
factory SnPollWithStats.fromJson(Map<String, dynamic> json) =>
|
||||
_$SnPollWithStatsFromJson(json);
|
||||
}
|
||||
|
||||
@freezed
|
||||
sealed class SnPoll with _$SnPoll {
|
||||
const factory SnPoll({
|
||||
required String id,
|
||||
required List<SnPollQuestion> questions,
|
||||
|
||||
String? title,
|
||||
String? description,
|
||||
|
||||
DateTime? endedAt,
|
||||
|
||||
required String publisherId,
|
||||
SnPublisher? publisher,
|
||||
|
||||
// ModelBase fields
|
||||
required DateTime createdAt,
|
||||
required DateTime updatedAt,
|
||||
DateTime? deletedAt,
|
||||
}) = _SnPoll;
|
||||
|
||||
factory SnPoll.fromJson(Map<String, dynamic> json) => _$SnPollFromJson(json);
|
||||
}
|
||||
|
||||
@freezed
|
||||
sealed class SnPollQuestion with _$SnPollQuestion {
|
||||
const factory SnPollQuestion({
|
||||
required String id,
|
||||
|
||||
required SnPollQuestionType type,
|
||||
List<SnPollOption>? options,
|
||||
|
||||
required String title,
|
||||
String? description,
|
||||
required int order,
|
||||
required bool isRequired,
|
||||
}) = _SnPollQuestion;
|
||||
|
||||
factory SnPollQuestion.fromJson(Map<String, dynamic> json) =>
|
||||
_$SnPollQuestionFromJson(json);
|
||||
}
|
||||
|
||||
@freezed
|
||||
sealed class SnPollOption with _$SnPollOption {
|
||||
const factory SnPollOption({
|
||||
required String id,
|
||||
required String label,
|
||||
String? description,
|
||||
required int order,
|
||||
}) = _SnPollOption;
|
||||
|
||||
factory SnPollOption.fromJson(Map<String, dynamic> json) =>
|
||||
_$SnPollOptionFromJson(json);
|
||||
}
|
||||
|
||||
enum SnPollQuestionType {
|
||||
@JsonValue(0)
|
||||
singleChoice,
|
||||
@JsonValue(1)
|
||||
multipleChoice,
|
||||
@JsonValue(2)
|
||||
yesNo,
|
||||
@JsonValue(3)
|
||||
rating,
|
||||
@JsonValue(4)
|
||||
freeText,
|
||||
}
|
||||
|
||||
@freezed
|
||||
sealed class SnPollAnswer with _$SnPollAnswer {
|
||||
const factory SnPollAnswer({
|
||||
required String id,
|
||||
required Map<String, dynamic> answer,
|
||||
required String accountId,
|
||||
required String pollId,
|
||||
required DateTime createdAt,
|
||||
required DateTime updatedAt,
|
||||
required DateTime? deletedAt,
|
||||
}) = _SnPollAnswer;
|
||||
|
||||
factory SnPollAnswer.fromJson(Map<String, dynamic> json) =>
|
||||
_$SnPollAnswerFromJson(json);
|
||||
}
|
||||
1467
lib/models/poll.freezed.dart
Normal file
1467
lib/models/poll.freezed.dart
Normal file
File diff suppressed because it is too large
Load Diff
158
lib/models/poll.g.dart
Normal file
158
lib/models/poll.g.dart
Normal file
@@ -0,0 +1,158 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'poll.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
_SnPollWithStats _$SnPollWithStatsFromJson(Map<String, dynamic> json) =>
|
||||
_SnPollWithStats(
|
||||
userAnswer: json['user_answer'] as Map<String, dynamic>?,
|
||||
stats: json['stats'] as Map<String, dynamic>,
|
||||
id: json['id'] as String,
|
||||
questions:
|
||||
(json['questions'] as List<dynamic>)
|
||||
.map((e) => SnPollQuestion.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
title: json['title'] as String?,
|
||||
description: json['description'] as String?,
|
||||
endedAt:
|
||||
json['ended_at'] == null
|
||||
? null
|
||||
: DateTime.parse(json['ended_at'] as String),
|
||||
publisherId: json['publisher_id'] as String,
|
||||
createdAt: DateTime.parse(json['created_at'] as String),
|
||||
updatedAt: DateTime.parse(json['updated_at'] as String),
|
||||
deletedAt:
|
||||
json['deleted_at'] == null
|
||||
? null
|
||||
: DateTime.parse(json['deleted_at'] as String),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$SnPollWithStatsToJson(_SnPollWithStats instance) =>
|
||||
<String, dynamic>{
|
||||
'user_answer': instance.userAnswer,
|
||||
'stats': instance.stats,
|
||||
'id': instance.id,
|
||||
'questions': instance.questions.map((e) => e.toJson()).toList(),
|
||||
'title': instance.title,
|
||||
'description': instance.description,
|
||||
'ended_at': instance.endedAt?.toIso8601String(),
|
||||
'publisher_id': instance.publisherId,
|
||||
'created_at': instance.createdAt.toIso8601String(),
|
||||
'updated_at': instance.updatedAt.toIso8601String(),
|
||||
'deleted_at': instance.deletedAt?.toIso8601String(),
|
||||
};
|
||||
|
||||
_SnPoll _$SnPollFromJson(Map<String, dynamic> json) => _SnPoll(
|
||||
id: json['id'] as String,
|
||||
questions:
|
||||
(json['questions'] as List<dynamic>)
|
||||
.map((e) => SnPollQuestion.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
title: json['title'] as String?,
|
||||
description: json['description'] as String?,
|
||||
endedAt:
|
||||
json['ended_at'] == null
|
||||
? null
|
||||
: DateTime.parse(json['ended_at'] as String),
|
||||
publisherId: json['publisher_id'] as String,
|
||||
publisher:
|
||||
json['publisher'] == null
|
||||
? null
|
||||
: SnPublisher.fromJson(json['publisher'] as Map<String, dynamic>),
|
||||
createdAt: DateTime.parse(json['created_at'] as String),
|
||||
updatedAt: DateTime.parse(json['updated_at'] as String),
|
||||
deletedAt:
|
||||
json['deleted_at'] == null
|
||||
? null
|
||||
: DateTime.parse(json['deleted_at'] as String),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$SnPollToJson(_SnPoll instance) => <String, dynamic>{
|
||||
'id': instance.id,
|
||||
'questions': instance.questions.map((e) => e.toJson()).toList(),
|
||||
'title': instance.title,
|
||||
'description': instance.description,
|
||||
'ended_at': instance.endedAt?.toIso8601String(),
|
||||
'publisher_id': instance.publisherId,
|
||||
'publisher': instance.publisher?.toJson(),
|
||||
'created_at': instance.createdAt.toIso8601String(),
|
||||
'updated_at': instance.updatedAt.toIso8601String(),
|
||||
'deleted_at': instance.deletedAt?.toIso8601String(),
|
||||
};
|
||||
|
||||
_SnPollQuestion _$SnPollQuestionFromJson(Map<String, dynamic> json) =>
|
||||
_SnPollQuestion(
|
||||
id: json['id'] as String,
|
||||
type: $enumDecode(_$SnPollQuestionTypeEnumMap, json['type']),
|
||||
options:
|
||||
(json['options'] as List<dynamic>?)
|
||||
?.map((e) => SnPollOption.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
title: json['title'] as String,
|
||||
description: json['description'] as String?,
|
||||
order: (json['order'] as num).toInt(),
|
||||
isRequired: json['is_required'] as bool,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$SnPollQuestionToJson(_SnPollQuestion instance) =>
|
||||
<String, dynamic>{
|
||||
'id': instance.id,
|
||||
'type': _$SnPollQuestionTypeEnumMap[instance.type]!,
|
||||
'options': instance.options?.map((e) => e.toJson()).toList(),
|
||||
'title': instance.title,
|
||||
'description': instance.description,
|
||||
'order': instance.order,
|
||||
'is_required': instance.isRequired,
|
||||
};
|
||||
|
||||
const _$SnPollQuestionTypeEnumMap = {
|
||||
SnPollQuestionType.singleChoice: 0,
|
||||
SnPollQuestionType.multipleChoice: 1,
|
||||
SnPollQuestionType.yesNo: 2,
|
||||
SnPollQuestionType.rating: 3,
|
||||
SnPollQuestionType.freeText: 4,
|
||||
};
|
||||
|
||||
_SnPollOption _$SnPollOptionFromJson(Map<String, dynamic> json) =>
|
||||
_SnPollOption(
|
||||
id: json['id'] as String,
|
||||
label: json['label'] as String,
|
||||
description: json['description'] as String?,
|
||||
order: (json['order'] as num).toInt(),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$SnPollOptionToJson(_SnPollOption instance) =>
|
||||
<String, dynamic>{
|
||||
'id': instance.id,
|
||||
'label': instance.label,
|
||||
'description': instance.description,
|
||||
'order': instance.order,
|
||||
};
|
||||
|
||||
_SnPollAnswer _$SnPollAnswerFromJson(Map<String, dynamic> json) =>
|
||||
_SnPollAnswer(
|
||||
id: json['id'] as String,
|
||||
answer: json['answer'] as Map<String, dynamic>,
|
||||
accountId: json['account_id'] as String,
|
||||
pollId: json['poll_id'] as String,
|
||||
createdAt: DateTime.parse(json['created_at'] as String),
|
||||
updatedAt: DateTime.parse(json['updated_at'] as String),
|
||||
deletedAt:
|
||||
json['deleted_at'] == null
|
||||
? null
|
||||
: DateTime.parse(json['deleted_at'] as String),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$SnPollAnswerToJson(_SnPollAnswer instance) =>
|
||||
<String, dynamic>{
|
||||
'id': instance.id,
|
||||
'answer': instance.answer,
|
||||
'account_id': instance.accountId,
|
||||
'poll_id': instance.pollId,
|
||||
'created_at': instance.createdAt.toIso8601String(),
|
||||
'updated_at': instance.updatedAt.toIso8601String(),
|
||||
'deleted_at': instance.deletedAt?.toIso8601String(),
|
||||
};
|
||||
@@ -34,9 +34,10 @@ sealed class SnPost with _$SnPost {
|
||||
@Default([]) List<SnCloudFile> attachments,
|
||||
required SnPublisher publisher,
|
||||
@Default({}) Map<String, int> reactionsCount,
|
||||
@Default({}) Map<String, bool> reactionsMade,
|
||||
@Default([]) List<dynamic> reactions,
|
||||
@Default([]) List<PostTag> tags,
|
||||
@Default([]) List<PostCategory> categories,
|
||||
@Default([]) List<SnPostTag> tags,
|
||||
@Default([]) List<SnPostCategory> categories,
|
||||
@Default([]) List<dynamic> collections,
|
||||
@Default(null) DateTime? createdAt,
|
||||
@Default(null) DateTime? updatedAt,
|
||||
@@ -77,6 +78,13 @@ sealed class SnSubscriptionStatus with _$SnSubscriptionStatus {
|
||||
sealed class ReactInfo with _$ReactInfo {
|
||||
const factory ReactInfo({required String icon, required int attitude}) =
|
||||
_ReactInfo;
|
||||
|
||||
static String getTranslationKey(String templateKey) {
|
||||
final parts = templateKey.split('_');
|
||||
final camelCase =
|
||||
parts.map((p) => p[0].toUpperCase() + p.substring(1)).join();
|
||||
return 'reaction$camelCase';
|
||||
}
|
||||
}
|
||||
|
||||
const Map<String, ReactInfo> kReactionTemplates = {
|
||||
|
||||
@@ -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; List<dynamic> get reactions; List<PostTag> get tags; List<PostCategory> get categories; List<dynamic> get collections; DateTime? get createdAt; DateTime? get updatedAt; DateTime? get deletedAt; bool get isTruncated;
|
||||
String get id; String? get title; String? get description; String? get language; DateTime? get editedAt; DateTime? get publishedAt; int get visibility; String? get content; int get type; Map<String, dynamic>? get meta; int get viewsUnique; int get viewsTotal; int get upvotes; int get downvotes; int get repliesCount; String? get threadedPostId; SnPost? get threadedPost; String? get repliedPostId; SnPost? get repliedPost; String? get forwardedPostId; SnPost? get forwardedPost; List<SnCloudFile> get attachments; SnPublisher get publisher; Map<String, int> get reactionsCount; Map<String, bool> get reactionsMade; List<dynamic> get reactions; List<SnPostTag> get tags; List<SnPostCategory> get categories; List<dynamic> get collections; DateTime? get createdAt; DateTime? get updatedAt; DateTime? get deletedAt; bool get isTruncated;
|
||||
/// Create a copy of SnPost
|
||||
/// 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.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.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));
|
||||
}
|
||||
|
||||
@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(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,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]);
|
||||
|
||||
@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, 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, 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)';
|
||||
}
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ abstract mixin class $SnPostCopyWith<$Res> {
|
||||
factory $SnPostCopyWith(SnPost value, $Res Function(SnPost) _then) = _$SnPostCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
String id, String? title, String? description, String? language, DateTime? editedAt, DateTime? publishedAt, int visibility, String? content, int type, Map<String, dynamic>? meta, int viewsUnique, int viewsTotal, int upvotes, int downvotes, int repliesCount, String? threadedPostId, SnPost? threadedPost, String? repliedPostId, SnPost? repliedPost, String? forwardedPostId, SnPost? forwardedPost, List<SnCloudFile> attachments, SnPublisher publisher, Map<String, int> reactionsCount, List<dynamic> reactions, List<PostTag> tags, List<PostCategory> categories, List<dynamic> collections, DateTime? createdAt, DateTime? updatedAt, DateTime? deletedAt, bool isTruncated
|
||||
String id, String? title, String? description, String? language, DateTime? editedAt, DateTime? publishedAt, int visibility, String? content, int type, Map<String, dynamic>? meta, int viewsUnique, int viewsTotal, int upvotes, int downvotes, int repliesCount, String? threadedPostId, SnPost? threadedPost, String? repliedPostId, SnPost? repliedPost, String? forwardedPostId, SnPost? forwardedPost, List<SnCloudFile> attachments, SnPublisher publisher, Map<String, int> reactionsCount, Map<String, bool> reactionsMade, List<dynamic> reactions, List<SnPostTag> tags, List<SnPostCategory> categories, List<dynamic> collections, DateTime? createdAt, DateTime? updatedAt, DateTime? deletedAt, bool isTruncated
|
||||
});
|
||||
|
||||
|
||||
@@ -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? 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? 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,}) {
|
||||
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
|
||||
@@ -91,10 +91,11 @@ as String?,forwardedPost: freezed == forwardedPost ? _self.forwardedPost : forwa
|
||||
as SnPost?,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>,reactions: null == reactions ? _self.reactions : reactions // ignore: cast_nullable_to_non_nullable
|
||||
as Map<String, int>,reactionsMade: null == reactionsMade ? _self.reactionsMade : reactionsMade // ignore: cast_nullable_to_non_nullable
|
||||
as Map<String, bool>,reactions: null == reactions ? _self.reactions : reactions // ignore: cast_nullable_to_non_nullable
|
||||
as List<dynamic>,tags: null == tags ? _self.tags : tags // ignore: cast_nullable_to_non_nullable
|
||||
as List<PostTag>,categories: null == categories ? _self.categories : categories // ignore: cast_nullable_to_non_nullable
|
||||
as List<PostCategory>,collections: null == collections ? _self.collections : collections // ignore: cast_nullable_to_non_nullable
|
||||
as List<SnPostTag>,categories: null == categories ? _self.categories : categories // ignore: cast_nullable_to_non_nullable
|
||||
as List<SnPostCategory>,collections: null == collections ? _self.collections : collections // ignore: cast_nullable_to_non_nullable
|
||||
as List<dynamic>,createdAt: freezed == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime?,updatedAt: freezed == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime?,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
|
||||
@@ -226,10 +227,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, List<dynamic> reactions, List<PostTag> tags, List<PostCategory> categories, List<dynamic> collections, DateTime? createdAt, DateTime? updatedAt, DateTime? deletedAt, bool isTruncated)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, String? title, String? description, String? language, DateTime? editedAt, DateTime? publishedAt, int visibility, String? content, int type, Map<String, dynamic>? meta, int viewsUnique, int viewsTotal, int upvotes, int downvotes, int repliesCount, String? threadedPostId, SnPost? threadedPost, String? repliedPostId, SnPost? repliedPost, String? forwardedPostId, SnPost? forwardedPost, List<SnCloudFile> attachments, SnPublisher publisher, Map<String, int> reactionsCount, Map<String, bool> reactionsMade, List<dynamic> reactions, List<SnPostTag> tags, List<SnPostCategory> categories, List<dynamic> collections, DateTime? createdAt, DateTime? updatedAt, DateTime? deletedAt, bool isTruncated)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
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.reactions,_that.tags,_that.categories,_that.collections,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.isTruncated);case _:
|
||||
return $default(_that.id,_that.title,_that.description,_that.language,_that.editedAt,_that.publishedAt,_that.visibility,_that.content,_that.type,_that.meta,_that.viewsUnique,_that.viewsTotal,_that.upvotes,_that.downvotes,_that.repliesCount,_that.threadedPostId,_that.threadedPost,_that.repliedPostId,_that.repliedPost,_that.forwardedPostId,_that.forwardedPost,_that.attachments,_that.publisher,_that.reactionsCount,_that.reactionsMade,_that.reactions,_that.tags,_that.categories,_that.collections,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.isTruncated);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
@@ -247,10 +248,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, List<dynamic> reactions, List<PostTag> tags, List<PostCategory> categories, List<dynamic> collections, DateTime? createdAt, DateTime? updatedAt, DateTime? deletedAt, bool isTruncated) $default,) {final _that = this;
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, String? title, String? description, String? language, DateTime? editedAt, DateTime? publishedAt, int visibility, String? content, int type, Map<String, dynamic>? meta, int viewsUnique, int viewsTotal, int upvotes, int downvotes, int repliesCount, String? threadedPostId, SnPost? threadedPost, String? repliedPostId, SnPost? repliedPost, String? forwardedPostId, SnPost? forwardedPost, List<SnCloudFile> attachments, SnPublisher publisher, Map<String, int> reactionsCount, Map<String, bool> reactionsMade, List<dynamic> reactions, List<SnPostTag> tags, List<SnPostCategory> categories, List<dynamic> collections, DateTime? createdAt, DateTime? updatedAt, DateTime? deletedAt, bool isTruncated) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
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.reactions,_that.tags,_that.categories,_that.collections,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.isTruncated);}
|
||||
return $default(_that.id,_that.title,_that.description,_that.language,_that.editedAt,_that.publishedAt,_that.visibility,_that.content,_that.type,_that.meta,_that.viewsUnique,_that.viewsTotal,_that.upvotes,_that.downvotes,_that.repliesCount,_that.threadedPostId,_that.threadedPost,_that.repliedPostId,_that.repliedPost,_that.forwardedPostId,_that.forwardedPost,_that.attachments,_that.publisher,_that.reactionsCount,_that.reactionsMade,_that.reactions,_that.tags,_that.categories,_that.collections,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.isTruncated);}
|
||||
}
|
||||
/// A variant of `when` that fallback to returning `null`
|
||||
///
|
||||
@@ -264,10 +265,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, List<dynamic> reactions, List<PostTag> tags, List<PostCategory> categories, List<dynamic> collections, DateTime? createdAt, DateTime? updatedAt, DateTime? deletedAt, bool isTruncated)? $default,) {final _that = this;
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, String? title, String? description, String? language, DateTime? editedAt, DateTime? publishedAt, int visibility, String? content, int type, Map<String, dynamic>? meta, int viewsUnique, int viewsTotal, int upvotes, int downvotes, int repliesCount, String? threadedPostId, SnPost? threadedPost, String? repliedPostId, SnPost? repliedPost, String? forwardedPostId, SnPost? forwardedPost, List<SnCloudFile> attachments, SnPublisher publisher, Map<String, int> reactionsCount, Map<String, bool> reactionsMade, List<dynamic> reactions, List<SnPostTag> tags, List<SnPostCategory> categories, List<dynamic> collections, DateTime? createdAt, DateTime? updatedAt, DateTime? deletedAt, bool isTruncated)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
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.reactions,_that.tags,_that.categories,_that.collections,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.isTruncated);case _:
|
||||
return $default(_that.id,_that.title,_that.description,_that.language,_that.editedAt,_that.publishedAt,_that.visibility,_that.content,_that.type,_that.meta,_that.viewsUnique,_that.viewsTotal,_that.upvotes,_that.downvotes,_that.repliesCount,_that.threadedPostId,_that.threadedPost,_that.repliedPostId,_that.repliedPost,_that.forwardedPostId,_that.forwardedPost,_that.attachments,_that.publisher,_that.reactionsCount,_that.reactionsMade,_that.reactions,_that.tags,_that.categories,_that.collections,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.isTruncated);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
@@ -279,7 +280,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 List<dynamic> reactions = const [], final List<PostTag> tags = const [], final List<PostCategory> categories = const [], final List<dynamic> collections = const [], this.createdAt = null, this.updatedAt = null, this.deletedAt, this.isTruncated = false}): _meta = meta,_attachments = attachments,_reactionsCount = reactionsCount,_reactions = reactions,_tags = tags,_categories = categories,_collections = collections;
|
||||
const _SnPost({required this.id, this.title, this.description, this.language, this.editedAt, this.publishedAt = null, this.visibility = 0, this.content, this.type = 0, final Map<String, dynamic>? meta, this.viewsUnique = 0, this.viewsTotal = 0, this.upvotes = 0, this.downvotes = 0, this.repliesCount = 0, this.threadedPostId, this.threadedPost, this.repliedPostId, this.repliedPost, this.forwardedPostId, this.forwardedPost, final List<SnCloudFile> attachments = const [], required this.publisher, final Map<String, int> reactionsCount = const {}, final Map<String, bool> reactionsMade = const {}, final List<dynamic> reactions = const [], final List<SnPostTag> tags = const [], final List<SnPostCategory> categories = const [], final List<dynamic> collections = const [], this.createdAt = null, this.updatedAt = null, this.deletedAt, this.isTruncated = false}): _meta = meta,_attachments = attachments,_reactionsCount = reactionsCount,_reactionsMade = reactionsMade,_reactions = reactions,_tags = tags,_categories = categories,_collections = collections;
|
||||
factory _SnPost.fromJson(Map<String, dynamic> json) => _$SnPostFromJson(json);
|
||||
|
||||
@override final String id;
|
||||
@@ -326,6 +327,13 @@ class _SnPost implements SnPost {
|
||||
return EqualUnmodifiableMapView(_reactionsCount);
|
||||
}
|
||||
|
||||
final Map<String, bool> _reactionsMade;
|
||||
@override@JsonKey() Map<String, bool> get reactionsMade {
|
||||
if (_reactionsMade is EqualUnmodifiableMapView) return _reactionsMade;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableMapView(_reactionsMade);
|
||||
}
|
||||
|
||||
final List<dynamic> _reactions;
|
||||
@override@JsonKey() List<dynamic> get reactions {
|
||||
if (_reactions is EqualUnmodifiableListView) return _reactions;
|
||||
@@ -333,15 +341,15 @@ class _SnPost implements SnPost {
|
||||
return EqualUnmodifiableListView(_reactions);
|
||||
}
|
||||
|
||||
final List<PostTag> _tags;
|
||||
@override@JsonKey() List<PostTag> get tags {
|
||||
final List<SnPostTag> _tags;
|
||||
@override@JsonKey() List<SnPostTag> get tags {
|
||||
if (_tags is EqualUnmodifiableListView) return _tags;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(_tags);
|
||||
}
|
||||
|
||||
final List<PostCategory> _categories;
|
||||
@override@JsonKey() List<PostCategory> get categories {
|
||||
final List<SnPostCategory> _categories;
|
||||
@override@JsonKey() List<SnPostCategory> get categories {
|
||||
if (_categories is EqualUnmodifiableListView) return _categories;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(_categories);
|
||||
@@ -372,16 +380,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._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.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));
|
||||
}
|
||||
|
||||
@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(_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,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]);
|
||||
|
||||
@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, 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, 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)';
|
||||
}
|
||||
|
||||
|
||||
@@ -392,7 +400,7 @@ abstract mixin class _$SnPostCopyWith<$Res> implements $SnPostCopyWith<$Res> {
|
||||
factory _$SnPostCopyWith(_SnPost value, $Res Function(_SnPost) _then) = __$SnPostCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
String id, String? title, String? description, String? language, DateTime? editedAt, DateTime? publishedAt, int visibility, String? content, int type, Map<String, dynamic>? meta, int viewsUnique, int viewsTotal, int upvotes, int downvotes, int repliesCount, String? threadedPostId, SnPost? threadedPost, String? repliedPostId, SnPost? repliedPost, String? forwardedPostId, SnPost? forwardedPost, List<SnCloudFile> attachments, SnPublisher publisher, Map<String, int> reactionsCount, List<dynamic> reactions, List<PostTag> tags, List<PostCategory> categories, List<dynamic> collections, DateTime? createdAt, DateTime? updatedAt, DateTime? deletedAt, bool isTruncated
|
||||
String id, String? title, String? description, String? language, DateTime? editedAt, DateTime? publishedAt, int visibility, String? content, int type, Map<String, dynamic>? meta, int viewsUnique, int viewsTotal, int upvotes, int downvotes, int repliesCount, String? threadedPostId, SnPost? threadedPost, String? repliedPostId, SnPost? repliedPost, String? forwardedPostId, SnPost? forwardedPost, List<SnCloudFile> attachments, SnPublisher publisher, Map<String, int> reactionsCount, Map<String, bool> reactionsMade, List<dynamic> reactions, List<SnPostTag> tags, List<SnPostCategory> categories, List<dynamic> collections, DateTime? createdAt, DateTime? updatedAt, DateTime? deletedAt, bool isTruncated
|
||||
});
|
||||
|
||||
|
||||
@@ -409,7 +417,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? 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? 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,}) {
|
||||
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
|
||||
@@ -435,10 +443,11 @@ as String?,forwardedPost: freezed == forwardedPost ? _self.forwardedPost : forwa
|
||||
as SnPost?,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>,reactions: null == reactions ? _self._reactions : reactions // ignore: cast_nullable_to_non_nullable
|
||||
as Map<String, int>,reactionsMade: null == reactionsMade ? _self._reactionsMade : reactionsMade // ignore: cast_nullable_to_non_nullable
|
||||
as Map<String, bool>,reactions: null == reactions ? _self._reactions : reactions // ignore: cast_nullable_to_non_nullable
|
||||
as List<dynamic>,tags: null == tags ? _self._tags : tags // ignore: cast_nullable_to_non_nullable
|
||||
as List<PostTag>,categories: null == categories ? _self._categories : categories // ignore: cast_nullable_to_non_nullable
|
||||
as List<PostCategory>,collections: null == collections ? _self._collections : collections // ignore: cast_nullable_to_non_nullable
|
||||
as List<SnPostTag>,categories: null == categories ? _self._categories : categories // ignore: cast_nullable_to_non_nullable
|
||||
as List<SnPostCategory>,collections: null == collections ? _self._collections : collections // ignore: cast_nullable_to_non_nullable
|
||||
as List<dynamic>,createdAt: freezed == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime?,updatedAt: freezed == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime?,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
|
||||
|
||||
@@ -54,15 +54,20 @@ _SnPost _$SnPostFromJson(Map<String, dynamic> json) => _SnPost(
|
||||
(k, e) => MapEntry(k, (e as num).toInt()),
|
||||
) ??
|
||||
const {},
|
||||
reactionsMade:
|
||||
(json['reactions_made'] as Map<String, dynamic>?)?.map(
|
||||
(k, e) => MapEntry(k, e as bool),
|
||||
) ??
|
||||
const {},
|
||||
reactions: json['reactions'] as List<dynamic>? ?? const [],
|
||||
tags:
|
||||
(json['tags'] as List<dynamic>?)
|
||||
?.map((e) => PostTag.fromJson(e as Map<String, dynamic>))
|
||||
?.map((e) => SnPostTag.fromJson(e as Map<String, dynamic>))
|
||||
.toList() ??
|
||||
const [],
|
||||
categories:
|
||||
(json['categories'] as List<dynamic>?)
|
||||
?.map((e) => PostCategory.fromJson(e as Map<String, dynamic>))
|
||||
?.map((e) => SnPostCategory.fromJson(e as Map<String, dynamic>))
|
||||
.toList() ??
|
||||
const [],
|
||||
collections: json['collections'] as List<dynamic>? ?? const [],
|
||||
@@ -106,6 +111,7 @@ Map<String, dynamic> _$SnPostToJson(_SnPost instance) => <String, dynamic>{
|
||||
'attachments': instance.attachments.map((e) => e.toJson()).toList(),
|
||||
'publisher': instance.publisher.toJson(),
|
||||
'reactions_count': instance.reactionsCount,
|
||||
'reactions_made': instance.reactionsMade,
|
||||
'reactions': instance.reactions,
|
||||
'tags': instance.tags.map((e) => e.toJson()).toList(),
|
||||
'categories': instance.categories.map((e) => e.toJson()).toList(),
|
||||
|
||||
@@ -1,19 +1,30 @@
|
||||
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:island/models/post.dart';
|
||||
import 'package:island/services/text.dart';
|
||||
|
||||
part 'post_category.freezed.dart';
|
||||
part 'post_category.g.dart';
|
||||
|
||||
@freezed
|
||||
sealed class PostCategory with _$PostCategory {
|
||||
const factory PostCategory({
|
||||
sealed class SnPostCategory with _$SnPostCategory {
|
||||
const SnPostCategory._();
|
||||
|
||||
const factory SnPostCategory({
|
||||
required String id,
|
||||
required String slug,
|
||||
String? name,
|
||||
@Default([]) List<SnPost> posts,
|
||||
}) = _PostCategory;
|
||||
}) = _SnPostCategory;
|
||||
|
||||
factory PostCategory.fromJson(Map<String, dynamic> json) =>
|
||||
_$PostCategoryFromJson(json);
|
||||
factory SnPostCategory.fromJson(Map<String, dynamic> json) =>
|
||||
_$SnPostCategoryFromJson(json);
|
||||
|
||||
String get categoryDisplayTitle {
|
||||
final capitalizedSlug = slug.capitalizeEachWord();
|
||||
if ('postCategory$capitalizedSlug'.trExists()) {
|
||||
return 'postCategory$capitalizedSlug'.tr();
|
||||
}
|
||||
return name ?? slug;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,22 +13,22 @@ part of 'post_category.dart';
|
||||
T _$identity<T>(T value) => value;
|
||||
|
||||
/// @nodoc
|
||||
mixin _$PostCategory {
|
||||
mixin _$SnPostCategory {
|
||||
|
||||
String get id; String get slug; String? get name; List<SnPost> get posts;
|
||||
/// Create a copy of PostCategory
|
||||
/// Create a copy of SnPostCategory
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
$PostCategoryCopyWith<PostCategory> get copyWith => _$PostCategoryCopyWithImpl<PostCategory>(this as PostCategory, _$identity);
|
||||
$SnPostCategoryCopyWith<SnPostCategory> get copyWith => _$SnPostCategoryCopyWithImpl<SnPostCategory>(this as SnPostCategory, _$identity);
|
||||
|
||||
/// Serializes this PostCategory to a JSON map.
|
||||
/// Serializes this SnPostCategory to a JSON map.
|
||||
Map<String, dynamic> toJson();
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is PostCategory&&(identical(other.id, id) || other.id == id)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.name, name) || other.name == name)&&const DeepCollectionEquality().equals(other.posts, posts));
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnPostCategory&&(identical(other.id, id) || other.id == id)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.name, name) || other.name == name)&&const DeepCollectionEquality().equals(other.posts, posts));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@@ -37,15 +37,15 @@ int get hashCode => Object.hash(runtimeType,id,slug,name,const DeepCollectionEqu
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'PostCategory(id: $id, slug: $slug, name: $name, posts: $posts)';
|
||||
return 'SnPostCategory(id: $id, slug: $slug, name: $name, posts: $posts)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class $PostCategoryCopyWith<$Res> {
|
||||
factory $PostCategoryCopyWith(PostCategory value, $Res Function(PostCategory) _then) = _$PostCategoryCopyWithImpl;
|
||||
abstract mixin class $SnPostCategoryCopyWith<$Res> {
|
||||
factory $SnPostCategoryCopyWith(SnPostCategory value, $Res Function(SnPostCategory) _then) = _$SnPostCategoryCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
String id, String slug, String? name, List<SnPost> posts
|
||||
@@ -56,14 +56,14 @@ $Res call({
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class _$PostCategoryCopyWithImpl<$Res>
|
||||
implements $PostCategoryCopyWith<$Res> {
|
||||
_$PostCategoryCopyWithImpl(this._self, this._then);
|
||||
class _$SnPostCategoryCopyWithImpl<$Res>
|
||||
implements $SnPostCategoryCopyWith<$Res> {
|
||||
_$SnPostCategoryCopyWithImpl(this._self, this._then);
|
||||
|
||||
final PostCategory _self;
|
||||
final $Res Function(PostCategory) _then;
|
||||
final SnPostCategory _self;
|
||||
final $Res Function(SnPostCategory) _then;
|
||||
|
||||
/// Create a copy of PostCategory
|
||||
/// Create a copy of SnPostCategory
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? slug = null,Object? name = freezed,Object? posts = null,}) {
|
||||
return _then(_self.copyWith(
|
||||
@@ -78,8 +78,8 @@ as List<SnPost>,
|
||||
}
|
||||
|
||||
|
||||
/// Adds pattern-matching-related methods to [PostCategory].
|
||||
extension PostCategoryPatterns on PostCategory {
|
||||
/// Adds pattern-matching-related methods to [SnPostCategory].
|
||||
extension SnPostCategoryPatterns on SnPostCategory {
|
||||
/// A variant of `map` that fallback to returning `orElse`.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
@@ -92,10 +92,10 @@ extension PostCategoryPatterns on PostCategory {
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _PostCategory value)? $default,{required TResult orElse(),}){
|
||||
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _SnPostCategory value)? $default,{required TResult orElse(),}){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _PostCategory() when $default != null:
|
||||
case _SnPostCategory() when $default != null:
|
||||
return $default(_that);case _:
|
||||
return orElse();
|
||||
|
||||
@@ -114,10 +114,10 @@ return $default(_that);case _:
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _PostCategory value) $default,){
|
||||
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _SnPostCategory value) $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _PostCategory():
|
||||
case _SnPostCategory():
|
||||
return $default(_that);}
|
||||
}
|
||||
/// A variant of `map` that fallback to returning `null`.
|
||||
@@ -132,10 +132,10 @@ return $default(_that);}
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _PostCategory value)? $default,){
|
||||
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _SnPostCategory value)? $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _PostCategory() when $default != null:
|
||||
case _SnPostCategory() when $default != null:
|
||||
return $default(_that);case _:
|
||||
return null;
|
||||
|
||||
@@ -155,7 +155,7 @@ return $default(_that);case _:
|
||||
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, String slug, String? name, List<SnPost> posts)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _PostCategory() when $default != null:
|
||||
case _SnPostCategory() when $default != null:
|
||||
return $default(_that.id,_that.slug,_that.name,_that.posts);case _:
|
||||
return orElse();
|
||||
|
||||
@@ -176,7 +176,7 @@ return $default(_that.id,_that.slug,_that.name,_that.posts);case _:
|
||||
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, String slug, String? name, List<SnPost> posts) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _PostCategory():
|
||||
case _SnPostCategory():
|
||||
return $default(_that.id,_that.slug,_that.name,_that.posts);}
|
||||
}
|
||||
/// A variant of `when` that fallback to returning `null`
|
||||
@@ -193,7 +193,7 @@ return $default(_that.id,_that.slug,_that.name,_that.posts);}
|
||||
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, String slug, String? name, List<SnPost> posts)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _PostCategory() when $default != null:
|
||||
case _SnPostCategory() when $default != null:
|
||||
return $default(_that.id,_that.slug,_that.name,_that.posts);case _:
|
||||
return null;
|
||||
|
||||
@@ -205,9 +205,9 @@ return $default(_that.id,_that.slug,_that.name,_that.posts);case _:
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
|
||||
class _PostCategory implements PostCategory {
|
||||
const _PostCategory({required this.id, required this.slug, this.name, final List<SnPost> posts = const []}): _posts = posts;
|
||||
factory _PostCategory.fromJson(Map<String, dynamic> json) => _$PostCategoryFromJson(json);
|
||||
class _SnPostCategory extends SnPostCategory {
|
||||
const _SnPostCategory({required this.id, required this.slug, this.name, final List<SnPost> posts = const []}): _posts = posts,super._();
|
||||
factory _SnPostCategory.fromJson(Map<String, dynamic> json) => _$SnPostCategoryFromJson(json);
|
||||
|
||||
@override final String id;
|
||||
@override final String slug;
|
||||
@@ -220,20 +220,20 @@ class _PostCategory implements PostCategory {
|
||||
}
|
||||
|
||||
|
||||
/// Create a copy of PostCategory
|
||||
/// Create a copy of SnPostCategory
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
_$PostCategoryCopyWith<_PostCategory> get copyWith => __$PostCategoryCopyWithImpl<_PostCategory>(this, _$identity);
|
||||
_$SnPostCategoryCopyWith<_SnPostCategory> get copyWith => __$SnPostCategoryCopyWithImpl<_SnPostCategory>(this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$PostCategoryToJson(this, );
|
||||
return _$SnPostCategoryToJson(this, );
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _PostCategory&&(identical(other.id, id) || other.id == id)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.name, name) || other.name == name)&&const DeepCollectionEquality().equals(other._posts, _posts));
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnPostCategory&&(identical(other.id, id) || other.id == id)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.name, name) || other.name == name)&&const DeepCollectionEquality().equals(other._posts, _posts));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@@ -242,15 +242,15 @@ int get hashCode => Object.hash(runtimeType,id,slug,name,const DeepCollectionEqu
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'PostCategory(id: $id, slug: $slug, name: $name, posts: $posts)';
|
||||
return 'SnPostCategory(id: $id, slug: $slug, name: $name, posts: $posts)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class _$PostCategoryCopyWith<$Res> implements $PostCategoryCopyWith<$Res> {
|
||||
factory _$PostCategoryCopyWith(_PostCategory value, $Res Function(_PostCategory) _then) = __$PostCategoryCopyWithImpl;
|
||||
abstract mixin class _$SnPostCategoryCopyWith<$Res> implements $SnPostCategoryCopyWith<$Res> {
|
||||
factory _$SnPostCategoryCopyWith(_SnPostCategory value, $Res Function(_SnPostCategory) _then) = __$SnPostCategoryCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
String id, String slug, String? name, List<SnPost> posts
|
||||
@@ -261,17 +261,17 @@ $Res call({
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class __$PostCategoryCopyWithImpl<$Res>
|
||||
implements _$PostCategoryCopyWith<$Res> {
|
||||
__$PostCategoryCopyWithImpl(this._self, this._then);
|
||||
class __$SnPostCategoryCopyWithImpl<$Res>
|
||||
implements _$SnPostCategoryCopyWith<$Res> {
|
||||
__$SnPostCategoryCopyWithImpl(this._self, this._then);
|
||||
|
||||
final _PostCategory _self;
|
||||
final $Res Function(_PostCategory) _then;
|
||||
final _SnPostCategory _self;
|
||||
final $Res Function(_SnPostCategory) _then;
|
||||
|
||||
/// Create a copy of PostCategory
|
||||
/// Create a copy of SnPostCategory
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? slug = null,Object? name = freezed,Object? posts = null,}) {
|
||||
return _then(_PostCategory(
|
||||
return _then(_SnPostCategory(
|
||||
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||
as String,slug: null == slug ? _self.slug : slug // ignore: cast_nullable_to_non_nullable
|
||||
as String,name: freezed == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
||||
|
||||
@@ -6,8 +6,8 @@ part of 'post_category.dart';
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
_PostCategory _$PostCategoryFromJson(Map<String, dynamic> json) =>
|
||||
_PostCategory(
|
||||
_SnPostCategory _$SnPostCategoryFromJson(Map<String, dynamic> json) =>
|
||||
_SnPostCategory(
|
||||
id: json['id'] as String,
|
||||
slug: json['slug'] as String,
|
||||
name: json['name'] as String?,
|
||||
@@ -18,7 +18,7 @@ _PostCategory _$PostCategoryFromJson(Map<String, dynamic> json) =>
|
||||
const [],
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$PostCategoryToJson(_PostCategory instance) =>
|
||||
Map<String, dynamic> _$SnPostCategoryToJson(_SnPostCategory instance) =>
|
||||
<String, dynamic>{
|
||||
'id': instance.id,
|
||||
'slug': instance.slug,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:island/models/post.dart';
|
||||
|
||||
@@ -6,14 +5,14 @@ part 'post_tag.freezed.dart';
|
||||
part 'post_tag.g.dart';
|
||||
|
||||
@freezed
|
||||
sealed class PostTag with _$PostTag {
|
||||
const factory PostTag({
|
||||
sealed class SnPostTag with _$SnPostTag {
|
||||
const factory SnPostTag({
|
||||
required String id,
|
||||
required String slug,
|
||||
String? name,
|
||||
@Default([]) List<SnPost> posts,
|
||||
}) = _PostTag;
|
||||
}) = _SnPostTag;
|
||||
|
||||
factory PostTag.fromJson(Map<String, dynamic> json) =>
|
||||
_$PostTagFromJson(json);
|
||||
factory SnPostTag.fromJson(Map<String, dynamic> json) =>
|
||||
_$SnPostTagFromJson(json);
|
||||
}
|
||||
|
||||
@@ -13,22 +13,22 @@ part of 'post_tag.dart';
|
||||
T _$identity<T>(T value) => value;
|
||||
|
||||
/// @nodoc
|
||||
mixin _$PostTag {
|
||||
mixin _$SnPostTag {
|
||||
|
||||
String get id; String get slug; String? get name; List<SnPost> get posts;
|
||||
/// Create a copy of PostTag
|
||||
/// Create a copy of SnPostTag
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
$PostTagCopyWith<PostTag> get copyWith => _$PostTagCopyWithImpl<PostTag>(this as PostTag, _$identity);
|
||||
$SnPostTagCopyWith<SnPostTag> get copyWith => _$SnPostTagCopyWithImpl<SnPostTag>(this as SnPostTag, _$identity);
|
||||
|
||||
/// Serializes this PostTag to a JSON map.
|
||||
/// Serializes this SnPostTag to a JSON map.
|
||||
Map<String, dynamic> toJson();
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is PostTag&&(identical(other.id, id) || other.id == id)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.name, name) || other.name == name)&&const DeepCollectionEquality().equals(other.posts, posts));
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnPostTag&&(identical(other.id, id) || other.id == id)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.name, name) || other.name == name)&&const DeepCollectionEquality().equals(other.posts, posts));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@@ -37,15 +37,15 @@ int get hashCode => Object.hash(runtimeType,id,slug,name,const DeepCollectionEqu
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'PostTag(id: $id, slug: $slug, name: $name, posts: $posts)';
|
||||
return 'SnPostTag(id: $id, slug: $slug, name: $name, posts: $posts)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class $PostTagCopyWith<$Res> {
|
||||
factory $PostTagCopyWith(PostTag value, $Res Function(PostTag) _then) = _$PostTagCopyWithImpl;
|
||||
abstract mixin class $SnPostTagCopyWith<$Res> {
|
||||
factory $SnPostTagCopyWith(SnPostTag value, $Res Function(SnPostTag) _then) = _$SnPostTagCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
String id, String slug, String? name, List<SnPost> posts
|
||||
@@ -56,14 +56,14 @@ $Res call({
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class _$PostTagCopyWithImpl<$Res>
|
||||
implements $PostTagCopyWith<$Res> {
|
||||
_$PostTagCopyWithImpl(this._self, this._then);
|
||||
class _$SnPostTagCopyWithImpl<$Res>
|
||||
implements $SnPostTagCopyWith<$Res> {
|
||||
_$SnPostTagCopyWithImpl(this._self, this._then);
|
||||
|
||||
final PostTag _self;
|
||||
final $Res Function(PostTag) _then;
|
||||
final SnPostTag _self;
|
||||
final $Res Function(SnPostTag) _then;
|
||||
|
||||
/// Create a copy of PostTag
|
||||
/// Create a copy of SnPostTag
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? slug = null,Object? name = freezed,Object? posts = null,}) {
|
||||
return _then(_self.copyWith(
|
||||
@@ -78,8 +78,8 @@ as List<SnPost>,
|
||||
}
|
||||
|
||||
|
||||
/// Adds pattern-matching-related methods to [PostTag].
|
||||
extension PostTagPatterns on PostTag {
|
||||
/// Adds pattern-matching-related methods to [SnPostTag].
|
||||
extension SnPostTagPatterns on SnPostTag {
|
||||
/// A variant of `map` that fallback to returning `orElse`.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
@@ -92,10 +92,10 @@ extension PostTagPatterns on PostTag {
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _PostTag value)? $default,{required TResult orElse(),}){
|
||||
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _SnPostTag value)? $default,{required TResult orElse(),}){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _PostTag() when $default != null:
|
||||
case _SnPostTag() when $default != null:
|
||||
return $default(_that);case _:
|
||||
return orElse();
|
||||
|
||||
@@ -114,10 +114,10 @@ return $default(_that);case _:
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _PostTag value) $default,){
|
||||
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _SnPostTag value) $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _PostTag():
|
||||
case _SnPostTag():
|
||||
return $default(_that);}
|
||||
}
|
||||
/// A variant of `map` that fallback to returning `null`.
|
||||
@@ -132,10 +132,10 @@ return $default(_that);}
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _PostTag value)? $default,){
|
||||
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _SnPostTag value)? $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _PostTag() when $default != null:
|
||||
case _SnPostTag() when $default != null:
|
||||
return $default(_that);case _:
|
||||
return null;
|
||||
|
||||
@@ -155,7 +155,7 @@ return $default(_that);case _:
|
||||
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, String slug, String? name, List<SnPost> posts)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _PostTag() when $default != null:
|
||||
case _SnPostTag() when $default != null:
|
||||
return $default(_that.id,_that.slug,_that.name,_that.posts);case _:
|
||||
return orElse();
|
||||
|
||||
@@ -176,7 +176,7 @@ return $default(_that.id,_that.slug,_that.name,_that.posts);case _:
|
||||
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, String slug, String? name, List<SnPost> posts) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _PostTag():
|
||||
case _SnPostTag():
|
||||
return $default(_that.id,_that.slug,_that.name,_that.posts);}
|
||||
}
|
||||
/// A variant of `when` that fallback to returning `null`
|
||||
@@ -193,7 +193,7 @@ return $default(_that.id,_that.slug,_that.name,_that.posts);}
|
||||
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, String slug, String? name, List<SnPost> posts)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _PostTag() when $default != null:
|
||||
case _SnPostTag() when $default != null:
|
||||
return $default(_that.id,_that.slug,_that.name,_that.posts);case _:
|
||||
return null;
|
||||
|
||||
@@ -205,9 +205,9 @@ return $default(_that.id,_that.slug,_that.name,_that.posts);case _:
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
|
||||
class _PostTag implements PostTag {
|
||||
const _PostTag({required this.id, required this.slug, this.name, final List<SnPost> posts = const []}): _posts = posts;
|
||||
factory _PostTag.fromJson(Map<String, dynamic> json) => _$PostTagFromJson(json);
|
||||
class _SnPostTag implements SnPostTag {
|
||||
const _SnPostTag({required this.id, required this.slug, this.name, final List<SnPost> posts = const []}): _posts = posts;
|
||||
factory _SnPostTag.fromJson(Map<String, dynamic> json) => _$SnPostTagFromJson(json);
|
||||
|
||||
@override final String id;
|
||||
@override final String slug;
|
||||
@@ -220,20 +220,20 @@ class _PostTag implements PostTag {
|
||||
}
|
||||
|
||||
|
||||
/// Create a copy of PostTag
|
||||
/// Create a copy of SnPostTag
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
_$PostTagCopyWith<_PostTag> get copyWith => __$PostTagCopyWithImpl<_PostTag>(this, _$identity);
|
||||
_$SnPostTagCopyWith<_SnPostTag> get copyWith => __$SnPostTagCopyWithImpl<_SnPostTag>(this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$PostTagToJson(this, );
|
||||
return _$SnPostTagToJson(this, );
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _PostTag&&(identical(other.id, id) || other.id == id)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.name, name) || other.name == name)&&const DeepCollectionEquality().equals(other._posts, _posts));
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnPostTag&&(identical(other.id, id) || other.id == id)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.name, name) || other.name == name)&&const DeepCollectionEquality().equals(other._posts, _posts));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@@ -242,15 +242,15 @@ int get hashCode => Object.hash(runtimeType,id,slug,name,const DeepCollectionEqu
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'PostTag(id: $id, slug: $slug, name: $name, posts: $posts)';
|
||||
return 'SnPostTag(id: $id, slug: $slug, name: $name, posts: $posts)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class _$PostTagCopyWith<$Res> implements $PostTagCopyWith<$Res> {
|
||||
factory _$PostTagCopyWith(_PostTag value, $Res Function(_PostTag) _then) = __$PostTagCopyWithImpl;
|
||||
abstract mixin class _$SnPostTagCopyWith<$Res> implements $SnPostTagCopyWith<$Res> {
|
||||
factory _$SnPostTagCopyWith(_SnPostTag value, $Res Function(_SnPostTag) _then) = __$SnPostTagCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
String id, String slug, String? name, List<SnPost> posts
|
||||
@@ -261,17 +261,17 @@ $Res call({
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class __$PostTagCopyWithImpl<$Res>
|
||||
implements _$PostTagCopyWith<$Res> {
|
||||
__$PostTagCopyWithImpl(this._self, this._then);
|
||||
class __$SnPostTagCopyWithImpl<$Res>
|
||||
implements _$SnPostTagCopyWith<$Res> {
|
||||
__$SnPostTagCopyWithImpl(this._self, this._then);
|
||||
|
||||
final _PostTag _self;
|
||||
final $Res Function(_PostTag) _then;
|
||||
final _SnPostTag _self;
|
||||
final $Res Function(_SnPostTag) _then;
|
||||
|
||||
/// Create a copy of PostTag
|
||||
/// Create a copy of SnPostTag
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? slug = null,Object? name = freezed,Object? posts = null,}) {
|
||||
return _then(_PostTag(
|
||||
return _then(_SnPostTag(
|
||||
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||
as String,slug: null == slug ? _self.slug : slug // ignore: cast_nullable_to_non_nullable
|
||||
as String,name: freezed == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
||||
|
||||
@@ -6,7 +6,7 @@ part of 'post_tag.dart';
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
_PostTag _$PostTagFromJson(Map<String, dynamic> json) => _PostTag(
|
||||
_SnPostTag _$SnPostTagFromJson(Map<String, dynamic> json) => _SnPostTag(
|
||||
id: json['id'] as String,
|
||||
slug: json['slug'] as String,
|
||||
name: json['name'] as String?,
|
||||
@@ -17,9 +17,10 @@ _PostTag _$PostTagFromJson(Map<String, dynamic> json) => _PostTag(
|
||||
const [],
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$PostTagToJson(_PostTag instance) => <String, dynamic>{
|
||||
'id': instance.id,
|
||||
'slug': instance.slug,
|
||||
'name': instance.name,
|
||||
'posts': instance.posts.map((e) => e.toJson()).toList(),
|
||||
};
|
||||
Map<String, dynamic> _$SnPostTagToJson(_SnPostTag instance) =>
|
||||
<String, dynamic>{
|
||||
'id': instance.id,
|
||||
'slug': instance.slug,
|
||||
'name': instance.name,
|
||||
'posts': instance.posts.map((e) => e.toJson()).toList(),
|
||||
};
|
||||
|
||||
@@ -35,6 +35,7 @@ sealed class SnStickerPack with _$SnStickerPack {
|
||||
required DateTime createdAt,
|
||||
required DateTime updatedAt,
|
||||
required DateTime? deletedAt,
|
||||
@Default([]) List<SnSticker> stickers,
|
||||
}) = _SnStickerPack;
|
||||
|
||||
factory SnStickerPack.fromJson(Map<String, dynamic> json) =>
|
||||
|
||||
@@ -338,7 +338,7 @@ $SnStickerPackCopyWith<$Res>? get pack {
|
||||
/// @nodoc
|
||||
mixin _$SnStickerPack {
|
||||
|
||||
String get id; String get name; String get description; String get prefix; String get publisherId; SnPublisher? get publisher; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt;
|
||||
String get id; String get name; String get description; String get prefix; String get publisherId; SnPublisher? get publisher; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; List<SnSticker> get stickers;
|
||||
/// Create a copy of SnStickerPack
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@@ -351,16 +351,16 @@ $SnStickerPackCopyWith<SnStickerPack> get copyWith => _$SnStickerPackCopyWithImp
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnStickerPack&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&(identical(other.prefix, prefix) || other.prefix == prefix)&&(identical(other.publisherId, publisherId) || other.publisherId == publisherId)&&(identical(other.publisher, publisher) || other.publisher == publisher)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnStickerPack&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&(identical(other.prefix, prefix) || other.prefix == prefix)&&(identical(other.publisherId, publisherId) || other.publisherId == publisherId)&&(identical(other.publisher, publisher) || other.publisher == publisher)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&const DeepCollectionEquality().equals(other.stickers, stickers));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,id,name,description,prefix,publisherId,publisher,createdAt,updatedAt,deletedAt);
|
||||
int get hashCode => Object.hash(runtimeType,id,name,description,prefix,publisherId,publisher,createdAt,updatedAt,deletedAt,const DeepCollectionEquality().hash(stickers));
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SnStickerPack(id: $id, name: $name, description: $description, prefix: $prefix, publisherId: $publisherId, publisher: $publisher, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
|
||||
return 'SnStickerPack(id: $id, name: $name, description: $description, prefix: $prefix, publisherId: $publisherId, publisher: $publisher, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, stickers: $stickers)';
|
||||
}
|
||||
|
||||
|
||||
@@ -371,7 +371,7 @@ abstract mixin class $SnStickerPackCopyWith<$Res> {
|
||||
factory $SnStickerPackCopyWith(SnStickerPack value, $Res Function(SnStickerPack) _then) = _$SnStickerPackCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
String id, String name, String description, String prefix, String publisherId, SnPublisher? publisher, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
|
||||
String id, String name, String description, String prefix, String publisherId, SnPublisher? publisher, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, List<SnSticker> stickers
|
||||
});
|
||||
|
||||
|
||||
@@ -388,7 +388,7 @@ class _$SnStickerPackCopyWithImpl<$Res>
|
||||
|
||||
/// Create a copy of SnStickerPack
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? name = null,Object? description = null,Object? prefix = null,Object? publisherId = null,Object? publisher = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? name = null,Object? description = null,Object? prefix = null,Object? publisherId = null,Object? publisher = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? stickers = null,}) {
|
||||
return _then(_self.copyWith(
|
||||
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||
as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
||||
@@ -399,7 +399,8 @@ as String,publisher: freezed == publisher ? _self.publisher : publisher // ignor
|
||||
as SnPublisher?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
||||
as 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?,stickers: null == stickers ? _self.stickers : stickers // ignore: cast_nullable_to_non_nullable
|
||||
as List<SnSticker>,
|
||||
));
|
||||
}
|
||||
/// Create a copy of SnStickerPack
|
||||
@@ -493,10 +494,10 @@ return $default(_that);case _:
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, String name, String description, String prefix, String publisherId, SnPublisher? publisher, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, String name, String description, String prefix, String publisherId, SnPublisher? publisher, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, List<SnSticker> stickers)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _SnStickerPack() when $default != null:
|
||||
return $default(_that.id,_that.name,_that.description,_that.prefix,_that.publisherId,_that.publisher,_that.createdAt,_that.updatedAt,_that.deletedAt);case _:
|
||||
return $default(_that.id,_that.name,_that.description,_that.prefix,_that.publisherId,_that.publisher,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.stickers);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
@@ -514,10 +515,10 @@ return $default(_that.id,_that.name,_that.description,_that.prefix,_that.publish
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, String name, String description, String prefix, String publisherId, SnPublisher? publisher, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt) $default,) {final _that = this;
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, String name, String description, String prefix, String publisherId, SnPublisher? publisher, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, List<SnSticker> stickers) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _SnStickerPack():
|
||||
return $default(_that.id,_that.name,_that.description,_that.prefix,_that.publisherId,_that.publisher,_that.createdAt,_that.updatedAt,_that.deletedAt);}
|
||||
return $default(_that.id,_that.name,_that.description,_that.prefix,_that.publisherId,_that.publisher,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.stickers);}
|
||||
}
|
||||
/// A variant of `when` that fallback to returning `null`
|
||||
///
|
||||
@@ -531,10 +532,10 @@ return $default(_that.id,_that.name,_that.description,_that.prefix,_that.publish
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, String name, String description, String prefix, String publisherId, SnPublisher? publisher, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt)? $default,) {final _that = this;
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, String name, String description, String prefix, String publisherId, SnPublisher? publisher, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, List<SnSticker> stickers)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _SnStickerPack() when $default != null:
|
||||
return $default(_that.id,_that.name,_that.description,_that.prefix,_that.publisherId,_that.publisher,_that.createdAt,_that.updatedAt,_that.deletedAt);case _:
|
||||
return $default(_that.id,_that.name,_that.description,_that.prefix,_that.publisherId,_that.publisher,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.stickers);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
@@ -546,7 +547,7 @@ return $default(_that.id,_that.name,_that.description,_that.prefix,_that.publish
|
||||
@JsonSerializable()
|
||||
|
||||
class _SnStickerPack implements SnStickerPack {
|
||||
const _SnStickerPack({required this.id, required this.name, required this.description, required this.prefix, required this.publisherId, required this.publisher, required this.createdAt, required this.updatedAt, required this.deletedAt});
|
||||
const _SnStickerPack({required this.id, required this.name, required this.description, required this.prefix, required this.publisherId, required this.publisher, required this.createdAt, required this.updatedAt, required this.deletedAt, final List<SnSticker> stickers = const []}): _stickers = stickers;
|
||||
factory _SnStickerPack.fromJson(Map<String, dynamic> json) => _$SnStickerPackFromJson(json);
|
||||
|
||||
@override final String id;
|
||||
@@ -558,6 +559,13 @@ class _SnStickerPack implements SnStickerPack {
|
||||
@override final DateTime createdAt;
|
||||
@override final DateTime updatedAt;
|
||||
@override final DateTime? deletedAt;
|
||||
final List<SnSticker> _stickers;
|
||||
@override@JsonKey() List<SnSticker> get stickers {
|
||||
if (_stickers is EqualUnmodifiableListView) return _stickers;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(_stickers);
|
||||
}
|
||||
|
||||
|
||||
/// Create a copy of SnStickerPack
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@@ -572,16 +580,16 @@ Map<String, dynamic> toJson() {
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnStickerPack&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&(identical(other.prefix, prefix) || other.prefix == prefix)&&(identical(other.publisherId, publisherId) || other.publisherId == publisherId)&&(identical(other.publisher, publisher) || other.publisher == publisher)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnStickerPack&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&(identical(other.prefix, prefix) || other.prefix == prefix)&&(identical(other.publisherId, publisherId) || other.publisherId == publisherId)&&(identical(other.publisher, publisher) || other.publisher == publisher)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&const DeepCollectionEquality().equals(other._stickers, _stickers));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,id,name,description,prefix,publisherId,publisher,createdAt,updatedAt,deletedAt);
|
||||
int get hashCode => Object.hash(runtimeType,id,name,description,prefix,publisherId,publisher,createdAt,updatedAt,deletedAt,const DeepCollectionEquality().hash(_stickers));
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SnStickerPack(id: $id, name: $name, description: $description, prefix: $prefix, publisherId: $publisherId, publisher: $publisher, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
|
||||
return 'SnStickerPack(id: $id, name: $name, description: $description, prefix: $prefix, publisherId: $publisherId, publisher: $publisher, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, stickers: $stickers)';
|
||||
}
|
||||
|
||||
|
||||
@@ -592,7 +600,7 @@ abstract mixin class _$SnStickerPackCopyWith<$Res> implements $SnStickerPackCopy
|
||||
factory _$SnStickerPackCopyWith(_SnStickerPack value, $Res Function(_SnStickerPack) _then) = __$SnStickerPackCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
String id, String name, String description, String prefix, String publisherId, SnPublisher? publisher, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
|
||||
String id, String name, String description, String prefix, String publisherId, SnPublisher? publisher, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, List<SnSticker> stickers
|
||||
});
|
||||
|
||||
|
||||
@@ -609,7 +617,7 @@ class __$SnStickerPackCopyWithImpl<$Res>
|
||||
|
||||
/// Create a copy of SnStickerPack
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? name = null,Object? description = null,Object? prefix = null,Object? publisherId = null,Object? publisher = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? name = null,Object? description = null,Object? prefix = null,Object? publisherId = null,Object? publisher = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? stickers = null,}) {
|
||||
return _then(_SnStickerPack(
|
||||
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||
as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
||||
@@ -620,7 +628,8 @@ as String,publisher: freezed == publisher ? _self.publisher : publisher // ignor
|
||||
as SnPublisher?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
||||
as 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?,stickers: null == stickers ? _self._stickers : stickers // ignore: cast_nullable_to_non_nullable
|
||||
as List<SnSticker>,
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
@@ -54,6 +54,11 @@ _SnStickerPack _$SnStickerPackFromJson(Map<String, dynamic> json) =>
|
||||
json['deleted_at'] == null
|
||||
? null
|
||||
: DateTime.parse(json['deleted_at'] as String),
|
||||
stickers:
|
||||
(json['stickers'] as List<dynamic>?)
|
||||
?.map((e) => SnSticker.fromJson(e as Map<String, dynamic>))
|
||||
.toList() ??
|
||||
const [],
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$SnStickerPackToJson(_SnStickerPack instance) =>
|
||||
@@ -67,4 +72,5 @@ Map<String, dynamic> _$SnStickerPackToJson(_SnStickerPack instance) =>
|
||||
'created_at': instance.createdAt.toIso8601String(),
|
||||
'updated_at': instance.updatedAt.toIso8601String(),
|
||||
'deleted_at': instance.deletedAt?.toIso8601String(),
|
||||
'stickers': instance.stickers.map((e) => e.toJson()).toList(),
|
||||
};
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import 'package:island/pods/userinfo.dart';
|
||||
import 'package:island/screens/chat/chat.dart';
|
||||
import 'dart:async';
|
||||
import 'dart:developer';
|
||||
import 'dart:io';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_webrtc/flutter_webrtc.dart';
|
||||
import 'package:island/widgets/chat/call_button.dart';
|
||||
import 'package:livekit_client/livekit_client.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'dart:async';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:island/pods/network.dart';
|
||||
import 'package:island/models/chat.dart';
|
||||
import 'package:island/pods/websocket.dart';
|
||||
|
||||
part 'call.g.dart';
|
||||
part 'call.freezed.dart';
|
||||
@@ -27,6 +28,7 @@ sealed class CallState with _$CallState {
|
||||
required bool isMicrophoneEnabled,
|
||||
required bool isCameraEnabled,
|
||||
required bool isScreenSharing,
|
||||
required bool isSpeakerphone,
|
||||
@Default(Duration(seconds: 0)) Duration duration,
|
||||
String? error,
|
||||
}) = _CallState;
|
||||
@@ -42,7 +44,8 @@ sealed class CallParticipantLive with _$CallParticipantLive {
|
||||
}) = _CallParticipantLive;
|
||||
|
||||
bool get isSpeaking => remoteParticipant.isSpeaking;
|
||||
bool get isMuted => remoteParticipant.isMuted;
|
||||
bool get isMuted =>
|
||||
remoteParticipant.isMuted || !remoteParticipant.isMicrophoneEnabled();
|
||||
bool get isScreenSharing => remoteParticipant.isScreenShareEnabled();
|
||||
bool get isScreenSharingWithAudio =>
|
||||
remoteParticipant.isScreenShareAudioEnabled();
|
||||
@@ -57,13 +60,14 @@ class CallNotifier extends _$CallNotifier {
|
||||
LocalParticipant? _localParticipant;
|
||||
List<CallParticipantLive> _participants = [];
|
||||
final Map<String, CallParticipant> _participantInfoByIdentity = {};
|
||||
StreamSubscription? _wsSubscription;
|
||||
EventsListener? _roomListener;
|
||||
|
||||
List<CallParticipantLive> get participants =>
|
||||
List.unmodifiable(_participants);
|
||||
LocalParticipant? get localParticipant => _localParticipant;
|
||||
|
||||
Map<String, double> participantsVolumes = {};
|
||||
|
||||
Timer? _durationTimer;
|
||||
|
||||
Room? get room => _room;
|
||||
@@ -71,36 +75,15 @@ class CallNotifier extends _$CallNotifier {
|
||||
@override
|
||||
CallState build() {
|
||||
// Subscribe to websocket updates
|
||||
_subscribeToParticipantsUpdate();
|
||||
return const CallState(
|
||||
isConnected: false,
|
||||
isMicrophoneEnabled: true,
|
||||
isCameraEnabled: false,
|
||||
isScreenSharing: false,
|
||||
isSpeakerphone: true,
|
||||
);
|
||||
}
|
||||
|
||||
void _subscribeToParticipantsUpdate() {
|
||||
// Only subscribe once
|
||||
if (_wsSubscription != null) return;
|
||||
final ws = ref.read(websocketProvider);
|
||||
_wsSubscription = ws.dataStream.listen((packet) {
|
||||
if (packet.type == 'call.participants.update' && packet.data != null) {
|
||||
final participantsData = packet.data!["participants"];
|
||||
if (participantsData is List) {
|
||||
final parsed =
|
||||
participantsData
|
||||
.map(
|
||||
(e) =>
|
||||
CallParticipant.fromJson(Map<String, dynamic>.from(e)),
|
||||
)
|
||||
.toList();
|
||||
_updateLiveParticipants(parsed);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _initRoomListeners() {
|
||||
if (_room == null) return;
|
||||
_roomListener?.dispose();
|
||||
@@ -143,8 +126,6 @@ class CallNotifier extends _$CallNotifier {
|
||||
identity: remote.identity,
|
||||
name: remote.identity,
|
||||
joinedAt: DateTime.now(),
|
||||
accountId: null,
|
||||
profile: null,
|
||||
);
|
||||
return CallParticipantLive(
|
||||
participant: match,
|
||||
@@ -169,16 +150,12 @@ class CallNotifier extends _$CallNotifier {
|
||||
if (idx != -1) return participants[idx];
|
||||
}
|
||||
|
||||
final userInfo = ref.read(userInfoProvider);
|
||||
final roomIdentity = ref.read(chatroomIdentityProvider(_roomId));
|
||||
// Otherwise, use info from the identity map or fallback to minimal
|
||||
return _participantInfoByIdentity[_localParticipant!.identity] ??
|
||||
CallParticipant(
|
||||
identity: _localParticipant!.identity,
|
||||
name: _localParticipant!.identity,
|
||||
joinedAt: DateTime.now(),
|
||||
accountId: userInfo.value?.id,
|
||||
profile: roomIdentity.value,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -205,6 +182,7 @@ class CallNotifier extends _$CallNotifier {
|
||||
remoteParticipant: _localParticipant!,
|
||||
),
|
||||
);
|
||||
state = state.copyWith();
|
||||
}
|
||||
// Add remote participants
|
||||
_participants.addAll(
|
||||
@@ -233,7 +211,13 @@ class CallNotifier extends _$CallNotifier {
|
||||
|
||||
Future<void> joinRoom(String roomId) async {
|
||||
if (_roomId == roomId && _room != null) {
|
||||
log('[Call] Call skipped. Already has data');
|
||||
return;
|
||||
} else if (_room != null) {
|
||||
if (!_room!.isDisposed &&
|
||||
_room!.connectionState != ConnectionState.disconnected) {
|
||||
throw Exception('Call already connected');
|
||||
}
|
||||
}
|
||||
_roomId = roomId;
|
||||
if (_room != null) {
|
||||
@@ -264,7 +248,8 @@ class CallNotifier extends _$CallNotifier {
|
||||
duration: Duration(
|
||||
milliseconds:
|
||||
(DateTime.now().millisecondsSinceEpoch -
|
||||
(ongoingCall?.createdAt.millisecondsSinceEpoch ?? 0)),
|
||||
(ongoingCall?.createdAt.millisecondsSinceEpoch ??
|
||||
DateTime.now().millisecondsSinceEpoch)),
|
||||
),
|
||||
);
|
||||
});
|
||||
@@ -286,6 +271,10 @@ class CallNotifier extends _$CallNotifier {
|
||||
_initRoomListeners();
|
||||
_updateLiveParticipants(participants);
|
||||
|
||||
if (!kIsWeb && (Platform.isIOS || Platform.isAndroid)) {
|
||||
Hardware.instance.setSpeakerphoneOn(true);
|
||||
}
|
||||
|
||||
// Listen for connection updates
|
||||
_room!.addListener(() {
|
||||
state = state.copyWith(
|
||||
@@ -318,6 +307,7 @@ class CallNotifier extends _$CallNotifier {
|
||||
stopOnMute: autostop,
|
||||
);
|
||||
}
|
||||
state = state.copyWith();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -326,6 +316,7 @@ class CallNotifier extends _$CallNotifier {
|
||||
final target = !_localParticipant!.isCameraEnabled();
|
||||
state = state.copyWith(isCameraEnabled: target);
|
||||
await _localParticipant!.setCameraEnabled(target);
|
||||
state = state.copyWith();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -334,9 +325,16 @@ class CallNotifier extends _$CallNotifier {
|
||||
final target = !_localParticipant!.isScreenShareEnabled();
|
||||
state = state.copyWith(isScreenSharing: target);
|
||||
await _localParticipant!.setScreenShareEnabled(target);
|
||||
state = state.copyWith();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> toggleSpeakerphone() async {
|
||||
state = state.copyWith(isSpeakerphone: !state.isSpeakerphone);
|
||||
await Hardware.instance.setSpeakerphoneOn(state.isSpeakerphone);
|
||||
state = state.copyWith();
|
||||
}
|
||||
|
||||
Future<void> disconnect() async {
|
||||
if (_room != null) {
|
||||
await _room!.disconnect();
|
||||
@@ -349,11 +347,39 @@ class CallNotifier extends _$CallNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
void setParticipantVolume(CallParticipantLive live, double volume) {
|
||||
if (participantsVolumes[live.remoteParticipant.sid] == null) {
|
||||
participantsVolumes[live.remoteParticipant.sid] = 1;
|
||||
}
|
||||
Helper.setVolume(
|
||||
volume,
|
||||
live
|
||||
.remoteParticipant
|
||||
.audioTrackPublications
|
||||
.first
|
||||
.track!
|
||||
.mediaStreamTrack,
|
||||
);
|
||||
participantsVolumes[live.remoteParticipant.sid] = volume;
|
||||
}
|
||||
|
||||
double getParticipantVolume(CallParticipantLive live) {
|
||||
return participantsVolumes[live.remoteParticipant.sid] ?? 1;
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
_wsSubscription?.cancel();
|
||||
state = state.copyWith(
|
||||
error: null,
|
||||
isConnected: false,
|
||||
isMicrophoneEnabled: false,
|
||||
isCameraEnabled: false,
|
||||
isScreenSharing: false,
|
||||
);
|
||||
_roomListener?.dispose();
|
||||
_room?.removeListener(_onRoomChange);
|
||||
_room?.dispose();
|
||||
_durationTimer?.cancel();
|
||||
_roomId = null;
|
||||
participantsVolumes = {};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,9 +12,9 @@ part of 'call.dart';
|
||||
// dart format off
|
||||
T _$identity<T>(T value) => value;
|
||||
/// @nodoc
|
||||
mixin _$CallState {
|
||||
mixin _$CallState implements DiagnosticableTreeMixin {
|
||||
|
||||
bool get isConnected; bool get isMicrophoneEnabled; bool get isCameraEnabled; bool get isScreenSharing; Duration get duration; String? get error;
|
||||
bool get isConnected; bool get isMicrophoneEnabled; bool get isCameraEnabled; bool get isScreenSharing; bool get isSpeakerphone; Duration get duration; String? get error;
|
||||
/// Create a copy of CallState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@@ -22,19 +22,25 @@ mixin _$CallState {
|
||||
$CallStateCopyWith<CallState> get copyWith => _$CallStateCopyWithImpl<CallState>(this as CallState, _$identity);
|
||||
|
||||
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
properties
|
||||
..add(DiagnosticsProperty('type', 'CallState'))
|
||||
..add(DiagnosticsProperty('isConnected', isConnected))..add(DiagnosticsProperty('isMicrophoneEnabled', isMicrophoneEnabled))..add(DiagnosticsProperty('isCameraEnabled', isCameraEnabled))..add(DiagnosticsProperty('isScreenSharing', isScreenSharing))..add(DiagnosticsProperty('isSpeakerphone', isSpeakerphone))..add(DiagnosticsProperty('duration', duration))..add(DiagnosticsProperty('error', error));
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is CallState&&(identical(other.isConnected, isConnected) || other.isConnected == isConnected)&&(identical(other.isMicrophoneEnabled, isMicrophoneEnabled) || other.isMicrophoneEnabled == isMicrophoneEnabled)&&(identical(other.isCameraEnabled, isCameraEnabled) || other.isCameraEnabled == isCameraEnabled)&&(identical(other.isScreenSharing, isScreenSharing) || other.isScreenSharing == isScreenSharing)&&(identical(other.duration, duration) || other.duration == duration)&&(identical(other.error, error) || other.error == error));
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is CallState&&(identical(other.isConnected, isConnected) || other.isConnected == isConnected)&&(identical(other.isMicrophoneEnabled, isMicrophoneEnabled) || other.isMicrophoneEnabled == isMicrophoneEnabled)&&(identical(other.isCameraEnabled, isCameraEnabled) || other.isCameraEnabled == isCameraEnabled)&&(identical(other.isScreenSharing, isScreenSharing) || other.isScreenSharing == isScreenSharing)&&(identical(other.isSpeakerphone, isSpeakerphone) || other.isSpeakerphone == isSpeakerphone)&&(identical(other.duration, duration) || other.duration == duration)&&(identical(other.error, error) || other.error == error));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,isConnected,isMicrophoneEnabled,isCameraEnabled,isScreenSharing,duration,error);
|
||||
int get hashCode => Object.hash(runtimeType,isConnected,isMicrophoneEnabled,isCameraEnabled,isScreenSharing,isSpeakerphone,duration,error);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'CallState(isConnected: $isConnected, isMicrophoneEnabled: $isMicrophoneEnabled, isCameraEnabled: $isCameraEnabled, isScreenSharing: $isScreenSharing, duration: $duration, error: $error)';
|
||||
String toString({ DiagnosticLevel minLevel = DiagnosticLevel.info }) {
|
||||
return 'CallState(isConnected: $isConnected, isMicrophoneEnabled: $isMicrophoneEnabled, isCameraEnabled: $isCameraEnabled, isScreenSharing: $isScreenSharing, isSpeakerphone: $isSpeakerphone, duration: $duration, error: $error)';
|
||||
}
|
||||
|
||||
|
||||
@@ -45,7 +51,7 @@ abstract mixin class $CallStateCopyWith<$Res> {
|
||||
factory $CallStateCopyWith(CallState value, $Res Function(CallState) _then) = _$CallStateCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
bool isConnected, bool isMicrophoneEnabled, bool isCameraEnabled, bool isScreenSharing, Duration duration, String? error
|
||||
bool isConnected, bool isMicrophoneEnabled, bool isCameraEnabled, bool isScreenSharing, bool isSpeakerphone, Duration duration, String? error
|
||||
});
|
||||
|
||||
|
||||
@@ -62,12 +68,13 @@ class _$CallStateCopyWithImpl<$Res>
|
||||
|
||||
/// Create a copy of CallState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? isConnected = null,Object? isMicrophoneEnabled = null,Object? isCameraEnabled = null,Object? isScreenSharing = null,Object? duration = null,Object? error = freezed,}) {
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? isConnected = null,Object? isMicrophoneEnabled = null,Object? isCameraEnabled = null,Object? isScreenSharing = null,Object? isSpeakerphone = null,Object? duration = null,Object? error = freezed,}) {
|
||||
return _then(_self.copyWith(
|
||||
isConnected: null == isConnected ? _self.isConnected : isConnected // ignore: cast_nullable_to_non_nullable
|
||||
as bool,isMicrophoneEnabled: null == isMicrophoneEnabled ? _self.isMicrophoneEnabled : isMicrophoneEnabled // ignore: cast_nullable_to_non_nullable
|
||||
as bool,isCameraEnabled: null == isCameraEnabled ? _self.isCameraEnabled : isCameraEnabled // ignore: cast_nullable_to_non_nullable
|
||||
as bool,isScreenSharing: null == isScreenSharing ? _self.isScreenSharing : isScreenSharing // ignore: cast_nullable_to_non_nullable
|
||||
as bool,isSpeakerphone: null == isSpeakerphone ? _self.isSpeakerphone : isSpeakerphone // ignore: cast_nullable_to_non_nullable
|
||||
as bool,duration: null == duration ? _self.duration : duration // ignore: cast_nullable_to_non_nullable
|
||||
as Duration,error: freezed == error ? _self.error : error // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
@@ -152,10 +159,10 @@ return $default(_that);case _:
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( bool isConnected, bool isMicrophoneEnabled, bool isCameraEnabled, bool isScreenSharing, Duration duration, String? error)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( bool isConnected, bool isMicrophoneEnabled, bool isCameraEnabled, bool isScreenSharing, bool isSpeakerphone, Duration duration, String? error)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _CallState() when $default != null:
|
||||
return $default(_that.isConnected,_that.isMicrophoneEnabled,_that.isCameraEnabled,_that.isScreenSharing,_that.duration,_that.error);case _:
|
||||
return $default(_that.isConnected,_that.isMicrophoneEnabled,_that.isCameraEnabled,_that.isScreenSharing,_that.isSpeakerphone,_that.duration,_that.error);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
@@ -173,10 +180,10 @@ return $default(_that.isConnected,_that.isMicrophoneEnabled,_that.isCameraEnable
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( bool isConnected, bool isMicrophoneEnabled, bool isCameraEnabled, bool isScreenSharing, Duration duration, String? error) $default,) {final _that = this;
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( bool isConnected, bool isMicrophoneEnabled, bool isCameraEnabled, bool isScreenSharing, bool isSpeakerphone, Duration duration, String? error) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _CallState():
|
||||
return $default(_that.isConnected,_that.isMicrophoneEnabled,_that.isCameraEnabled,_that.isScreenSharing,_that.duration,_that.error);}
|
||||
return $default(_that.isConnected,_that.isMicrophoneEnabled,_that.isCameraEnabled,_that.isScreenSharing,_that.isSpeakerphone,_that.duration,_that.error);}
|
||||
}
|
||||
/// A variant of `when` that fallback to returning `null`
|
||||
///
|
||||
@@ -190,10 +197,10 @@ return $default(_that.isConnected,_that.isMicrophoneEnabled,_that.isCameraEnable
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( bool isConnected, bool isMicrophoneEnabled, bool isCameraEnabled, bool isScreenSharing, Duration duration, String? error)? $default,) {final _that = this;
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( bool isConnected, bool isMicrophoneEnabled, bool isCameraEnabled, bool isScreenSharing, bool isSpeakerphone, Duration duration, String? error)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _CallState() when $default != null:
|
||||
return $default(_that.isConnected,_that.isMicrophoneEnabled,_that.isCameraEnabled,_that.isScreenSharing,_that.duration,_that.error);case _:
|
||||
return $default(_that.isConnected,_that.isMicrophoneEnabled,_that.isCameraEnabled,_that.isScreenSharing,_that.isSpeakerphone,_that.duration,_that.error);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
@@ -204,14 +211,15 @@ return $default(_that.isConnected,_that.isMicrophoneEnabled,_that.isCameraEnable
|
||||
/// @nodoc
|
||||
|
||||
|
||||
class _CallState implements CallState {
|
||||
const _CallState({required this.isConnected, required this.isMicrophoneEnabled, required this.isCameraEnabled, required this.isScreenSharing, this.duration = const Duration(seconds: 0), this.error});
|
||||
class _CallState with DiagnosticableTreeMixin implements CallState {
|
||||
const _CallState({required this.isConnected, required this.isMicrophoneEnabled, required this.isCameraEnabled, required this.isScreenSharing, required this.isSpeakerphone, this.duration = const Duration(seconds: 0), this.error});
|
||||
|
||||
|
||||
@override final bool isConnected;
|
||||
@override final bool isMicrophoneEnabled;
|
||||
@override final bool isCameraEnabled;
|
||||
@override final bool isScreenSharing;
|
||||
@override final bool isSpeakerphone;
|
||||
@override@JsonKey() final Duration duration;
|
||||
@override final String? error;
|
||||
|
||||
@@ -222,19 +230,25 @@ class _CallState implements CallState {
|
||||
_$CallStateCopyWith<_CallState> get copyWith => __$CallStateCopyWithImpl<_CallState>(this, _$identity);
|
||||
|
||||
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
properties
|
||||
..add(DiagnosticsProperty('type', 'CallState'))
|
||||
..add(DiagnosticsProperty('isConnected', isConnected))..add(DiagnosticsProperty('isMicrophoneEnabled', isMicrophoneEnabled))..add(DiagnosticsProperty('isCameraEnabled', isCameraEnabled))..add(DiagnosticsProperty('isScreenSharing', isScreenSharing))..add(DiagnosticsProperty('isSpeakerphone', isSpeakerphone))..add(DiagnosticsProperty('duration', duration))..add(DiagnosticsProperty('error', error));
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _CallState&&(identical(other.isConnected, isConnected) || other.isConnected == isConnected)&&(identical(other.isMicrophoneEnabled, isMicrophoneEnabled) || other.isMicrophoneEnabled == isMicrophoneEnabled)&&(identical(other.isCameraEnabled, isCameraEnabled) || other.isCameraEnabled == isCameraEnabled)&&(identical(other.isScreenSharing, isScreenSharing) || other.isScreenSharing == isScreenSharing)&&(identical(other.duration, duration) || other.duration == duration)&&(identical(other.error, error) || other.error == error));
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _CallState&&(identical(other.isConnected, isConnected) || other.isConnected == isConnected)&&(identical(other.isMicrophoneEnabled, isMicrophoneEnabled) || other.isMicrophoneEnabled == isMicrophoneEnabled)&&(identical(other.isCameraEnabled, isCameraEnabled) || other.isCameraEnabled == isCameraEnabled)&&(identical(other.isScreenSharing, isScreenSharing) || other.isScreenSharing == isScreenSharing)&&(identical(other.isSpeakerphone, isSpeakerphone) || other.isSpeakerphone == isSpeakerphone)&&(identical(other.duration, duration) || other.duration == duration)&&(identical(other.error, error) || other.error == error));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,isConnected,isMicrophoneEnabled,isCameraEnabled,isScreenSharing,duration,error);
|
||||
int get hashCode => Object.hash(runtimeType,isConnected,isMicrophoneEnabled,isCameraEnabled,isScreenSharing,isSpeakerphone,duration,error);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'CallState(isConnected: $isConnected, isMicrophoneEnabled: $isMicrophoneEnabled, isCameraEnabled: $isCameraEnabled, isScreenSharing: $isScreenSharing, duration: $duration, error: $error)';
|
||||
String toString({ DiagnosticLevel minLevel = DiagnosticLevel.info }) {
|
||||
return 'CallState(isConnected: $isConnected, isMicrophoneEnabled: $isMicrophoneEnabled, isCameraEnabled: $isCameraEnabled, isScreenSharing: $isScreenSharing, isSpeakerphone: $isSpeakerphone, duration: $duration, error: $error)';
|
||||
}
|
||||
|
||||
|
||||
@@ -245,7 +259,7 @@ abstract mixin class _$CallStateCopyWith<$Res> implements $CallStateCopyWith<$Re
|
||||
factory _$CallStateCopyWith(_CallState value, $Res Function(_CallState) _then) = __$CallStateCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
bool isConnected, bool isMicrophoneEnabled, bool isCameraEnabled, bool isScreenSharing, Duration duration, String? error
|
||||
bool isConnected, bool isMicrophoneEnabled, bool isCameraEnabled, bool isScreenSharing, bool isSpeakerphone, Duration duration, String? error
|
||||
});
|
||||
|
||||
|
||||
@@ -262,12 +276,13 @@ class __$CallStateCopyWithImpl<$Res>
|
||||
|
||||
/// Create a copy of CallState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? isConnected = null,Object? isMicrophoneEnabled = null,Object? isCameraEnabled = null,Object? isScreenSharing = null,Object? duration = null,Object? error = freezed,}) {
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? isConnected = null,Object? isMicrophoneEnabled = null,Object? isCameraEnabled = null,Object? isScreenSharing = null,Object? isSpeakerphone = null,Object? duration = null,Object? error = freezed,}) {
|
||||
return _then(_CallState(
|
||||
isConnected: null == isConnected ? _self.isConnected : isConnected // ignore: cast_nullable_to_non_nullable
|
||||
as bool,isMicrophoneEnabled: null == isMicrophoneEnabled ? _self.isMicrophoneEnabled : isMicrophoneEnabled // ignore: cast_nullable_to_non_nullable
|
||||
as bool,isCameraEnabled: null == isCameraEnabled ? _self.isCameraEnabled : isCameraEnabled // ignore: cast_nullable_to_non_nullable
|
||||
as bool,isScreenSharing: null == isScreenSharing ? _self.isScreenSharing : isScreenSharing // ignore: cast_nullable_to_non_nullable
|
||||
as bool,isSpeakerphone: null == isSpeakerphone ? _self.isSpeakerphone : isSpeakerphone // ignore: cast_nullable_to_non_nullable
|
||||
as bool,duration: null == duration ? _self.duration : duration // ignore: cast_nullable_to_non_nullable
|
||||
as Duration,error: freezed == error ? _self.error : error // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
@@ -278,7 +293,7 @@ as String?,
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$CallParticipantLive {
|
||||
mixin _$CallParticipantLive implements DiagnosticableTreeMixin {
|
||||
|
||||
CallParticipant get participant; Participant get remoteParticipant;
|
||||
/// Create a copy of CallParticipantLive
|
||||
@@ -288,6 +303,12 @@ mixin _$CallParticipantLive {
|
||||
$CallParticipantLiveCopyWith<CallParticipantLive> get copyWith => _$CallParticipantLiveCopyWithImpl<CallParticipantLive>(this as CallParticipantLive, _$identity);
|
||||
|
||||
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
properties
|
||||
..add(DiagnosticsProperty('type', 'CallParticipantLive'))
|
||||
..add(DiagnosticsProperty('participant', participant))..add(DiagnosticsProperty('remoteParticipant', remoteParticipant));
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
@@ -299,7 +320,7 @@ bool operator ==(Object other) {
|
||||
int get hashCode => Object.hash(runtimeType,participant,remoteParticipant);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
String toString({ DiagnosticLevel minLevel = DiagnosticLevel.info }) {
|
||||
return 'CallParticipantLive(participant: $participant, remoteParticipant: $remoteParticipant)';
|
||||
}
|
||||
|
||||
@@ -475,7 +496,7 @@ return $default(_that.participant,_that.remoteParticipant);case _:
|
||||
/// @nodoc
|
||||
|
||||
|
||||
class _CallParticipantLive extends CallParticipantLive {
|
||||
class _CallParticipantLive extends CallParticipantLive with DiagnosticableTreeMixin {
|
||||
const _CallParticipantLive({required this.participant, required this.remoteParticipant}): super._();
|
||||
|
||||
|
||||
@@ -489,6 +510,12 @@ class _CallParticipantLive extends CallParticipantLive {
|
||||
_$CallParticipantLiveCopyWith<_CallParticipantLive> get copyWith => __$CallParticipantLiveCopyWithImpl<_CallParticipantLive>(this, _$identity);
|
||||
|
||||
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
properties
|
||||
..add(DiagnosticsProperty('type', 'CallParticipantLive'))
|
||||
..add(DiagnosticsProperty('participant', participant))..add(DiagnosticsProperty('remoteParticipant', remoteParticipant));
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
@@ -500,7 +527,7 @@ bool operator ==(Object other) {
|
||||
int get hashCode => Object.hash(runtimeType,participant,remoteParticipant);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
String toString({ DiagnosticLevel minLevel = DiagnosticLevel.info }) {
|
||||
return 'CallParticipantLive(participant: $participant, remoteParticipant: $remoteParticipant)';
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ part of 'call.dart';
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$callNotifierHash() => r'107174cd6cfab6bfafe44f8c4a72a67bcb93217b';
|
||||
String _$callNotifierHash() => r'18fb807f067eecd3ea42631c1426c3e5f1fb4280';
|
||||
|
||||
/// See also [CallNotifier].
|
||||
@ProviderFor(CallNotifier)
|
||||
|
||||
38
lib/pods/translate.dart
Normal file
38
lib/pods/translate.dart
Normal file
@@ -0,0 +1,38 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:island/pods/network.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:flutter_langdetect/flutter_langdetect.dart' as langdetect;
|
||||
|
||||
part 'translate.freezed.dart';
|
||||
part 'translate.g.dart';
|
||||
|
||||
@freezed
|
||||
sealed class TranslateQuery with _$TranslateQuery {
|
||||
const factory TranslateQuery({required String text, required String lang}) =
|
||||
_TranslateQuery;
|
||||
}
|
||||
|
||||
@riverpod
|
||||
Future<String> translateString(Ref ref, TranslateQuery query) async {
|
||||
final client = ref.watch(apiClientProvider);
|
||||
final response = await client.post(
|
||||
'/sphere/translate',
|
||||
queryParameters: {'to': query.lang},
|
||||
data: jsonEncode(query.text),
|
||||
);
|
||||
return response.data as String;
|
||||
}
|
||||
|
||||
@riverpod
|
||||
String? detectStringLanguage(Ref ref, String text) {
|
||||
try {
|
||||
return langdetect.detectLangs(text).firstOrNull?.lang;
|
||||
} catch (err) {
|
||||
log('[Language] Unable to detect text\'s language: $text');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
268
lib/pods/translate.freezed.dart
Normal file
268
lib/pods/translate.freezed.dart
Normal file
@@ -0,0 +1,268 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// coverage:ignore-file
|
||||
// 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 'translate.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// dart format off
|
||||
T _$identity<T>(T value) => value;
|
||||
/// @nodoc
|
||||
mixin _$TranslateQuery {
|
||||
|
||||
String get text; String get lang;
|
||||
/// Create a copy of TranslateQuery
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
$TranslateQueryCopyWith<TranslateQuery> get copyWith => _$TranslateQueryCopyWithImpl<TranslateQuery>(this as TranslateQuery, _$identity);
|
||||
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is TranslateQuery&&(identical(other.text, text) || other.text == text)&&(identical(other.lang, lang) || other.lang == lang));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,text,lang);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'TranslateQuery(text: $text, lang: $lang)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class $TranslateQueryCopyWith<$Res> {
|
||||
factory $TranslateQueryCopyWith(TranslateQuery value, $Res Function(TranslateQuery) _then) = _$TranslateQueryCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
String text, String lang
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class _$TranslateQueryCopyWithImpl<$Res>
|
||||
implements $TranslateQueryCopyWith<$Res> {
|
||||
_$TranslateQueryCopyWithImpl(this._self, this._then);
|
||||
|
||||
final TranslateQuery _self;
|
||||
final $Res Function(TranslateQuery) _then;
|
||||
|
||||
/// Create a copy of TranslateQuery
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? text = null,Object? lang = null,}) {
|
||||
return _then(_self.copyWith(
|
||||
text: null == text ? _self.text : text // ignore: cast_nullable_to_non_nullable
|
||||
as String,lang: null == lang ? _self.lang : lang // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// Adds pattern-matching-related methods to [TranslateQuery].
|
||||
extension TranslateQueryPatterns on TranslateQuery {
|
||||
/// 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( _TranslateQuery value)? $default,{required TResult orElse(),}){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _TranslateQuery() 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( _TranslateQuery value) $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _TranslateQuery():
|
||||
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( _TranslateQuery value)? $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _TranslateQuery() 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 text, String lang)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _TranslateQuery() when $default != null:
|
||||
return $default(_that.text,_that.lang);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 text, String lang) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _TranslateQuery():
|
||||
return $default(_that.text,_that.lang);}
|
||||
}
|
||||
/// 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 text, String lang)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _TranslateQuery() when $default != null:
|
||||
return $default(_that.text,_that.lang);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
|
||||
class _TranslateQuery implements TranslateQuery {
|
||||
const _TranslateQuery({required this.text, required this.lang});
|
||||
|
||||
|
||||
@override final String text;
|
||||
@override final String lang;
|
||||
|
||||
/// Create a copy of TranslateQuery
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
_$TranslateQueryCopyWith<_TranslateQuery> get copyWith => __$TranslateQueryCopyWithImpl<_TranslateQuery>(this, _$identity);
|
||||
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _TranslateQuery&&(identical(other.text, text) || other.text == text)&&(identical(other.lang, lang) || other.lang == lang));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,text,lang);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'TranslateQuery(text: $text, lang: $lang)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class _$TranslateQueryCopyWith<$Res> implements $TranslateQueryCopyWith<$Res> {
|
||||
factory _$TranslateQueryCopyWith(_TranslateQuery value, $Res Function(_TranslateQuery) _then) = __$TranslateQueryCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
String text, String lang
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class __$TranslateQueryCopyWithImpl<$Res>
|
||||
implements _$TranslateQueryCopyWith<$Res> {
|
||||
__$TranslateQueryCopyWithImpl(this._self, this._then);
|
||||
|
||||
final _TranslateQuery _self;
|
||||
final $Res Function(_TranslateQuery) _then;
|
||||
|
||||
/// Create a copy of TranslateQuery
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? text = null,Object? lang = null,}) {
|
||||
return _then(_TranslateQuery(
|
||||
text: null == text ? _self.text : text // ignore: cast_nullable_to_non_nullable
|
||||
as String,lang: null == lang ? _self.lang : lang // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// dart format on
|
||||
274
lib/pods/translate.g.dart
Normal file
274
lib/pods/translate.g.dart
Normal file
@@ -0,0 +1,274 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'translate.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$translateStringHash() => r'51d638cf07cbf3ffa9469298f5bd9c667bc0ccb7';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
_SystemHash._();
|
||||
|
||||
static int combine(int hash, int value) {
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + value);
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
|
||||
return hash ^ (hash >> 6);
|
||||
}
|
||||
|
||||
static int finish(int hash) {
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
|
||||
// ignore: parameter_assignments
|
||||
hash = hash ^ (hash >> 11);
|
||||
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
|
||||
}
|
||||
}
|
||||
|
||||
/// See also [translateString].
|
||||
@ProviderFor(translateString)
|
||||
const translateStringProvider = TranslateStringFamily();
|
||||
|
||||
/// See also [translateString].
|
||||
class TranslateStringFamily extends Family<AsyncValue<String>> {
|
||||
/// See also [translateString].
|
||||
const TranslateStringFamily();
|
||||
|
||||
/// See also [translateString].
|
||||
TranslateStringProvider call(TranslateQuery query) {
|
||||
return TranslateStringProvider(query);
|
||||
}
|
||||
|
||||
@override
|
||||
TranslateStringProvider getProviderOverride(
|
||||
covariant TranslateStringProvider provider,
|
||||
) {
|
||||
return call(provider.query);
|
||||
}
|
||||
|
||||
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'translateStringProvider';
|
||||
}
|
||||
|
||||
/// See also [translateString].
|
||||
class TranslateStringProvider extends AutoDisposeFutureProvider<String> {
|
||||
/// See also [translateString].
|
||||
TranslateStringProvider(TranslateQuery query)
|
||||
: this._internal(
|
||||
(ref) => translateString(ref as TranslateStringRef, query),
|
||||
from: translateStringProvider,
|
||||
name: r'translateStringProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$translateStringHash,
|
||||
dependencies: TranslateStringFamily._dependencies,
|
||||
allTransitiveDependencies:
|
||||
TranslateStringFamily._allTransitiveDependencies,
|
||||
query: query,
|
||||
);
|
||||
|
||||
TranslateStringProvider._internal(
|
||||
super._createNotifier, {
|
||||
required super.name,
|
||||
required super.dependencies,
|
||||
required super.allTransitiveDependencies,
|
||||
required super.debugGetCreateSourceHash,
|
||||
required super.from,
|
||||
required this.query,
|
||||
}) : super.internal();
|
||||
|
||||
final TranslateQuery query;
|
||||
|
||||
@override
|
||||
Override overrideWith(
|
||||
FutureOr<String> Function(TranslateStringRef provider) create,
|
||||
) {
|
||||
return ProviderOverride(
|
||||
origin: this,
|
||||
override: TranslateStringProvider._internal(
|
||||
(ref) => create(ref as TranslateStringRef),
|
||||
from: from,
|
||||
name: null,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
debugGetCreateSourceHash: null,
|
||||
query: query,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
AutoDisposeFutureProviderElement<String> createElement() {
|
||||
return _TranslateStringProviderElement(this);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is TranslateStringProvider && other.query == query;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||
hash = _SystemHash.combine(hash, query.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
mixin TranslateStringRef on AutoDisposeFutureProviderRef<String> {
|
||||
/// The parameter `query` of this provider.
|
||||
TranslateQuery get query;
|
||||
}
|
||||
|
||||
class _TranslateStringProviderElement
|
||||
extends AutoDisposeFutureProviderElement<String>
|
||||
with TranslateStringRef {
|
||||
_TranslateStringProviderElement(super.provider);
|
||||
|
||||
@override
|
||||
TranslateQuery get query => (origin as TranslateStringProvider).query;
|
||||
}
|
||||
|
||||
String _$detectStringLanguageHash() =>
|
||||
r'697b68464b3d00927cc43ccc1ba8ba93f2a470ed';
|
||||
|
||||
/// See also [detectStringLanguage].
|
||||
@ProviderFor(detectStringLanguage)
|
||||
const detectStringLanguageProvider = DetectStringLanguageFamily();
|
||||
|
||||
/// See also [detectStringLanguage].
|
||||
class DetectStringLanguageFamily extends Family<String?> {
|
||||
/// See also [detectStringLanguage].
|
||||
const DetectStringLanguageFamily();
|
||||
|
||||
/// See also [detectStringLanguage].
|
||||
DetectStringLanguageProvider call(String text) {
|
||||
return DetectStringLanguageProvider(text);
|
||||
}
|
||||
|
||||
@override
|
||||
DetectStringLanguageProvider getProviderOverride(
|
||||
covariant DetectStringLanguageProvider provider,
|
||||
) {
|
||||
return call(provider.text);
|
||||
}
|
||||
|
||||
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'detectStringLanguageProvider';
|
||||
}
|
||||
|
||||
/// See also [detectStringLanguage].
|
||||
class DetectStringLanguageProvider extends AutoDisposeProvider<String?> {
|
||||
/// See also [detectStringLanguage].
|
||||
DetectStringLanguageProvider(String text)
|
||||
: this._internal(
|
||||
(ref) => detectStringLanguage(ref as DetectStringLanguageRef, text),
|
||||
from: detectStringLanguageProvider,
|
||||
name: r'detectStringLanguageProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$detectStringLanguageHash,
|
||||
dependencies: DetectStringLanguageFamily._dependencies,
|
||||
allTransitiveDependencies:
|
||||
DetectStringLanguageFamily._allTransitiveDependencies,
|
||||
text: text,
|
||||
);
|
||||
|
||||
DetectStringLanguageProvider._internal(
|
||||
super._createNotifier, {
|
||||
required super.name,
|
||||
required super.dependencies,
|
||||
required super.allTransitiveDependencies,
|
||||
required super.debugGetCreateSourceHash,
|
||||
required super.from,
|
||||
required this.text,
|
||||
}) : super.internal();
|
||||
|
||||
final String text;
|
||||
|
||||
@override
|
||||
Override overrideWith(
|
||||
String? Function(DetectStringLanguageRef provider) create,
|
||||
) {
|
||||
return ProviderOverride(
|
||||
origin: this,
|
||||
override: DetectStringLanguageProvider._internal(
|
||||
(ref) => create(ref as DetectStringLanguageRef),
|
||||
from: from,
|
||||
name: null,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
debugGetCreateSourceHash: null,
|
||||
text: text,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
AutoDisposeProviderElement<String?> createElement() {
|
||||
return _DetectStringLanguageProviderElement(this);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is DetectStringLanguageProvider && other.text == text;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||
hash = _SystemHash.combine(hash, text.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
mixin DetectStringLanguageRef on AutoDisposeProviderRef<String?> {
|
||||
/// The parameter `text` of this provider.
|
||||
String get text;
|
||||
}
|
||||
|
||||
class _DetectStringLanguageProviderElement
|
||||
extends AutoDisposeProviderElement<String?>
|
||||
with DetectStringLanguageRef {
|
||||
_DetectStringLanguageProviderElement(super.provider);
|
||||
|
||||
@override
|
||||
String get text => (origin as DetectStringLanguageProvider).text;
|
||||
}
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
||||
@@ -1,7 +1,6 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
@@ -18,6 +17,8 @@ sealed class WebSocketState with _$WebSocketState {
|
||||
const factory WebSocketState.connected() = _Connected;
|
||||
const factory WebSocketState.connecting() = _Connecting;
|
||||
const factory WebSocketState.disconnected() = _Disconnected;
|
||||
const factory WebSocketState.serverDown() = _ServerDown;
|
||||
const factory WebSocketState.duplicateDevice() = _DuplicateDevice;
|
||||
const factory WebSocketState.error(String message) = _Error;
|
||||
}
|
||||
|
||||
@@ -46,6 +47,10 @@ class WebSocketService {
|
||||
final StreamController<WebSocketState> _statusStreamController =
|
||||
StreamController<WebSocketState>.broadcast();
|
||||
Timer? _reconnectTimer;
|
||||
Timer? _heartbeatTimer;
|
||||
|
||||
DateTime? _heartbeatAt;
|
||||
Duration? heartbeatDelay;
|
||||
|
||||
Stream<WebSocketPacket> get dataStream => _streamController.stream;
|
||||
Stream<WebSocketState> get statusStream => _statusStreamController.stream;
|
||||
@@ -71,15 +76,28 @@ class WebSocketService {
|
||||
}
|
||||
await _channel!.ready;
|
||||
_statusStreamController.sink.add(WebSocketState.connected());
|
||||
_scheduleHeartbeat();
|
||||
_channel!.stream.listen(
|
||||
(data) {
|
||||
final dataStr =
|
||||
data is Uint8List ? utf8.decode(data) : data.toString();
|
||||
final packet = WebSocketPacket.fromJson(jsonDecode(dataStr));
|
||||
if (packet.type == 'error.dupe') {
|
||||
_statusStreamController.sink.add(WebSocketState.duplicateDevice());
|
||||
_channel!.sink.close();
|
||||
return;
|
||||
}
|
||||
_streamController.sink.add(packet);
|
||||
log(
|
||||
"[WebSocket] Received packet: ${packet.type} ${packet.errorMessage}",
|
||||
);
|
||||
if (packet.type == 'pong' && _heartbeatAt != null) {
|
||||
var now = DateTime.now();
|
||||
heartbeatDelay = now.difference(_heartbeatAt!);
|
||||
log(
|
||||
"[WebSocket] Server respond last heartbeat for ${heartbeatDelay!.inMilliseconds} ms",
|
||||
);
|
||||
}
|
||||
},
|
||||
onDone: () {
|
||||
log('[WebSocket] Connection closed, attempting to reconnect...');
|
||||
@@ -108,6 +126,19 @@ class WebSocketService {
|
||||
});
|
||||
}
|
||||
|
||||
void _scheduleHeartbeat() {
|
||||
_heartbeatTimer?.cancel();
|
||||
_heartbeatTimer = Timer.periodic(const Duration(seconds: 60), (_) {
|
||||
_beatTheHeart();
|
||||
});
|
||||
}
|
||||
|
||||
void _beatTheHeart() {
|
||||
_heartbeatAt = DateTime.now();
|
||||
log('[WebSocket] We\'re beating the heart! $_heartbeatAt');
|
||||
sendMessage(jsonEncode(WebSocketPacket(type: 'ping', data: null)));
|
||||
}
|
||||
|
||||
WebSocketChannel? get ws => _channel;
|
||||
|
||||
void sendMessage(String message) {
|
||||
|
||||
@@ -61,13 +61,15 @@ extension WebSocketStatePatterns on WebSocketState {
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeMap<TResult extends Object?>({TResult Function( _Connected value)? connected,TResult Function( _Connecting value)? connecting,TResult Function( _Disconnected value)? disconnected,TResult Function( _Error value)? error,required TResult orElse(),}){
|
||||
@optionalTypeArgs TResult maybeMap<TResult extends Object?>({TResult Function( _Connected value)? connected,TResult Function( _Connecting value)? connecting,TResult Function( _Disconnected value)? disconnected,TResult Function( _ServerDown value)? serverDown,TResult Function( _DuplicateDevice value)? duplicateDevice,TResult Function( _Error value)? error,required TResult orElse(),}){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _Connected() when connected != null:
|
||||
return connected(_that);case _Connecting() when connecting != null:
|
||||
return connecting(_that);case _Disconnected() when disconnected != null:
|
||||
return disconnected(_that);case _Error() when error != null:
|
||||
return disconnected(_that);case _ServerDown() when serverDown != null:
|
||||
return serverDown(_that);case _DuplicateDevice() when duplicateDevice != null:
|
||||
return duplicateDevice(_that);case _Error() when error != null:
|
||||
return error(_that);case _:
|
||||
return orElse();
|
||||
|
||||
@@ -86,13 +88,15 @@ return error(_that);case _:
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult map<TResult extends Object?>({required TResult Function( _Connected value) connected,required TResult Function( _Connecting value) connecting,required TResult Function( _Disconnected value) disconnected,required TResult Function( _Error value) error,}){
|
||||
@optionalTypeArgs TResult map<TResult extends Object?>({required TResult Function( _Connected value) connected,required TResult Function( _Connecting value) connecting,required TResult Function( _Disconnected value) disconnected,required TResult Function( _ServerDown value) serverDown,required TResult Function( _DuplicateDevice value) duplicateDevice,required TResult Function( _Error value) error,}){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _Connected():
|
||||
return connected(_that);case _Connecting():
|
||||
return connecting(_that);case _Disconnected():
|
||||
return disconnected(_that);case _Error():
|
||||
return disconnected(_that);case _ServerDown():
|
||||
return serverDown(_that);case _DuplicateDevice():
|
||||
return duplicateDevice(_that);case _Error():
|
||||
return error(_that);}
|
||||
}
|
||||
/// A variant of `map` that fallback to returning `null`.
|
||||
@@ -107,13 +111,15 @@ return error(_that);}
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>({TResult? Function( _Connected value)? connected,TResult? Function( _Connecting value)? connecting,TResult? Function( _Disconnected value)? disconnected,TResult? Function( _Error value)? error,}){
|
||||
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>({TResult? Function( _Connected value)? connected,TResult? Function( _Connecting value)? connecting,TResult? Function( _Disconnected value)? disconnected,TResult? Function( _ServerDown value)? serverDown,TResult? Function( _DuplicateDevice value)? duplicateDevice,TResult? Function( _Error value)? error,}){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _Connected() when connected != null:
|
||||
return connected(_that);case _Connecting() when connecting != null:
|
||||
return connecting(_that);case _Disconnected() when disconnected != null:
|
||||
return disconnected(_that);case _Error() when error != null:
|
||||
return disconnected(_that);case _ServerDown() when serverDown != null:
|
||||
return serverDown(_that);case _DuplicateDevice() when duplicateDevice != null:
|
||||
return duplicateDevice(_that);case _Error() when error != null:
|
||||
return error(_that);case _:
|
||||
return null;
|
||||
|
||||
@@ -131,12 +137,14 @@ return error(_that);case _:
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>({TResult Function()? connected,TResult Function()? connecting,TResult Function()? disconnected,TResult Function( String message)? error,required TResult orElse(),}) {final _that = this;
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>({TResult Function()? connected,TResult Function()? connecting,TResult Function()? disconnected,TResult Function()? serverDown,TResult Function()? duplicateDevice,TResult Function( String message)? error,required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _Connected() when connected != null:
|
||||
return connected();case _Connecting() when connecting != null:
|
||||
return connecting();case _Disconnected() when disconnected != null:
|
||||
return disconnected();case _Error() when error != null:
|
||||
return disconnected();case _ServerDown() when serverDown != null:
|
||||
return serverDown();case _DuplicateDevice() when duplicateDevice != null:
|
||||
return duplicateDevice();case _Error() when error != null:
|
||||
return error(_that.message);case _:
|
||||
return orElse();
|
||||
|
||||
@@ -155,12 +163,14 @@ return error(_that.message);case _:
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>({required TResult Function() connected,required TResult Function() connecting,required TResult Function() disconnected,required TResult Function( String message) error,}) {final _that = this;
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>({required TResult Function() connected,required TResult Function() connecting,required TResult Function() disconnected,required TResult Function() serverDown,required TResult Function() duplicateDevice,required TResult Function( String message) error,}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _Connected():
|
||||
return connected();case _Connecting():
|
||||
return connecting();case _Disconnected():
|
||||
return disconnected();case _Error():
|
||||
return disconnected();case _ServerDown():
|
||||
return serverDown();case _DuplicateDevice():
|
||||
return duplicateDevice();case _Error():
|
||||
return error(_that.message);}
|
||||
}
|
||||
/// A variant of `when` that fallback to returning `null`
|
||||
@@ -175,12 +185,14 @@ return error(_that.message);}
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>({TResult? Function()? connected,TResult? Function()? connecting,TResult? Function()? disconnected,TResult? Function( String message)? error,}) {final _that = this;
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>({TResult? Function()? connected,TResult? Function()? connecting,TResult? Function()? disconnected,TResult? Function()? serverDown,TResult? Function()? duplicateDevice,TResult? Function( String message)? error,}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _Connected() when connected != null:
|
||||
return connected();case _Connecting() when connecting != null:
|
||||
return connecting();case _Disconnected() when disconnected != null:
|
||||
return disconnected();case _Error() when error != null:
|
||||
return disconnected();case _ServerDown() when serverDown != null:
|
||||
return serverDown();case _DuplicateDevice() when duplicateDevice != null:
|
||||
return duplicateDevice();case _Error() when error != null:
|
||||
return error(_that.message);case _:
|
||||
return null;
|
||||
|
||||
@@ -303,6 +315,82 @@ String toString({ DiagnosticLevel minLevel = DiagnosticLevel.info }) {
|
||||
|
||||
|
||||
|
||||
/// @nodoc
|
||||
|
||||
|
||||
class _ServerDown with DiagnosticableTreeMixin implements WebSocketState {
|
||||
const _ServerDown();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
properties
|
||||
..add(DiagnosticsProperty('type', 'WebSocketState.serverDown'))
|
||||
;
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _ServerDown);
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => runtimeType.hashCode;
|
||||
|
||||
@override
|
||||
String toString({ DiagnosticLevel minLevel = DiagnosticLevel.info }) {
|
||||
return 'WebSocketState.serverDown()';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// @nodoc
|
||||
|
||||
|
||||
class _DuplicateDevice with DiagnosticableTreeMixin implements WebSocketState {
|
||||
const _DuplicateDevice();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
properties
|
||||
..add(DiagnosticsProperty('type', 'WebSocketState.duplicateDevice'))
|
||||
;
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _DuplicateDevice);
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => runtimeType.hashCode;
|
||||
|
||||
@override
|
||||
String toString({ DiagnosticLevel minLevel = DiagnosticLevel.info }) {
|
||||
return 'WebSocketState.duplicateDevice()';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// @nodoc
|
||||
|
||||
|
||||
|
||||
117
lib/route.dart
117
lib/route.dart
@@ -7,6 +7,7 @@ import 'package:island/screens/developers/edit_app.dart';
|
||||
import 'package:island/screens/developers/new_app.dart';
|
||||
import 'package:island/screens/developers/hub.dart';
|
||||
import 'package:island/screens/discovery/articles.dart';
|
||||
import 'package:island/screens/posts/post_category_detail.dart';
|
||||
import 'package:island/screens/posts/post_search.dart';
|
||||
import 'package:island/widgets/app_wrapper.dart';
|
||||
import 'package:island/screens/tabs.dart';
|
||||
@@ -28,9 +29,13 @@ import 'package:island/screens/creators/hub.dart';
|
||||
import 'package:island/screens/creators/posts/post_manage_list.dart';
|
||||
import 'package:island/screens/creators/stickers/stickers.dart';
|
||||
import 'package:island/screens/creators/stickers/pack_detail.dart';
|
||||
import 'package:island/screens/stickers/marketplace.dart';
|
||||
import 'package:island/screens/stickers/pack_detail.dart';
|
||||
import 'package:island/screens/creators/poll/poll_list.dart';
|
||||
import 'package:island/screens/creators/publishers.dart';
|
||||
import 'package:island/screens/creators/webfeed/webfeed_list.dart';
|
||||
import 'package:island/screens/creators/webfeed/webfeed_edit.dart';
|
||||
import 'package:island/screens/poll/poll_editor.dart';
|
||||
import 'package:island/screens/posts/compose.dart';
|
||||
import 'package:island/screens/posts/post_detail.dart';
|
||||
import 'package:island/screens/posts/pub_profile.dart';
|
||||
@@ -144,6 +149,37 @@ final routerProvider = Provider<GoRouter>((ref) {
|
||||
return CreatorPostListScreen(pubName: name);
|
||||
},
|
||||
),
|
||||
// Poll list route
|
||||
GoRoute(
|
||||
name: 'creatorPolls',
|
||||
path: '/creators/:name/polls',
|
||||
builder: (context, state) {
|
||||
final name = state.pathParameters['name']!;
|
||||
return CreatorPollListScreen(pubName: name);
|
||||
},
|
||||
),
|
||||
// Poll routes
|
||||
GoRoute(
|
||||
name: 'creatorPollNew',
|
||||
path: '/creators/:name/polls/new',
|
||||
builder: (context, state) {
|
||||
final name = state.pathParameters['name']!;
|
||||
// initialPollId left null for create; initialPublisher prefilled
|
||||
return PollEditorScreen(initialPublisher: name);
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
name: 'creatorPollEdit',
|
||||
path: '/creators/:name/polls/:id/edit',
|
||||
builder: (context, state) {
|
||||
final name = state.pathParameters['name']!;
|
||||
final id = state.pathParameters['id']!;
|
||||
return PollEditorScreen(
|
||||
initialPollId: id,
|
||||
initialPublisher: name,
|
||||
);
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
name: 'creatorStickers',
|
||||
path: '/creators/:name/stickers',
|
||||
@@ -287,21 +323,6 @@ final routerProvider = Provider<GoRouter>((ref) {
|
||||
builder: (context, state) => const AboutScreen(),
|
||||
),
|
||||
|
||||
GoRoute(
|
||||
name: 'reportList',
|
||||
path: '/safety/reports/me',
|
||||
builder: (context, state) => const AbuseReportListScreen(),
|
||||
),
|
||||
|
||||
GoRoute(
|
||||
name: 'reportDetail',
|
||||
path: '/safety/reports/me/:id',
|
||||
builder: (context, state) {
|
||||
final id = state.pathParameters['id']!;
|
||||
return AbuseReportDetailScreen(reportId: id);
|
||||
},
|
||||
),
|
||||
|
||||
// Main tabs with TabsScreen shell
|
||||
ShellRoute(
|
||||
navigatorKey: _tabsShellKey,
|
||||
@@ -328,6 +349,25 @@ final routerProvider = Provider<GoRouter>((ref) {
|
||||
return PostDetailScreen(id: id);
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
name: 'postCategoryDetail',
|
||||
path: '/posts/categories/:slug',
|
||||
builder: (context, state) {
|
||||
final slug = state.pathParameters['slug']!;
|
||||
return PostCategoryDetailScreen(slug: slug, isCategory: true);
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
name: 'postTagDetail',
|
||||
path: '/posts/tags/:slug',
|
||||
builder: (context, state) {
|
||||
final slug = state.pathParameters['slug']!;
|
||||
return PostCategoryDetailScreen(
|
||||
slug: slug,
|
||||
isCategory: false,
|
||||
);
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
name: 'publisherProfile',
|
||||
path: '/publishers/:name',
|
||||
@@ -424,6 +464,23 @@ final routerProvider = Provider<GoRouter>((ref) {
|
||||
path: '/account',
|
||||
builder: (context, state) => const AccountScreen(),
|
||||
),
|
||||
// Sticker marketplace (user-facing, no publisher)
|
||||
GoRoute(
|
||||
name: 'stickerMarketplace',
|
||||
path: '/stickers',
|
||||
builder:
|
||||
(context, state) => const MarketplaceStickersScreen(),
|
||||
routes: [
|
||||
GoRoute(
|
||||
name: 'stickerPackDetail',
|
||||
path: ':packId',
|
||||
builder: (context, state) {
|
||||
final packId = state.pathParameters['packId']!;
|
||||
return MarketplaceStickerPackDetailScreen(id: packId);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
GoRoute(
|
||||
name: 'notifications',
|
||||
path: '/account/notifications',
|
||||
@@ -439,14 +496,6 @@ final routerProvider = Provider<GoRouter>((ref) {
|
||||
path: '/account/relationships',
|
||||
builder: (context, state) => const RelationshipScreen(),
|
||||
),
|
||||
GoRoute(
|
||||
name: 'accountProfile',
|
||||
path: '/account/:name',
|
||||
builder: (context, state) {
|
||||
final name = state.pathParameters['name']!;
|
||||
return AccountProfileScreen(name: name);
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
name: 'profileUpdate',
|
||||
path: '/account/me/update',
|
||||
@@ -462,8 +511,30 @@ final routerProvider = Provider<GoRouter>((ref) {
|
||||
path: '/account/me/settings',
|
||||
builder: (context, state) => const AccountSettingsScreen(),
|
||||
),
|
||||
GoRoute(
|
||||
name: 'reportList',
|
||||
path: '/safety/reports/me',
|
||||
builder: (context, state) => const AbuseReportListScreen(),
|
||||
),
|
||||
GoRoute(
|
||||
name: 'reportDetail',
|
||||
path: '/safety/reports/me/:id',
|
||||
builder: (context, state) {
|
||||
final id = state.pathParameters['id']!;
|
||||
return AbuseReportDetailScreen(reportId: id);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
GoRoute(
|
||||
name: 'accountProfile',
|
||||
path: '/account/:name',
|
||||
builder: (context, state) {
|
||||
final name = state.pathParameters['name']!;
|
||||
return AccountProfileScreen(name: name);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
|
||||
@@ -11,6 +11,8 @@ import 'package:island/widgets/app_scaffold.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
import 'package:island/services/update_service.dart';
|
||||
import 'package:island/widgets/content/sheet.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
@@ -93,6 +95,7 @@ class _AboutScreenState extends ConsumerState<AboutScreen> {
|
||||
final theme = Theme.of(context);
|
||||
|
||||
return AppScaffold(
|
||||
isNoBackground: false,
|
||||
appBar: AppBar(title: Text('about'.tr()), elevation: 0),
|
||||
body:
|
||||
_isLoading
|
||||
@@ -189,6 +192,45 @@ class _AboutScreenState extends ConsumerState<AboutScreen> {
|
||||
context,
|
||||
title: 'aboutScreenLinksSectionTitle'.tr(),
|
||||
children: [
|
||||
_buildListTile(
|
||||
context,
|
||||
icon: Symbols.system_update,
|
||||
title: 'Check for updates',
|
||||
onTap: () async {
|
||||
// Fetch latest release and show the unified sheet
|
||||
final svc = UpdateService();
|
||||
// Reuse service fetch + compare to decide content
|
||||
final release = await svc.fetchLatestRelease();
|
||||
if (release != null) {
|
||||
await svc.showUpdateSheet(context, release);
|
||||
} else {
|
||||
// Fallback: show a simple sheet indicating no info
|
||||
// Use your SheetScaffold for consistent styling
|
||||
// Show a minimal message
|
||||
// ignore: use_build_context_synchronously
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
useSafeArea: true,
|
||||
showDragHandle: true,
|
||||
backgroundColor:
|
||||
Theme.of(context).colorScheme.surface,
|
||||
builder:
|
||||
(_) => const SheetScaffold(
|
||||
titleText: 'Update',
|
||||
child: Center(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(24),
|
||||
child: Text(
|
||||
'Unable to fetch release info at this time.',
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
_buildListTile(
|
||||
context,
|
||||
icon: Symbols.privacy_tip,
|
||||
@@ -204,7 +246,7 @@ class _AboutScreenState extends ConsumerState<AboutScreen> {
|
||||
title: 'aboutScreenTermsOfServiceTitle'.tr(),
|
||||
onTap:
|
||||
() => _launchURL(
|
||||
'https://solsynth.dev/terms/basic-law',
|
||||
'https://solsynth.dev/terms/user-agreement',
|
||||
),
|
||||
),
|
||||
_buildListTile(
|
||||
|
||||
@@ -64,7 +64,7 @@ class AccountScreen extends HookConsumerWidget {
|
||||
}
|
||||
|
||||
return AppScaffold(
|
||||
noBackground: isWide,
|
||||
isNoBackground: isWide,
|
||||
appBar: AppBar(backgroundColor: Colors.transparent, toolbarHeight: 0),
|
||||
body: SingleChildScrollView(
|
||||
padding: getTabbedPadding(context),
|
||||
@@ -189,7 +189,6 @@ class AccountScreen extends HookConsumerWidget {
|
||||
),
|
||||
],
|
||||
).padding(horizontal: 8),
|
||||
const Gap(8),
|
||||
ListTile(
|
||||
minTileHeight: 48,
|
||||
leading: const Icon(Symbols.notifications),
|
||||
@@ -228,10 +227,20 @@ class AccountScreen extends HookConsumerWidget {
|
||||
context.pushNamed('relationships');
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
minTileHeight: 48,
|
||||
leading: const Icon(Symbols.emoji_emotions),
|
||||
trailing: const Icon(Symbols.chevron_right),
|
||||
contentPadding: EdgeInsets.symmetric(horizontal: 24),
|
||||
title: Text('stickers').tr(),
|
||||
onTap: () {
|
||||
context.pushNamed('stickerMarketplace');
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
minTileHeight: 48,
|
||||
title: Text('abuseReports').tr(),
|
||||
contentPadding: const EdgeInsets.only(left: 24, right: 17),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
leading: const Icon(Symbols.gavel),
|
||||
trailing: const Icon(Symbols.chevron_right),
|
||||
onTap: () => context.pushNamed('reportList'),
|
||||
|
||||
@@ -46,7 +46,7 @@ class EventCalanderScreen extends HookConsumerWidget {
|
||||
}
|
||||
|
||||
return AppScaffold(
|
||||
noBackground: false,
|
||||
isNoBackground: false,
|
||||
appBar: AppBar(
|
||||
leading: const PageBackButton(),
|
||||
title: Text('eventCalander').tr(),
|
||||
|
||||
@@ -280,7 +280,7 @@ class LevelingScreen extends HookConsumerWidget {
|
||||
try {
|
||||
showLoadingModal(context);
|
||||
final client = ref.watch(apiClientProvider);
|
||||
await client.post('/subscriptions/${membership.identifier}/cancel');
|
||||
await client.post('/id/subscriptions/${membership.identifier}/cancel');
|
||||
ref.invalidate(accountStellarSubscriptionProvider);
|
||||
ref.read(userInfoProvider.notifier).fetchUser();
|
||||
if (context.mounted) {
|
||||
@@ -603,7 +603,7 @@ class LevelingScreen extends HookConsumerWidget {
|
||||
try {
|
||||
showLoadingModal(context);
|
||||
final resp = await client.post(
|
||||
'/subscriptions',
|
||||
'/id/subscriptions',
|
||||
data: {
|
||||
'identifier': tierId,
|
||||
'payment_method': 'solian.wallet',
|
||||
@@ -615,7 +615,7 @@ class LevelingScreen extends HookConsumerWidget {
|
||||
final subscription = SnWalletSubscription.fromJson(resp.data);
|
||||
if (subscription.status == 1) return;
|
||||
final orderResp = await client.post(
|
||||
'/subscriptions/${subscription.identifier}/order',
|
||||
'/id/subscriptions/${subscription.identifier}/order',
|
||||
);
|
||||
final order = SnWalletOrder.fromJson(orderResp.data);
|
||||
|
||||
@@ -633,7 +633,7 @@ class LevelingScreen extends HookConsumerWidget {
|
||||
|
||||
if (paidOrder != null) {
|
||||
await client.post(
|
||||
'/subscriptions/order/handle',
|
||||
'/id/subscriptions/order/handle',
|
||||
data: {'order_id': paidOrder.id},
|
||||
);
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ part of 'leveling.dart';
|
||||
// **************************************************************************
|
||||
|
||||
String _$accountStellarSubscriptionHash() =>
|
||||
r'37fb821460e3ac50b5cf777c933b6779f732daee';
|
||||
r'80abcdefb3868775fd8fe3c980215713efff5948';
|
||||
|
||||
/// See also [accountStellarSubscription].
|
||||
@ProviderFor(accountStellarSubscription)
|
||||
|
||||
@@ -12,6 +12,7 @@ import 'package:island/pods/event_calendar.dart';
|
||||
import 'package:island/pods/network.dart';
|
||||
import 'package:island/pods/userinfo.dart';
|
||||
import 'package:island/services/color.dart';
|
||||
import 'package:island/services/responsive.dart';
|
||||
import 'package:island/services/time.dart';
|
||||
import 'package:island/services/timezone/native.dart';
|
||||
import 'package:island/widgets/account/account_name.dart';
|
||||
@@ -22,10 +23,12 @@ import 'package:island/widgets/account/status.dart';
|
||||
import 'package:island/widgets/alert.dart';
|
||||
import 'package:island/widgets/app_scaffold.dart';
|
||||
import 'package:island/widgets/content/cloud_files.dart';
|
||||
import 'package:island/widgets/content/markdown.dart';
|
||||
import 'package:island/widgets/safety/abuse_report_helper.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:palette_generator/palette_generator.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
|
||||
part 'profile.g.dart';
|
||||
@@ -248,294 +251,402 @@ class AccountProfileScreen extends HookConsumerWidget {
|
||||
|
||||
final user = ref.watch(userInfoProvider);
|
||||
|
||||
Widget accountBasicInfo(SnAccount data) => Padding(
|
||||
padding: const EdgeInsets.fromLTRB(24, 24, 24, 8),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
ProfilePictureWidget(file: data.profile.picture, radius: 32),
|
||||
const Gap(20),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
AccountName(account: data, style: TextStyle(fontSize: 20)),
|
||||
const Gap(6),
|
||||
Flexible(
|
||||
child: Text(
|
||||
'@${data.name}',
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
).fontSize(14).opacity(0.85),
|
||||
),
|
||||
],
|
||||
),
|
||||
AccountStatusWidget(uname: name, padding: EdgeInsets.zero),
|
||||
],
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
SharePlus.instance.share(
|
||||
ShareParams(
|
||||
uri: Uri.parse('https://id.solian.app/@${data.name}'),
|
||||
),
|
||||
);
|
||||
},
|
||||
icon: const Icon(Symbols.share),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
Widget accountProfileBio(SnAccount data) => Card(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('bio').tr().bold().fontSize(15).padding(bottom: 8),
|
||||
if (data.profile.bio.isEmpty)
|
||||
Text('descriptionNone').tr().italic()
|
||||
else
|
||||
MarkdownTextContent(
|
||||
content: data.profile.bio,
|
||||
linesMargin: EdgeInsets.zero,
|
||||
),
|
||||
],
|
||||
).padding(horizontal: 24, vertical: 20),
|
||||
);
|
||||
|
||||
Widget accountProfileDetail(SnAccount data) => Card(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
spacing: 24,
|
||||
children: [
|
||||
if (buildSubcolumn(data).isNotEmpty)
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
spacing: 2,
|
||||
children: buildSubcolumn(data),
|
||||
),
|
||||
if (data.profile.timeZone.isNotEmpty)
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('timeZone').tr().bold(),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.baseline,
|
||||
textBaseline: TextBaseline.alphabetic,
|
||||
spacing: 6,
|
||||
children: [
|
||||
Text(data.profile.timeZone),
|
||||
Text(
|
||||
getTzInfo(
|
||||
data.profile.timeZone,
|
||||
).$2.formatCustomGlobal('HH:mm'),
|
||||
),
|
||||
Text(
|
||||
getTzInfo(data.profile.timeZone).$1.formatOffsetLocal(),
|
||||
).fontSize(11),
|
||||
Text(
|
||||
'UTC${getTzInfo(data.profile.timeZone).$1.formatOffset()}',
|
||||
).fontSize(11).opacity(0.75),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
).padding(horizontal: 24, vertical: 16),
|
||||
);
|
||||
|
||||
Widget accountAction(SnAccount data) => Card(
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
spacing: 8,
|
||||
children: [
|
||||
if (accountRelationship.value == null ||
|
||||
accountRelationship.value!.status > -100)
|
||||
Expanded(
|
||||
child: FilledButton.icon(
|
||||
style: ButtonStyle(
|
||||
backgroundColor: WidgetStatePropertyAll(
|
||||
accountRelationship.value == null
|
||||
? null
|
||||
: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
foregroundColor: WidgetStatePropertyAll(
|
||||
accountRelationship.value == null
|
||||
? null
|
||||
: Theme.of(context).colorScheme.onSecondary,
|
||||
),
|
||||
),
|
||||
onPressed: relationshipAction,
|
||||
label:
|
||||
Text(
|
||||
accountRelationship.value == null
|
||||
? 'addFriendShort'
|
||||
: 'added',
|
||||
).tr(),
|
||||
icon:
|
||||
accountRelationship.value == null
|
||||
? const Icon(Symbols.person_add)
|
||||
: const Icon(Symbols.person_check),
|
||||
),
|
||||
),
|
||||
if (accountRelationship.value == null ||
|
||||
accountRelationship.value!.status <= -100)
|
||||
Expanded(
|
||||
child: FilledButton.icon(
|
||||
style: ButtonStyle(
|
||||
backgroundColor: WidgetStatePropertyAll(
|
||||
accountRelationship.value == null
|
||||
? null
|
||||
: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
foregroundColor: WidgetStatePropertyAll(
|
||||
accountRelationship.value == null
|
||||
? null
|
||||
: Theme.of(context).colorScheme.onSecondary,
|
||||
),
|
||||
),
|
||||
onPressed: blockAction,
|
||||
label:
|
||||
Text(
|
||||
accountRelationship.value == null
|
||||
? 'blockUser'
|
||||
: 'unblockUser',
|
||||
).tr(),
|
||||
icon:
|
||||
accountRelationship.value == null
|
||||
? const Icon(Symbols.block)
|
||||
: const Icon(Symbols.person_cancel),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
spacing: 8,
|
||||
children: [
|
||||
Expanded(
|
||||
child: FilledButton.icon(
|
||||
onPressed: directMessageAction,
|
||||
icon: const Icon(Symbols.message),
|
||||
label:
|
||||
Text(
|
||||
accountChat.value == null
|
||||
? 'createDirectMessage'
|
||||
: 'gotoDirectMessage',
|
||||
maxLines: 1,
|
||||
).tr(),
|
||||
),
|
||||
),
|
||||
IconButton.filled(
|
||||
onPressed: () {
|
||||
showAbuseReportSheet(
|
||||
context,
|
||||
resourceIdentifier: 'account/${data.id}',
|
||||
);
|
||||
},
|
||||
icon: Icon(
|
||||
Symbols.flag,
|
||||
color: Theme.of(context).colorScheme.onError,
|
||||
),
|
||||
style: ButtonStyle(
|
||||
backgroundColor: WidgetStatePropertyAll(
|
||||
Theme.of(context).colorScheme.error,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
).padding(horizontal: 16, vertical: 8),
|
||||
);
|
||||
|
||||
return account.when(
|
||||
data:
|
||||
(data) => AppScaffold(
|
||||
body: CustomScrollView(
|
||||
slivers: [
|
||||
SliverAppBar(
|
||||
foregroundColor: appbarColor.value,
|
||||
expandedHeight: 180,
|
||||
pinned: true,
|
||||
leading: PageBackButton(
|
||||
color: appbarColor.value,
|
||||
shadows: [appbarShadow],
|
||||
),
|
||||
flexibleSpace: Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child:
|
||||
data.profile.background?.id != null
|
||||
? CloudImageWidget(
|
||||
file: data.profile.background,
|
||||
)
|
||||
: Container(
|
||||
color:
|
||||
Theme.of(
|
||||
context,
|
||||
).appBarTheme.backgroundColor,
|
||||
),
|
||||
isNoBackground: false,
|
||||
appBar:
|
||||
isWideScreen(context)
|
||||
? AppBar(
|
||||
foregroundColor: appbarColor.value,
|
||||
leading: PageBackButton(
|
||||
color: appbarColor.value,
|
||||
shadows: [appbarShadow],
|
||||
),
|
||||
FlexibleSpaceBar(
|
||||
title: Text(
|
||||
data.nick,
|
||||
style: TextStyle(
|
||||
color:
|
||||
appbarColor.value ??
|
||||
Theme.of(context).appBarTheme.foregroundColor,
|
||||
shadows: [appbarShadow],
|
||||
flexibleSpace: Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child:
|
||||
data.profile.background?.id != null
|
||||
? CloudImageWidget(
|
||||
file: data.profile.background,
|
||||
)
|
||||
: Container(
|
||||
color:
|
||||
Theme.of(
|
||||
context,
|
||||
).appBarTheme.backgroundColor,
|
||||
),
|
||||
),
|
||||
FlexibleSpaceBar(
|
||||
title: Text(
|
||||
data.nick,
|
||||
style: TextStyle(
|
||||
color:
|
||||
appbarColor.value ??
|
||||
Theme.of(
|
||||
context,
|
||||
).appBarTheme.foregroundColor,
|
||||
shadows: [appbarShadow],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
: null,
|
||||
body:
|
||||
isWideScreen(context)
|
||||
? Row(
|
||||
children: [
|
||||
Flexible(
|
||||
child: CustomScrollView(
|
||||
slivers: [
|
||||
SliverToBoxAdapter(child: accountBasicInfo(data)),
|
||||
if (data.badges.isNotEmpty)
|
||||
SliverToBoxAdapter(
|
||||
child: BadgeList(
|
||||
badges: data.badges,
|
||||
).padding(horizontal: 24, bottom: 24),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: Column(
|
||||
spacing: 12,
|
||||
children: [
|
||||
LevelingProgressCard(
|
||||
level: data.profile.level,
|
||||
experience: data.profile.experience,
|
||||
progress: data.profile.levelingProgress,
|
||||
),
|
||||
if (data.profile.verification != null)
|
||||
Card(
|
||||
child: VerificationStatusCard(
|
||||
mark: data.profile.verification!,
|
||||
),
|
||||
),
|
||||
],
|
||||
).padding(horizontal: 4, top: 8),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: accountProfileBio(data).padding(top: 4),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: accountProfileDetail(data),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(24, 24, 24, 8),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
ProfilePictureWidget(
|
||||
file: data.profile.picture,
|
||||
radius: 32,
|
||||
),
|
||||
const Gap(20),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
AccountName(
|
||||
account: data,
|
||||
style: TextStyle(fontSize: 20),
|
||||
Flexible(
|
||||
child: CustomScrollView(
|
||||
slivers: [
|
||||
SliverGap(24),
|
||||
if (user.value != null)
|
||||
SliverToBoxAdapter(child: accountAction(data)),
|
||||
SliverToBoxAdapter(
|
||||
child: Card(
|
||||
child: FortuneGraphWidget(
|
||||
events: accountEvents,
|
||||
eventCalanderUser: data.name,
|
||||
margin: EdgeInsets.zero,
|
||||
),
|
||||
const Gap(6),
|
||||
Text(
|
||||
'@${data.name}',
|
||||
).fontSize(14).opacity(0.85),
|
||||
],
|
||||
),
|
||||
AccountStatusWidget(
|
||||
uname: name,
|
||||
padding: EdgeInsets.zero,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
if (data.badges.isNotEmpty)
|
||||
SliverToBoxAdapter(
|
||||
child: BadgeList(
|
||||
badges: data.badges,
|
||||
).padding(horizontal: 24, bottom: 24),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: Column(
|
||||
spacing: 12,
|
||||
children: [
|
||||
LevelingProgressCard(
|
||||
level: data.profile.level,
|
||||
experience: data.profile.experience,
|
||||
progress: data.profile.levelingProgress,
|
||||
),
|
||||
if (data.profile.verification != null)
|
||||
VerificationStatusCard(
|
||||
mark: data.profile.verification!,
|
||||
),
|
||||
],
|
||||
).padding(horizontal: 20),
|
||||
),
|
||||
|
||||
SliverToBoxAdapter(
|
||||
child: const Divider(height: 1).padding(vertical: 24),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
spacing: 24,
|
||||
children: [
|
||||
if (buildSubcolumn(data).isNotEmpty)
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
spacing: 2,
|
||||
children: buildSubcolumn(data),
|
||||
),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('bio').tr().bold(),
|
||||
Text(
|
||||
data.profile.bio.isEmpty
|
||||
? 'descriptionNone'.tr()
|
||||
: data.profile.bio,
|
||||
).padding(horizontal: 24)
|
||||
: CustomScrollView(
|
||||
slivers: [
|
||||
SliverAppBar(
|
||||
foregroundColor: appbarColor.value,
|
||||
expandedHeight: 180,
|
||||
pinned: true,
|
||||
leading: PageBackButton(
|
||||
color: appbarColor.value,
|
||||
shadows: [appbarShadow],
|
||||
),
|
||||
],
|
||||
),
|
||||
if (data.profile.timeZone.isNotEmpty)
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('timeZone').tr().bold(),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.baseline,
|
||||
textBaseline: TextBaseline.alphabetic,
|
||||
spacing: 6,
|
||||
children: [
|
||||
Text(data.profile.timeZone),
|
||||
Text(
|
||||
getTzInfo(
|
||||
data.profile.timeZone,
|
||||
).$2.formatCustomGlobal('HH:mm'),
|
||||
),
|
||||
Text(
|
||||
getTzInfo(
|
||||
data.profile.timeZone,
|
||||
).$1.formatOffsetLocal(),
|
||||
).fontSize(11),
|
||||
Text(
|
||||
'UTC${getTzInfo(data.profile.timeZone).$1.formatOffset()}',
|
||||
).fontSize(11).opacity(0.75),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
).padding(horizontal: 24),
|
||||
),
|
||||
|
||||
if (user.value != null)
|
||||
SliverToBoxAdapter(
|
||||
child: const Divider(
|
||||
height: 1,
|
||||
).padding(top: 24, bottom: 12),
|
||||
),
|
||||
if (user.value != null)
|
||||
SliverToBoxAdapter(
|
||||
child: Row(
|
||||
spacing: 8,
|
||||
children: [
|
||||
if (accountRelationship.value == null ||
|
||||
accountRelationship.value!.status > -100)
|
||||
Expanded(
|
||||
child: FilledButton.icon(
|
||||
style: ButtonStyle(
|
||||
backgroundColor: WidgetStatePropertyAll(
|
||||
accountRelationship.value == null
|
||||
? null
|
||||
: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
foregroundColor: WidgetStatePropertyAll(
|
||||
accountRelationship.value == null
|
||||
? null
|
||||
: Theme.of(
|
||||
context,
|
||||
).colorScheme.onSecondary,
|
||||
flexibleSpace: Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child:
|
||||
data.profile.background?.id != null
|
||||
? CloudImageWidget(
|
||||
file: data.profile.background,
|
||||
)
|
||||
: Container(
|
||||
color:
|
||||
Theme.of(
|
||||
context,
|
||||
).appBarTheme.backgroundColor,
|
||||
),
|
||||
),
|
||||
FlexibleSpaceBar(
|
||||
title: Text(
|
||||
data.nick,
|
||||
style: TextStyle(
|
||||
color:
|
||||
appbarColor.value ??
|
||||
Theme.of(
|
||||
context,
|
||||
).appBarTheme.foregroundColor,
|
||||
shadows: [appbarShadow],
|
||||
),
|
||||
),
|
||||
),
|
||||
onPressed: relationshipAction,
|
||||
label:
|
||||
Text(
|
||||
accountRelationship.value == null
|
||||
? 'addFriendShort'
|
||||
: 'added',
|
||||
).tr(),
|
||||
icon:
|
||||
accountRelationship.value == null
|
||||
? const Icon(Symbols.person_add)
|
||||
: const Icon(Symbols.person_check),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (accountRelationship.value == null ||
|
||||
accountRelationship.value!.status <= -100)
|
||||
Expanded(
|
||||
child: FilledButton.icon(
|
||||
style: ButtonStyle(
|
||||
backgroundColor: WidgetStatePropertyAll(
|
||||
accountRelationship.value == null
|
||||
? null
|
||||
: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
foregroundColor: WidgetStatePropertyAll(
|
||||
accountRelationship.value == null
|
||||
? null
|
||||
: Theme.of(
|
||||
context,
|
||||
).colorScheme.onSecondary,
|
||||
),
|
||||
),
|
||||
onPressed: blockAction,
|
||||
label:
|
||||
Text(
|
||||
accountRelationship.value == null
|
||||
? 'blockUser'
|
||||
: 'unblockUser',
|
||||
).tr(),
|
||||
icon:
|
||||
accountRelationship.value == null
|
||||
? const Icon(Symbols.block)
|
||||
: const Icon(Symbols.person_cancel),
|
||||
),
|
||||
),
|
||||
SliverToBoxAdapter(child: accountBasicInfo(data)),
|
||||
if (data.badges.isNotEmpty)
|
||||
SliverToBoxAdapter(
|
||||
child: BadgeList(
|
||||
badges: data.badges,
|
||||
).padding(horizontal: 24, bottom: 24),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: Column(
|
||||
children: [
|
||||
LevelingProgressCard(
|
||||
level: data.profile.level,
|
||||
experience: data.profile.experience,
|
||||
progress: data.profile.levelingProgress,
|
||||
).padding(top: 8, horizontal: 8, bottom: 4),
|
||||
if (data.profile.verification != null)
|
||||
Card(
|
||||
child: VerificationStatusCard(
|
||||
mark: data.profile.verification!,
|
||||
),
|
||||
).padding(horizontal: 4),
|
||||
],
|
||||
),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: accountProfileBio(data).padding(horizontal: 4),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: accountProfileDetail(
|
||||
data,
|
||||
).padding(horizontal: 4),
|
||||
),
|
||||
if (user.value != null)
|
||||
SliverToBoxAdapter(
|
||||
child: accountAction(data).padding(horizontal: 4),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: Card(
|
||||
child: FortuneGraphWidget(
|
||||
events: accountEvents,
|
||||
eventCalanderUser: data.name,
|
||||
),
|
||||
).padding(horizontal: 4),
|
||||
),
|
||||
],
|
||||
).padding(horizontal: 16),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: Row(
|
||||
spacing: 8,
|
||||
children: [
|
||||
Expanded(
|
||||
child: FilledButton.icon(
|
||||
onPressed: directMessageAction,
|
||||
icon: const Icon(Symbols.message),
|
||||
label:
|
||||
Text(
|
||||
accountChat.value == null
|
||||
? 'createDirectMessage'
|
||||
: 'gotoDirectMessage',
|
||||
maxLines: 1,
|
||||
).tr(),
|
||||
),
|
||||
),
|
||||
IconButton.filled(
|
||||
onPressed: () {
|
||||
showAbuseReportSheet(
|
||||
context,
|
||||
resourceIdentifier: 'account/${data.id}',
|
||||
);
|
||||
},
|
||||
icon: Icon(
|
||||
Symbols.flag,
|
||||
color: Theme.of(context).colorScheme.onError,
|
||||
),
|
||||
style: ButtonStyle(
|
||||
backgroundColor: WidgetStatePropertyAll(
|
||||
Theme.of(context).colorScheme.error,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
).padding(horizontal: 16, top: 4),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: const Divider(height: 1).padding(top: 12),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: Column(
|
||||
children: [
|
||||
FortuneGraphWidget(
|
||||
events: accountEvents,
|
||||
eventCalanderUser: data.name,
|
||||
),
|
||||
],
|
||||
).padding(all: 8),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
error:
|
||||
(error, stackTrace) => AppScaffold(
|
||||
|
||||
@@ -73,7 +73,7 @@ class CreateAccountScreen extends HookConsumerWidget {
|
||||
}
|
||||
|
||||
return AppScaffold(
|
||||
noBackground: false,
|
||||
isNoBackground: false,
|
||||
appBar: AppBar(
|
||||
leading: const PageBackButton(),
|
||||
title: Text('createAccount').tr(),
|
||||
|
||||
@@ -55,7 +55,7 @@ class LoginScreen extends HookConsumerWidget {
|
||||
final factorPicked = useState<SnAuthFactor?>(null);
|
||||
|
||||
return AppScaffold(
|
||||
noBackground: false,
|
||||
isNoBackground: false,
|
||||
appBar: AppBar(
|
||||
leading: const PageBackButton(),
|
||||
title: Text('login').tr(),
|
||||
|
||||
@@ -80,7 +80,7 @@ class _OidcScreenState extends ConsumerState<OidcScreen> {
|
||||
: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36',
|
||||
),
|
||||
initialUrlRequest: URLRequest(
|
||||
url: WebUri('$serverUrl/auth/login/${widget.provider}'),
|
||||
url: WebUri('$serverUrl/id/auth/login/${widget.provider}'),
|
||||
headers: {
|
||||
if (token?.token.isNotEmpty ?? false)
|
||||
'Authorization': 'AtField ${token!.token}',
|
||||
@@ -120,7 +120,7 @@ class _OidcScreenState extends ConsumerState<OidcScreen> {
|
||||
final queryParams = url.queryParameters;
|
||||
|
||||
// Check if we're on the token page
|
||||
if (path.endsWith('/id/auth/callback')) {
|
||||
if (path.endsWith('/auth/callback')) {
|
||||
// Extract token from URL
|
||||
final challenge = queryParams['challenge'];
|
||||
// Return the token and close the webview
|
||||
@@ -205,7 +205,7 @@ class _OidcScreenState extends ConsumerState<OidcScreen> {
|
||||
onPressed: () {
|
||||
if (currentUrl != null) {
|
||||
Clipboard.setData(ClipboardData(text: currentUrl!));
|
||||
showSnackBar('copyToClipboard');
|
||||
showSnackBar('copyToClipboard'.tr());
|
||||
}
|
||||
},
|
||||
),
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/material.dart' hide ConnectionState;
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:island/pods/call.dart';
|
||||
import 'package:island/services/responsive.dart';
|
||||
import 'package:island/widgets/app_scaffold.dart';
|
||||
import 'package:island/widgets/chat/call_button.dart';
|
||||
import 'package:island/widgets/chat/call_overlay.dart';
|
||||
import 'package:island/widgets/chat/call_participant_tile.dart';
|
||||
import 'package:island/widgets/alert.dart';
|
||||
import 'package:livekit_client/livekit_client.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
@@ -21,17 +23,39 @@ class CallScreen extends HookConsumerWidget {
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final ongoingCall = ref.watch(ongoingCallProvider(roomId));
|
||||
final callState = ref.watch(callNotifierProvider);
|
||||
final callNotifier = ref.read(callNotifierProvider.notifier);
|
||||
final callNotifier = ref.watch(callNotifierProvider.notifier);
|
||||
|
||||
useEffect(() {
|
||||
callNotifier.joinRoom(roomId);
|
||||
log('[Call] Joining the call...');
|
||||
callNotifier.joinRoom(roomId).catchError((_) {
|
||||
showConfirmAlert(
|
||||
'Seems there already has a call connected, do you want override it?',
|
||||
'Call already connected',
|
||||
).then((value) {
|
||||
if (value != true) return;
|
||||
log('[Call] Joining the call... with overrides');
|
||||
callNotifier.disconnect();
|
||||
callNotifier.dispose();
|
||||
callNotifier.joinRoom(roomId);
|
||||
});
|
||||
});
|
||||
return null;
|
||||
}, []);
|
||||
|
||||
final viewMode = useState<String>('grid');
|
||||
final allAudioOnly = callNotifier.participants.every(
|
||||
(p) =>
|
||||
!(p.hasVideo &&
|
||||
p.remoteParticipant.trackPublications.values.any(
|
||||
(pub) =>
|
||||
pub.track != null &&
|
||||
pub.kind == TrackType.VIDEO &&
|
||||
!pub.muted &&
|
||||
!pub.isDisposed,
|
||||
)),
|
||||
);
|
||||
|
||||
return AppScaffold(
|
||||
noBackground: false,
|
||||
isNoBackground: false,
|
||||
appBar: AppBar(
|
||||
leading: PageBackButton(),
|
||||
title: Column(
|
||||
@@ -44,45 +68,55 @@ class CallScreen extends HookConsumerWidget {
|
||||
Text(
|
||||
callState.isConnected
|
||||
? formatDuration(callState.duration)
|
||||
: 'Connecting',
|
||||
: (switch (callNotifier.room?.connectionState) {
|
||||
ConnectionState.connected => 'connected',
|
||||
ConnectionState.connecting => 'connecting',
|
||||
ConnectionState.reconnecting => 'reconnecting',
|
||||
_ => 'disconnected',
|
||||
}).tr(),
|
||||
style: const TextStyle(fontSize: 14),
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
IconButton(
|
||||
icon: Icon(Symbols.grid_view),
|
||||
tooltip: 'Grid View',
|
||||
onPressed: () => viewMode.value = 'grid',
|
||||
color:
|
||||
viewMode.value == 'grid'
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: null,
|
||||
if (!allAudioOnly)
|
||||
SingleChildScrollView(
|
||||
child: Row(
|
||||
spacing: 4,
|
||||
children: [
|
||||
for (final live in callNotifier.participants)
|
||||
SpeakingRippleAvatar(live: live, size: 30),
|
||||
const Gap(8),
|
||||
],
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(Symbols.view_agenda),
|
||||
tooltip: 'Stage View',
|
||||
onPressed: () => viewMode.value = 'stage',
|
||||
color:
|
||||
viewMode.value == 'stage'
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: null,
|
||||
),
|
||||
],
|
||||
),
|
||||
const Gap(8),
|
||||
),
|
||||
],
|
||||
),
|
||||
body:
|
||||
callState.error != null
|
||||
? Center(
|
||||
child: Text(
|
||||
callState.error!,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(color: Colors.red),
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 320),
|
||||
child: Column(
|
||||
children: [
|
||||
const Icon(Symbols.error_outline, size: 48),
|
||||
const Gap(4),
|
||||
Text(
|
||||
callState.error!,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(color: Color(0xFF757575)),
|
||||
),
|
||||
const Gap(8),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
callNotifier.disconnect();
|
||||
callNotifier.dispose();
|
||||
callNotifier.joinRoom(roomId);
|
||||
},
|
||||
child: Text('retry').tr(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
: Column(
|
||||
@@ -100,17 +134,8 @@ class CallScreen extends HookConsumerWidget {
|
||||
child: Text('No participants in call'),
|
||||
);
|
||||
}
|
||||
|
||||
final participants = callNotifier.participants;
|
||||
final allAudioOnly = participants.every(
|
||||
(p) =>
|
||||
!(p.hasVideo &&
|
||||
p.remoteParticipant.trackPublications.values
|
||||
.any(
|
||||
(pub) =>
|
||||
pub.track != null &&
|
||||
pub.kind == TrackType.VIDEO,
|
||||
)),
|
||||
);
|
||||
if (allAudioOnly) {
|
||||
// Audio-only: show avatars in a compact row
|
||||
return Center(
|
||||
@@ -123,138 +148,41 @@ class CallScreen extends HookConsumerWidget {
|
||||
runSpacing: 8,
|
||||
children: [
|
||||
for (final live in participants)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8,
|
||||
),
|
||||
child: SpeakingRippleAvatar(
|
||||
isSpeaking: live.isSpeaking,
|
||||
audioLevel:
|
||||
live.remoteParticipant.audioLevel,
|
||||
pictureId:
|
||||
live
|
||||
.participant
|
||||
.profile
|
||||
?.account
|
||||
.profile
|
||||
.picture
|
||||
?.id,
|
||||
size: 72,
|
||||
),
|
||||
),
|
||||
SpeakingRippleAvatar(
|
||||
live: live,
|
||||
size: 72,
|
||||
).padding(horizontal: 4),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
if (viewMode.value == 'stage') {
|
||||
// Stage view: show main speaker(s) large, others in row
|
||||
final mainSpeakers =
|
||||
participants
|
||||
.where(
|
||||
(p) => p
|
||||
.remoteParticipant
|
||||
.trackPublications
|
||||
.values
|
||||
.any(
|
||||
(pub) =>
|
||||
pub.track != null &&
|
||||
pub.kind == TrackType.VIDEO,
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
if (mainSpeakers.isEmpty && participants.isNotEmpty) {
|
||||
mainSpeakers.add(participants.first);
|
||||
}
|
||||
final others =
|
||||
participants
|
||||
.where((p) => !mainSpeakers.contains(p))
|
||||
.toList();
|
||||
return Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
for (final speaker in mainSpeakers)
|
||||
Expanded(
|
||||
child:
|
||||
AspectRatio(
|
||||
aspectRatio: 16 / 9,
|
||||
child: Card(
|
||||
margin: EdgeInsets.zero,
|
||||
child: ClipRRect(
|
||||
borderRadius:
|
||||
BorderRadius.circular(8),
|
||||
child: Column(
|
||||
children: [
|
||||
CallParticipantTile(
|
||||
live: speaker,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
).center(),
|
||||
|
||||
// Stage view: show main speaker(s) large, others in row
|
||||
final mainSpeakers =
|
||||
participants
|
||||
.where(
|
||||
(p) => p
|
||||
.remoteParticipant
|
||||
.trackPublications
|
||||
.values
|
||||
.any(
|
||||
(pub) =>
|
||||
pub.track != null &&
|
||||
pub.kind == TrackType.VIDEO,
|
||||
),
|
||||
],
|
||||
).padding(horizontal: 12),
|
||||
),
|
||||
if (others.isNotEmpty)
|
||||
SizedBox(
|
||||
height: 100,
|
||||
child: ListView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
children: [
|
||||
for (final other in others)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8,
|
||||
),
|
||||
child: CallParticipantTile(
|
||||
live: other,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
)
|
||||
.toList();
|
||||
if (mainSpeakers.isEmpty && participants.isNotEmpty) {
|
||||
mainSpeakers.add(participants.first);
|
||||
}
|
||||
// Default: grid view
|
||||
return GridView.builder(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 8,
|
||||
),
|
||||
gridDelegate:
|
||||
SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount:
|
||||
isWidestScreen(context)
|
||||
? 4
|
||||
: isWiderScreen(context)
|
||||
? 3
|
||||
: 2,
|
||||
childAspectRatio: 16 / 9,
|
||||
crossAxisSpacing: 8,
|
||||
mainAxisSpacing: 8,
|
||||
return Column(
|
||||
children: [
|
||||
for (final speaker in mainSpeakers)
|
||||
Expanded(
|
||||
child: CallParticipantTile(live: speaker),
|
||||
),
|
||||
itemCount: participants.length,
|
||||
itemBuilder: (context, idx) {
|
||||
final live = participants[idx];
|
||||
return AspectRatio(
|
||||
aspectRatio: 16 / 9,
|
||||
child: Card(
|
||||
margin: EdgeInsets.zero,
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Column(
|
||||
children: [CallParticipantTile(live: live)],
|
||||
),
|
||||
),
|
||||
),
|
||||
).center();
|
||||
},
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
@@ -21,7 +21,6 @@ import 'package:island/services/responsive.dart';
|
||||
import 'package:island/widgets/account/account_picker.dart';
|
||||
import 'package:island/widgets/alert.dart';
|
||||
import 'package:island/widgets/app_scaffold.dart';
|
||||
import 'package:island/widgets/chat/call_overlay.dart';
|
||||
import 'package:island/widgets/content/cloud_files.dart';
|
||||
import 'package:island/widgets/content/sheet.dart';
|
||||
import 'package:island/widgets/realms/selection_dropdown.dart';
|
||||
@@ -346,91 +345,79 @@ class ChatListScreen extends HookConsumerWidget {
|
||||
child: const Icon(Symbols.add),
|
||||
),
|
||||
floatingActionButtonLocation: TabbedFabLocation(context),
|
||||
body: Stack(
|
||||
body: Column(
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
Consumer(
|
||||
builder: (context, ref, _) {
|
||||
final summaryState = ref.watch(chatSummaryProvider);
|
||||
return summaryState.maybeWhen(
|
||||
loading:
|
||||
() => const LinearProgressIndicator(
|
||||
minHeight: 2,
|
||||
borderRadius: BorderRadius.zero,
|
||||
),
|
||||
orElse: () => const SizedBox.shrink(),
|
||||
);
|
||||
},
|
||||
),
|
||||
Expanded(
|
||||
child: chats.when(
|
||||
data:
|
||||
(items) => RefreshIndicator(
|
||||
onRefresh:
|
||||
() => Future.sync(() {
|
||||
ref.invalidate(chatroomsJoinedProvider);
|
||||
}),
|
||||
child: ListView.builder(
|
||||
padding: getTabbedPadding(
|
||||
context,
|
||||
bottom: callState.isConnected ? 96 : null,
|
||||
),
|
||||
itemCount:
|
||||
items
|
||||
.where(
|
||||
(item) =>
|
||||
selectedTab.value == 0 ||
|
||||
(selectedTab.value == 1 &&
|
||||
item.type == 1) ||
|
||||
(selectedTab.value == 2 &&
|
||||
item.type != 1),
|
||||
)
|
||||
.length,
|
||||
itemBuilder: (context, index) {
|
||||
final filteredItems =
|
||||
items
|
||||
.where(
|
||||
(item) =>
|
||||
selectedTab.value == 0 ||
|
||||
(selectedTab.value == 1 &&
|
||||
item.type == 1) ||
|
||||
(selectedTab.value == 2 &&
|
||||
item.type != 1),
|
||||
)
|
||||
.toList();
|
||||
final item = filteredItems[index];
|
||||
return ChatRoomListTile(
|
||||
room: item,
|
||||
isDirect: item.type == 1,
|
||||
onTap: () {
|
||||
context.pushNamed(
|
||||
'chatRoom',
|
||||
pathParameters: {'id': item.id},
|
||||
);
|
||||
},
|
||||
Consumer(
|
||||
builder: (context, ref, _) {
|
||||
final summaryState = ref.watch(chatSummaryProvider);
|
||||
return summaryState.maybeWhen(
|
||||
loading:
|
||||
() => const LinearProgressIndicator(
|
||||
minHeight: 2,
|
||||
borderRadius: BorderRadius.zero,
|
||||
),
|
||||
orElse: () => const SizedBox.shrink(),
|
||||
);
|
||||
},
|
||||
),
|
||||
Expanded(
|
||||
child: chats.when(
|
||||
data:
|
||||
(items) => RefreshIndicator(
|
||||
onRefresh:
|
||||
() => Future.sync(() {
|
||||
ref.invalidate(chatroomsJoinedProvider);
|
||||
}),
|
||||
child: ListView.builder(
|
||||
padding: getTabbedPadding(
|
||||
context,
|
||||
bottom: callState.isConnected ? 96 : null,
|
||||
),
|
||||
itemCount:
|
||||
items
|
||||
.where(
|
||||
(item) =>
|
||||
selectedTab.value == 0 ||
|
||||
(selectedTab.value == 1 &&
|
||||
item.type == 1) ||
|
||||
(selectedTab.value == 2 && item.type != 1),
|
||||
)
|
||||
.length,
|
||||
itemBuilder: (context, index) {
|
||||
final filteredItems =
|
||||
items
|
||||
.where(
|
||||
(item) =>
|
||||
selectedTab.value == 0 ||
|
||||
(selectedTab.value == 1 &&
|
||||
item.type == 1) ||
|
||||
(selectedTab.value == 2 &&
|
||||
item.type != 1),
|
||||
)
|
||||
.toList();
|
||||
final item = filteredItems[index];
|
||||
return ChatRoomListTile(
|
||||
room: item,
|
||||
isDirect: item.type == 1,
|
||||
onTap: () {
|
||||
context.pushNamed(
|
||||
'chatRoom',
|
||||
pathParameters: {'id': item.id},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
loading:
|
||||
() => const Center(child: CircularProgressIndicator()),
|
||||
error:
|
||||
(error, stack) => ResponseErrorWidget(
|
||||
error: error,
|
||||
onRetry: () {
|
||||
ref.invalidate(chatroomsJoinedProvider);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Positioned(
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: getTabbedPadding(context).bottom + 8,
|
||||
child: const CallOverlayBar().padding(horizontal: 16, vertical: 12),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
loading: () => const Center(child: CircularProgressIndicator()),
|
||||
error:
|
||||
(error, stack) => ResponseErrorWidget(
|
||||
error: error,
|
||||
onRetry: () {
|
||||
ref.invalidate(chatroomsJoinedProvider);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -35,6 +35,7 @@ import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'chat.dart';
|
||||
import 'package:island/widgets/chat/call_button.dart';
|
||||
import 'package:island/widgets/stickers/picker.dart';
|
||||
|
||||
part 'room.g.dart';
|
||||
|
||||
@@ -1066,11 +1067,19 @@ class _ChatInput extends HookConsumerWidget {
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemCount: attachments.length,
|
||||
itemBuilder: (context, idx) {
|
||||
return AttachmentPreview(
|
||||
item: attachments[idx],
|
||||
onRequestUpload: () => onUploadAttachment(idx),
|
||||
onDelete: () => onDeleteAttachment(idx),
|
||||
onMove: (delta) => onMoveAttachment(idx, delta),
|
||||
return SizedBox(
|
||||
height: 280,
|
||||
width: 280,
|
||||
child: AttachmentPreview(
|
||||
item: attachments[idx],
|
||||
onRequestUpload: () => onUploadAttachment(idx),
|
||||
onDelete: () => onDeleteAttachment(idx),
|
||||
onUpdate: (value) {
|
||||
attachments[idx] = value;
|
||||
onAttachmentsChanged(attachments);
|
||||
},
|
||||
onMove: (delta) => onMoveAttachment(idx, delta),
|
||||
),
|
||||
);
|
||||
},
|
||||
separatorBuilder: (_, _) => const Gap(8),
|
||||
@@ -1125,31 +1134,76 @@ class _ChatInput extends HookConsumerWidget {
|
||||
padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 8),
|
||||
child: Row(
|
||||
children: [
|
||||
PopupMenuButton(
|
||||
icon: const Icon(Symbols.photo_library),
|
||||
itemBuilder:
|
||||
(context) => [
|
||||
PopupMenuItem(
|
||||
onTap: () => onPickFile(true),
|
||||
child: Row(
|
||||
spacing: 12,
|
||||
children: [
|
||||
const Icon(Symbols.photo),
|
||||
Text('addPhoto').tr(),
|
||||
],
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
IconButton(
|
||||
tooltip: 'stickers'.tr(),
|
||||
icon: const Icon(Symbols.emoji_symbols),
|
||||
onPressed: () {
|
||||
final size = MediaQuery.of(context).size;
|
||||
showStickerPickerPopover(
|
||||
context,
|
||||
Offset(
|
||||
20,
|
||||
size.height -
|
||||
480 -
|
||||
MediaQuery.of(context).padding.bottom,
|
||||
),
|
||||
),
|
||||
PopupMenuItem(
|
||||
onTap: () => onPickFile(false),
|
||||
child: Row(
|
||||
spacing: 12,
|
||||
children: [
|
||||
const Icon(Symbols.video_call),
|
||||
Text('addVideo').tr(),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
onPick: (placeholder) {
|
||||
// Insert placeholder at current cursor position
|
||||
final text = messageController.text;
|
||||
final selection = messageController.selection;
|
||||
final start =
|
||||
selection.start >= 0
|
||||
? selection.start
|
||||
: text.length;
|
||||
final end =
|
||||
selection.end >= 0
|
||||
? selection.end
|
||||
: text.length;
|
||||
final newText = text.replaceRange(
|
||||
start,
|
||||
end,
|
||||
placeholder,
|
||||
);
|
||||
messageController.value = TextEditingValue(
|
||||
text: newText,
|
||||
selection: TextSelection.collapsed(
|
||||
offset: start + placeholder.length,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
PopupMenuButton(
|
||||
icon: const Icon(Symbols.photo_library),
|
||||
itemBuilder:
|
||||
(context) => [
|
||||
PopupMenuItem(
|
||||
onTap: () => onPickFile(true),
|
||||
child: Row(
|
||||
spacing: 12,
|
||||
children: [
|
||||
const Icon(Symbols.photo),
|
||||
Text('addPhoto').tr(),
|
||||
],
|
||||
),
|
||||
),
|
||||
PopupMenuItem(
|
||||
onTap: () => onPickFile(false),
|
||||
child: Row(
|
||||
spacing: 12,
|
||||
children: [
|
||||
const Icon(Symbols.video_call),
|
||||
Text('addVideo').tr(),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
Expanded(
|
||||
child: RawKeyboardListener(
|
||||
|
||||
@@ -41,7 +41,7 @@ class ChatDetailScreen extends HookConsumerWidget {
|
||||
try {
|
||||
final client = ref.watch(apiClientProvider);
|
||||
await client.patch(
|
||||
'/chat/$id/members/me/notify',
|
||||
'/sphere/chat/$id/members/me/notify',
|
||||
data: {'notify_level': level},
|
||||
);
|
||||
ref.invalidate(chatroomIdentityProvider(id));
|
||||
@@ -59,7 +59,7 @@ class ChatDetailScreen extends HookConsumerWidget {
|
||||
try {
|
||||
final client = ref.watch(apiClientProvider);
|
||||
await client.patch(
|
||||
'/chat/$id/members/me/notify',
|
||||
'/sphere/chat/$id/members/me/notify',
|
||||
data: {'break_until': until.toUtc().toIso8601String()},
|
||||
);
|
||||
ref.invalidate(chatroomProvider(id));
|
||||
@@ -421,10 +421,10 @@ class _ChatRoomActionMenu extends HookConsumerWidget {
|
||||
showConfirmAlert(
|
||||
'deleteChatRoomHint'.tr(),
|
||||
'deleteChatRoom'.tr(),
|
||||
).then((confirm) {
|
||||
).then((confirm) async {
|
||||
if (confirm) {
|
||||
final client = ref.watch(apiClientProvider);
|
||||
client.delete('/sphere/chat/$id');
|
||||
await client.delete('/sphere/chat/$id');
|
||||
ref.invalidate(chatroomsJoinedProvider);
|
||||
if (context.mounted) {
|
||||
context.pop();
|
||||
@@ -454,10 +454,10 @@ class _ChatRoomActionMenu extends HookConsumerWidget {
|
||||
showConfirmAlert(
|
||||
'leaveChatRoomHint'.tr(),
|
||||
'leaveChatRoom'.tr(),
|
||||
).then((confirm) {
|
||||
).then((confirm) async {
|
||||
if (confirm) {
|
||||
final client = ref.watch(apiClientProvider);
|
||||
client.delete('/sphere/chat/$id/members/me');
|
||||
await client.delete('/sphere/chat/$id/members/me');
|
||||
ref.invalidate(chatroomsJoinedProvider);
|
||||
if (context.mounted) {
|
||||
context.pop();
|
||||
|
||||
@@ -114,9 +114,9 @@ class CreatorHubShellScreen extends StatelessWidget {
|
||||
isRoot: true,
|
||||
child: Row(
|
||||
children: [
|
||||
SizedBox(width: 360, child: const CreatorHubScreen(isAside: true)),
|
||||
Flexible(flex: 2, child: const CreatorHubScreen(isAside: true)),
|
||||
const VerticalDivider(width: 1),
|
||||
Expanded(child: child),
|
||||
Flexible(flex: 3, child: child),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -380,6 +380,23 @@ class CreatorHubScreen extends HookConsumerWidget {
|
||||
);
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
minTileHeight: 48,
|
||||
title: const Text('Polls'),
|
||||
trailing: const Icon(Symbols.chevron_right),
|
||||
leading: const Icon(Symbols.poll),
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 24,
|
||||
),
|
||||
onTap: () {
|
||||
context.pushNamed(
|
||||
'creatorPolls',
|
||||
pathParameters: {
|
||||
'name': currentPublisher.value!.name,
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
minTileHeight: 48,
|
||||
title: Text('publisherMembers').tr(),
|
||||
|
||||
179
lib/screens/creators/poll/poll_list.dart
Normal file
179
lib/screens/creators/poll/poll_list.dart
Normal file
@@ -0,0 +1,179 @@
|
||||
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/poll.dart';
|
||||
import 'package:island/pods/network.dart';
|
||||
import 'package:island/widgets/poll/poll_feedback.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
|
||||
|
||||
part 'poll_list.g.dart';
|
||||
|
||||
@riverpod
|
||||
class PollListNotifier extends _$PollListNotifier
|
||||
with CursorPagingNotifierMixin<SnPoll> {
|
||||
static const int _pageSize = 20;
|
||||
|
||||
@override
|
||||
Future<CursorPagingData<SnPoll>> build(String? pubName) {
|
||||
// immediately load first page
|
||||
return fetch(cursor: null);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<CursorPagingData<SnPoll>> fetch({required String? cursor}) async {
|
||||
final client = ref.read(apiClientProvider);
|
||||
final offset = cursor == null ? 0 : int.parse(cursor);
|
||||
|
||||
// read the current family argument passed to provider
|
||||
final currentPub = pubName;
|
||||
final queryParams = {
|
||||
'offset': offset,
|
||||
'take': _pageSize,
|
||||
if (currentPub != null) 'pub': currentPub,
|
||||
};
|
||||
|
||||
final response = await client.get(
|
||||
'/sphere/polls/me',
|
||||
queryParameters: queryParams,
|
||||
);
|
||||
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 hasMore = offset + items.length < total;
|
||||
final nextCursor = hasMore ? (offset + items.length).toString() : null;
|
||||
|
||||
return CursorPagingData(
|
||||
items: items,
|
||||
hasMore: hasMore,
|
||||
nextCursor: nextCursor,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CreatorPollListScreen extends HookConsumerWidget {
|
||||
const CreatorPollListScreen({super.key, required this.pubName});
|
||||
|
||||
final String pubName;
|
||||
|
||||
Future<void> _createPoll(BuildContext context) async {
|
||||
final result = await GoRouter.of(
|
||||
context,
|
||||
).pushNamed('creatorPollNew', pathParameters: {'name': pubName});
|
||||
if (result is SnPoll && context.mounted) {
|
||||
Navigator.of(context).maybePop(result);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Polls')),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: () => _createPoll(context),
|
||||
child: const Icon(Icons.add),
|
||||
),
|
||||
body: RefreshIndicator(
|
||||
onRefresh: () => ref.refresh(pollListNotifierProvider(pubName).future),
|
||||
child: CustomScrollView(
|
||||
slivers: [
|
||||
PagingHelperSliverView(
|
||||
provider: pollListNotifierProvider(pubName),
|
||||
futureRefreshable: pollListNotifierProvider(pubName).future,
|
||||
notifierRefreshable: pollListNotifierProvider(pubName).notifier,
|
||||
contentBuilder:
|
||||
(data, widgetCount, endItemView) => SliverList.builder(
|
||||
itemCount: widgetCount,
|
||||
itemBuilder: (context, index) {
|
||||
if (index == widgetCount - 1) {
|
||||
return endItemView;
|
||||
}
|
||||
final poll = data.items[index];
|
||||
return _CreatorPollItem(poll: poll, pubName: pubName);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _CreatorPollItem extends StatelessWidget {
|
||||
final String pubName;
|
||||
const _CreatorPollItem({required this.poll, required this.pubName});
|
||||
|
||||
final SnPoll poll;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final ended = poll.endedAt;
|
||||
final endedText =
|
||||
ended == null
|
||||
? 'No end'
|
||||
: MaterialLocalizations.of(context).formatFullDate(ended);
|
||||
|
||||
return Card(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: ListTile(
|
||||
title: Text(poll.title ?? 'Untitled poll'),
|
||||
subtitle: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (poll.description != null && poll.description!.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 4),
|
||||
child: Text(
|
||||
poll.description!,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 4),
|
||||
child: Text(
|
||||
'Questions: ${poll.questions.length} · Ends: $endedText',
|
||||
style: theme.textTheme.bodySmall,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
trailing: PopupMenuButton<String>(
|
||||
itemBuilder:
|
||||
(context) => [
|
||||
PopupMenuItem(
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Symbols.edit),
|
||||
const Gap(16),
|
||||
Text('Edit'),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
GoRouter.of(context).pushNamed(
|
||||
'creatorPollEdit',
|
||||
pathParameters: {'name': pubName, 'id': poll.id},
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
useRootNavigator: true,
|
||||
isScrollControlled: true,
|
||||
builder:
|
||||
(context) => PollFeedbackSheet(pollId: poll.id, poll: poll),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
179
lib/screens/creators/poll/poll_list.g.dart
Normal file
179
lib/screens/creators/poll/poll_list.g.dart
Normal file
@@ -0,0 +1,179 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'poll_list.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$pollListNotifierHash() => r'd3da24ff6bbb8f35b06d57fc41625dc0312508e4';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
_SystemHash._();
|
||||
|
||||
static int combine(int hash, int value) {
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + value);
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
|
||||
return hash ^ (hash >> 6);
|
||||
}
|
||||
|
||||
static int finish(int hash) {
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
|
||||
// ignore: parameter_assignments
|
||||
hash = hash ^ (hash >> 11);
|
||||
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _$PollListNotifier
|
||||
extends BuildlessAutoDisposeAsyncNotifier<CursorPagingData<SnPoll>> {
|
||||
late final String? pubName;
|
||||
|
||||
FutureOr<CursorPagingData<SnPoll>> build(String? pubName);
|
||||
}
|
||||
|
||||
/// See also [PollListNotifier].
|
||||
@ProviderFor(PollListNotifier)
|
||||
const pollListNotifierProvider = PollListNotifierFamily();
|
||||
|
||||
/// See also [PollListNotifier].
|
||||
class PollListNotifierFamily
|
||||
extends Family<AsyncValue<CursorPagingData<SnPoll>>> {
|
||||
/// See also [PollListNotifier].
|
||||
const PollListNotifierFamily();
|
||||
|
||||
/// See also [PollListNotifier].
|
||||
PollListNotifierProvider call(String? pubName) {
|
||||
return PollListNotifierProvider(pubName);
|
||||
}
|
||||
|
||||
@override
|
||||
PollListNotifierProvider getProviderOverride(
|
||||
covariant PollListNotifierProvider provider,
|
||||
) {
|
||||
return call(provider.pubName);
|
||||
}
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _dependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
|
||||
_allTransitiveDependencies;
|
||||
|
||||
@override
|
||||
String? get name => r'pollListNotifierProvider';
|
||||
}
|
||||
|
||||
/// See also [PollListNotifier].
|
||||
class PollListNotifierProvider
|
||||
extends
|
||||
AutoDisposeAsyncNotifierProviderImpl<
|
||||
PollListNotifier,
|
||||
CursorPagingData<SnPoll>
|
||||
> {
|
||||
/// See also [PollListNotifier].
|
||||
PollListNotifierProvider(String? pubName)
|
||||
: this._internal(
|
||||
() => PollListNotifier()..pubName = pubName,
|
||||
from: pollListNotifierProvider,
|
||||
name: r'pollListNotifierProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$pollListNotifierHash,
|
||||
dependencies: PollListNotifierFamily._dependencies,
|
||||
allTransitiveDependencies:
|
||||
PollListNotifierFamily._allTransitiveDependencies,
|
||||
pubName: pubName,
|
||||
);
|
||||
|
||||
PollListNotifierProvider._internal(
|
||||
super._createNotifier, {
|
||||
required super.name,
|
||||
required super.dependencies,
|
||||
required super.allTransitiveDependencies,
|
||||
required super.debugGetCreateSourceHash,
|
||||
required super.from,
|
||||
required this.pubName,
|
||||
}) : super.internal();
|
||||
|
||||
final String? pubName;
|
||||
|
||||
@override
|
||||
FutureOr<CursorPagingData<SnPoll>> runNotifierBuild(
|
||||
covariant PollListNotifier notifier,
|
||||
) {
|
||||
return notifier.build(pubName);
|
||||
}
|
||||
|
||||
@override
|
||||
Override overrideWith(PollListNotifier Function() create) {
|
||||
return ProviderOverride(
|
||||
origin: this,
|
||||
override: PollListNotifierProvider._internal(
|
||||
() => create()..pubName = pubName,
|
||||
from: from,
|
||||
name: null,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
debugGetCreateSourceHash: null,
|
||||
pubName: pubName,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
AutoDisposeAsyncNotifierProviderElement<
|
||||
PollListNotifier,
|
||||
CursorPagingData<SnPoll>
|
||||
>
|
||||
createElement() {
|
||||
return _PollListNotifierProviderElement(this);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is PollListNotifierProvider && other.pubName == pubName;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||
hash = _SystemHash.combine(hash, pubName.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
mixin PollListNotifierRef
|
||||
on AutoDisposeAsyncNotifierProviderRef<CursorPagingData<SnPoll>> {
|
||||
/// The parameter `pubName` of this provider.
|
||||
String? get pubName;
|
||||
}
|
||||
|
||||
class _PollListNotifierProviderElement
|
||||
extends
|
||||
AutoDisposeAsyncNotifierProviderElement<
|
||||
PollListNotifier,
|
||||
CursorPagingData<SnPoll>
|
||||
>
|
||||
with PollListNotifierRef {
|
||||
_PollListNotifierProviderElement(super.provider);
|
||||
|
||||
@override
|
||||
String? get pubName => (origin as PollListNotifierProvider).pubName;
|
||||
}
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
||||
@@ -26,7 +26,7 @@ part 'pack_detail.freezed.dart';
|
||||
@riverpod
|
||||
Future<List<SnSticker>> stickerPackContent(Ref ref, String packId) async {
|
||||
final apiClient = ref.watch(apiClientProvider);
|
||||
final resp = await apiClient.get('/stickers/$packId/content');
|
||||
final resp = await apiClient.get('/sphere/stickers/$packId/content');
|
||||
return resp.data
|
||||
.map<SnSticker>((e) => SnSticker.fromJson(e))
|
||||
.cast<SnSticker>()
|
||||
@@ -74,13 +74,16 @@ class StickerPackDetailScreen extends HookConsumerWidget {
|
||||
IconButton(
|
||||
icon: const Icon(Symbols.add_circle),
|
||||
onPressed: () {
|
||||
context.pushNamed('creatorStickerNew', pathParameters: {'packId': id}).then((
|
||||
value,
|
||||
) {
|
||||
if (value != null) {
|
||||
ref.invalidate(stickerPackContentProvider(id));
|
||||
}
|
||||
});
|
||||
context
|
||||
.pushNamed(
|
||||
'creatorStickerNew',
|
||||
pathParameters: {'name': pubName, 'packId': id},
|
||||
)
|
||||
.then((value) {
|
||||
if (value != null) {
|
||||
ref.invalidate(stickerPackContentProvider(id));
|
||||
}
|
||||
});
|
||||
},
|
||||
),
|
||||
_StickerPackActionMenu(
|
||||
@@ -173,9 +176,13 @@ class StickerPackDetailScreen extends HookConsumerWidget {
|
||||
title: 'edit'.tr(),
|
||||
image: MenuImage.icon(Symbols.edit),
|
||||
callback: () {
|
||||
context.pushNamed(
|
||||
context
|
||||
.pushNamed(
|
||||
'creatorStickerEdit',
|
||||
pathParameters: {'packId': id, 'id': sticker.id},
|
||||
pathParameters: {
|
||||
'packId': id,
|
||||
'id': sticker.id,
|
||||
},
|
||||
)
|
||||
.then((value) {
|
||||
if (value != null) {
|
||||
@@ -259,9 +266,7 @@ class _StickerPackActionMenu extends HookConsumerWidget {
|
||||
(context) => [
|
||||
PopupMenuItem(
|
||||
onTap: () {
|
||||
context.push(
|
||||
'/creators/$pubName/stickers/$packId/edit',
|
||||
);
|
||||
context.push('/creators/$pubName/stickers/$packId/edit');
|
||||
},
|
||||
child: Row(
|
||||
children: [
|
||||
|
||||
@@ -7,7 +7,7 @@ part of 'pack_detail.dart';
|
||||
// **************************************************************************
|
||||
|
||||
String _$stickerPackContentHash() =>
|
||||
r'78de848fba1f341f217f8ae4b9eef2d8afa67964';
|
||||
r'42d74f51022e67e35cb601c2f30f4f02e1f2be9d';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
|
||||
@@ -31,7 +31,7 @@ class StickersScreen extends HookConsumerWidget {
|
||||
context
|
||||
.pushNamed(
|
||||
'creatorStickerPackNew',
|
||||
queryParameters: {'pubName': pubName},
|
||||
queryParameters: {'name': pubName},
|
||||
)
|
||||
.then((value) {
|
||||
if (value != null) {
|
||||
@@ -76,7 +76,7 @@ class SliverStickerPacksList extends HookConsumerWidget {
|
||||
onTap: () {
|
||||
context.pushNamed(
|
||||
'creatorStickerPackDetail',
|
||||
pathParameters: {'pubName': pubName, 'packId': sticker.id},
|
||||
pathParameters: {'name': pubName, 'packId': sticker.id},
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -18,7 +18,7 @@ part 'apps.g.dart';
|
||||
@riverpod
|
||||
Future<List<CustomApp>> customApps(Ref ref, String publisherName) async {
|
||||
final client = ref.watch(apiClientProvider);
|
||||
final resp = await client.get('/developers/$publisherName/apps');
|
||||
final resp = await client.get('/develop/developers/$publisherName/apps');
|
||||
return resp.data.map((e) => CustomApp.fromJson(e)).cast<CustomApp>().toList();
|
||||
}
|
||||
|
||||
@@ -37,7 +37,10 @@ class CustomAppsScreen extends HookConsumerWidget {
|
||||
IconButton(
|
||||
icon: const Icon(Symbols.add),
|
||||
onPressed: () {
|
||||
context.pushNamed('developerAppNew', pathParameters: {'name': publisherName});
|
||||
context.pushNamed(
|
||||
'developerAppNew',
|
||||
pathParameters: {'name': publisherName},
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
@@ -121,7 +124,13 @@ class CustomAppsScreen extends HookConsumerWidget {
|
||||
],
|
||||
onSelected: (value) {
|
||||
if (value == 'edit') {
|
||||
context.pushNamed('developerAppEdit', pathParameters: {'name': publisherName, 'id': app.id});
|
||||
context.pushNamed(
|
||||
'developerAppEdit',
|
||||
pathParameters: {
|
||||
'name': publisherName,
|
||||
'id': app.id,
|
||||
},
|
||||
);
|
||||
} else if (value == 'delete') {
|
||||
showConfirmAlert(
|
||||
'deleteCustomAppHint'.tr(),
|
||||
@@ -130,7 +139,7 @@ class CustomAppsScreen extends HookConsumerWidget {
|
||||
if (confirm) {
|
||||
final client = ref.read(apiClientProvider);
|
||||
client.delete(
|
||||
'/developers/$publisherName/apps/${app.id}',
|
||||
'/develop/developers/$publisherName/apps/${app.id}',
|
||||
);
|
||||
ref.invalidate(
|
||||
customAppsProvider(publisherName),
|
||||
|
||||
@@ -6,7 +6,7 @@ part of 'apps.dart';
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$customAppsHash() => r'1dec11573b9d987c3adbdf4732b3781a6f40172a';
|
||||
String _$customAppsHash() => r'c6ac78060eb51a2b208a749a81ecbe0a9c608ce1';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
|
||||
@@ -24,7 +24,7 @@ part 'edit_app.g.dart';
|
||||
@riverpod
|
||||
Future<CustomApp?> customApp(Ref ref, String publisherName, String id) async {
|
||||
final client = ref.watch(apiClientProvider);
|
||||
final resp = await client.get('/developers/$publisherName/apps/$id');
|
||||
final resp = await client.get('/develop/developers/$publisherName/apps/$id');
|
||||
return CustomApp.fromJson(resp.data);
|
||||
}
|
||||
|
||||
@@ -282,9 +282,15 @@ class EditAppScreen extends HookConsumerWidget {
|
||||
: null,
|
||||
};
|
||||
if (isNew) {
|
||||
await client.post('/developers/$publisherName/apps', data: data);
|
||||
await client.post(
|
||||
'/develop/developers/$publisherName/apps',
|
||||
data: data,
|
||||
);
|
||||
} else {
|
||||
await client.patch('/developers/$publisherName/apps/$id', data: data);
|
||||
await client.patch(
|
||||
'/develop/developers/$publisherName/apps/$id',
|
||||
data: data,
|
||||
);
|
||||
}
|
||||
ref.invalidate(customAppsProvider(publisherName));
|
||||
if (context.mounted) {
|
||||
|
||||
@@ -6,7 +6,7 @@ part of 'edit_app.dart';
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$customAppHash() => r'aa4d1fb803c47a99cbacf6d91481f4fce3fda457';
|
||||
String _$customAppHash() => r'42ad937b8439c793e3c5c35568bb5fa4da017df3';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
|
||||
@@ -25,14 +25,14 @@ part 'hub.g.dart';
|
||||
Future<DeveloperStats?> developerStats(Ref ref, String? uname) async {
|
||||
if (uname == null) return null;
|
||||
final apiClient = ref.watch(apiClientProvider);
|
||||
final resp = await apiClient.get('/sphere/developers/$uname/stats');
|
||||
final resp = await apiClient.get('/develop/developers/$uname/stats');
|
||||
return DeveloperStats.fromJson(resp.data);
|
||||
}
|
||||
|
||||
@riverpod
|
||||
Future<List<SnPublisher>> developers(Ref ref) async {
|
||||
final client = ref.watch(apiClientProvider);
|
||||
final resp = await client.get('/sphere/developers');
|
||||
final resp = await client.get('/develop/developers');
|
||||
return resp.data
|
||||
.map((e) => SnPublisher.fromJson(e))
|
||||
.cast<SnPublisher>()
|
||||
@@ -51,12 +51,9 @@ class DeveloperHubShellScreen extends StatelessWidget {
|
||||
isRoot: true,
|
||||
child: Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 360,
|
||||
child: const DeveloperHubScreen(isAside: true),
|
||||
),
|
||||
Flexible(flex: 2, child: const DeveloperHubScreen(isAside: true)),
|
||||
const VerticalDivider(width: 1),
|
||||
Expanded(child: child),
|
||||
Flexible(flex: 3, child: child),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -114,7 +111,7 @@ class DeveloperHubScreen extends HookConsumerWidget {
|
||||
);
|
||||
|
||||
return AppScaffold(
|
||||
noBackground: false,
|
||||
isNoBackground: false,
|
||||
appBar: AppBar(
|
||||
leading: !isWide ? const PageBackButton() : null,
|
||||
title: Text('developerHub').tr(),
|
||||
@@ -339,7 +336,7 @@ class _DeveloperEnrollmentSheet extends HookConsumerWidget {
|
||||
Future<void> enroll(SnPublisher publisher) async {
|
||||
try {
|
||||
final client = ref.read(apiClientProvider);
|
||||
await client.post('/sphere/developers/${publisher.name}/enroll');
|
||||
await client.post('/develop/developers/${publisher.name}/enroll');
|
||||
if (context.mounted) {
|
||||
Navigator.pop(context, true);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ part of 'hub.dart';
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$developerStatsHash() => r'baa708f3586e8987e221cc8ab825d759658c0f55';
|
||||
String _$developerStatsHash() => r'45546f29ec7cd1a9c3a4e0f4e39275e78bf34755';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
@@ -149,7 +149,7 @@ class _DeveloperStatsProviderElement
|
||||
String? get uname => (origin as DeveloperStatsProvider).uname;
|
||||
}
|
||||
|
||||
String _$developersHash() => r'f11335fdf553c661110281edeec70ef89c64727d';
|
||||
String _$developersHash() => r'04f25db31f511f651a5add128d56631236ed0b39';
|
||||
|
||||
/// See also [developers].
|
||||
@ProviderFor(developers)
|
||||
|
||||
@@ -17,7 +17,7 @@ class DiscoveryRealmsScreen extends HookConsumerWidget {
|
||||
final currentQuery = useState<String?>(null);
|
||||
|
||||
return AppScaffold(
|
||||
noBackground: false,
|
||||
isNoBackground: false,
|
||||
appBar: AppBar(title: Text('discoverRealms'.tr())),
|
||||
body: Stack(
|
||||
children: [
|
||||
|
||||
@@ -12,11 +12,11 @@ import 'package:island/models/webfeed.dart';
|
||||
import 'package:island/pods/event_calendar.dart';
|
||||
import 'package:island/pods/userinfo.dart';
|
||||
import 'package:island/services/responsive.dart';
|
||||
import 'package:island/widgets/account/event_calendar.dart';
|
||||
import 'package:island/widgets/account/fortune_graph.dart';
|
||||
import 'package:island/widgets/app_scaffold.dart';
|
||||
import 'package:island/models/post.dart';
|
||||
import 'package:island/widgets/check_in.dart';
|
||||
import 'package:island/widgets/post/post_featured.dart';
|
||||
import 'package:island/widgets/post/post_item.dart';
|
||||
import 'package:island/screens/tabs.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
@@ -70,22 +70,15 @@ class ExploreScreen extends HookConsumerWidget {
|
||||
final events = ref.watch(eventCalendarProvider(query.value));
|
||||
|
||||
final selectedDay = useState(now);
|
||||
|
||||
void onMonthChanged(int year, int month) {
|
||||
query.value = EventCalendarQuery(
|
||||
uname: query.value.uname,
|
||||
year: year,
|
||||
month: month,
|
||||
);
|
||||
}
|
||||
|
||||
// Function to handle day selection for synchronizing between widgets
|
||||
void onDaySelected(DateTime day) {
|
||||
selectedDay.value = day;
|
||||
}
|
||||
|
||||
final user = ref.watch(userInfoProvider);
|
||||
|
||||
return AppScaffold(
|
||||
noBackground: false,
|
||||
isNoBackground: false,
|
||||
appBar: AppBar(
|
||||
toolbarHeight: 0,
|
||||
bottom: PreferredSize(
|
||||
@@ -167,67 +160,95 @@ class ExploreScreen extends HookConsumerWidget {
|
||||
),
|
||||
),
|
||||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
heroTag: Key("explore-page-fab"),
|
||||
onPressed: () {
|
||||
context.pushNamed('postCompose').then((value) {
|
||||
if (value != null) {
|
||||
activitiesNotifier.forceRefresh();
|
||||
}
|
||||
});
|
||||
floatingActionButton: InkWell(
|
||||
onLongPress: () {
|
||||
context.pushNamed('postCompose', queryParameters: {'type': '1'}).then(
|
||||
(value) {
|
||||
if (value != null) {
|
||||
activitiesNotifier.forceRefresh();
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
child: const Icon(Symbols.edit),
|
||||
child: FloatingActionButton(
|
||||
heroTag: Key("explore-page-fab"),
|
||||
onPressed: () {
|
||||
context.pushNamed('postCompose').then((value) {
|
||||
if (value != null) {
|
||||
activitiesNotifier.forceRefresh();
|
||||
}
|
||||
});
|
||||
},
|
||||
child: const Icon(Symbols.edit),
|
||||
),
|
||||
),
|
||||
floatingActionButtonLocation: TabbedFabLocation(context),
|
||||
body: Builder(
|
||||
builder: (context) {
|
||||
final isWider = isWiderScreen(context);
|
||||
|
||||
final bodyView = TabBarView(
|
||||
controller: tabController,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
children: [
|
||||
_buildActivityList(context, ref, null),
|
||||
_buildActivityList(context, ref, 'subscriptions'),
|
||||
_buildActivityList(context, ref, 'friends'),
|
||||
],
|
||||
final bodyView = _buildActivityList(
|
||||
context,
|
||||
ref,
|
||||
currentFilter.value,
|
||||
);
|
||||
|
||||
if (isWider) {
|
||||
return Row(
|
||||
children: [
|
||||
Flexible(flex: 3, child: bodyView),
|
||||
const VerticalDivider(width: 1),
|
||||
Flexible(
|
||||
flex: 2,
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
CheckInWidget(),
|
||||
Card(
|
||||
margin: EdgeInsets.only(left: 16, right: 16, top: 8),
|
||||
child: Column(
|
||||
children: [
|
||||
// Use the reusable EventCalendarWidget
|
||||
EventCalendarWidget(
|
||||
events: events,
|
||||
initialDate: now,
|
||||
showEventDetails: true,
|
||||
onMonthChanged: onMonthChanged,
|
||||
onDaySelected: onDaySelected,
|
||||
),
|
||||
],
|
||||
Flexible(flex: 3, child: bodyView.padding(left: 8)),
|
||||
if (user.value != null)
|
||||
Flexible(
|
||||
flex: 2,
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
CheckInWidget(
|
||||
margin: EdgeInsets.only(
|
||||
left: 8,
|
||||
right: 12,
|
||||
top: 16,
|
||||
),
|
||||
onChecked: () {
|
||||
ref.invalidate(
|
||||
eventCalendarProvider(query.value),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
FortuneGraphWidget(
|
||||
events: events,
|
||||
constrainWidth: true,
|
||||
onPointSelected: onDaySelected,
|
||||
PostFeaturedList().padding(
|
||||
left: 8,
|
||||
right: 12,
|
||||
top: 8,
|
||||
),
|
||||
FortuneGraphWidget(
|
||||
margin: EdgeInsets.only(left: 8, right: 12, top: 8),
|
||||
events: events,
|
||||
constrainWidth: true,
|
||||
onPointSelected: onDaySelected,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
else
|
||||
Flexible(
|
||||
flex: 2,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Welcome to\nthe Solar Network',
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
).bold(),
|
||||
const Gap(2),
|
||||
Text(
|
||||
'Login to explore more!',
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
],
|
||||
),
|
||||
).padding(horizontal: 36, vertical: 16),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
@@ -280,56 +301,62 @@ class _DiscoveryActivityItem extends StatelessWidget {
|
||||
final items = data['items'] as List;
|
||||
final type = items.firstOrNull?['type'] ?? 'unknown';
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(Symbols.explore, size: 19),
|
||||
const Gap(8),
|
||||
Text(
|
||||
(switch (type) {
|
||||
'realm' => 'discoverRealms',
|
||||
'publisher' => 'discoverPublishers',
|
||||
'article' => 'discoverWebArticles',
|
||||
_ => 'unknown',
|
||||
}).tr(),
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
).padding(top: 1),
|
||||
],
|
||||
).padding(horizontal: 20, top: 8, bottom: 4),
|
||||
SizedBox(
|
||||
height: 180,
|
||||
child: ListView.builder(
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemCount: items.length,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
itemBuilder: (context, index) {
|
||||
final item = items[index];
|
||||
switch (type) {
|
||||
case 'realm':
|
||||
return RealmCard(
|
||||
realm: SnRealm.fromJson(item['data']),
|
||||
maxWidth: 280,
|
||||
);
|
||||
case 'publisher':
|
||||
return PublisherCard(
|
||||
publisher: SnPublisher.fromJson(item['data']),
|
||||
maxWidth: 280,
|
||||
);
|
||||
case 'article':
|
||||
return WebArticleCard(
|
||||
article: SnWebArticle.fromJson(item['data']),
|
||||
maxWidth: 280,
|
||||
);
|
||||
default:
|
||||
return Placeholder();
|
||||
}
|
||||
},
|
||||
),
|
||||
).padding(bottom: 4),
|
||||
],
|
||||
return Card(
|
||||
margin: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(Symbols.explore, size: 19),
|
||||
const Gap(8),
|
||||
Text(
|
||||
(switch (type) {
|
||||
'realm' => 'discoverRealms',
|
||||
'publisher' => 'discoverPublishers',
|
||||
'article' => 'discoverWebArticles',
|
||||
_ => 'unknown',
|
||||
}).tr(),
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
).padding(top: 1),
|
||||
],
|
||||
).padding(horizontal: 20, top: 8, bottom: 4),
|
||||
SizedBox(
|
||||
height: 180,
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxHeight: 200),
|
||||
child: CarouselView.weighted(
|
||||
flexWeights:
|
||||
isWideScreen(context) ? <int>[3, 2, 1] : <int>[4, 1],
|
||||
consumeMaxWeight: false,
|
||||
enableSplash: false,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(8)),
|
||||
),
|
||||
children: [
|
||||
for (final item in items)
|
||||
switch (type) {
|
||||
'realm' => RealmCard(
|
||||
realm: SnRealm.fromJson(item['data']),
|
||||
maxWidth: 280,
|
||||
),
|
||||
'publisher' => PublisherCard(
|
||||
publisher: SnPublisher.fromJson(item['data']),
|
||||
maxWidth: 280,
|
||||
),
|
||||
'article' => WebArticleCard(
|
||||
article: SnWebArticle.fromJson(item['data']),
|
||||
maxWidth: 280,
|
||||
),
|
||||
_ => Placeholder(),
|
||||
},
|
||||
],
|
||||
),
|
||||
),
|
||||
).padding(bottom: 8, horizontal: 8),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -355,8 +382,17 @@ class _ActivityListView extends HookConsumerWidget {
|
||||
|
||||
return CustomScrollView(
|
||||
slivers: [
|
||||
SliverGap(12),
|
||||
if (user.value != null && !contentOnly)
|
||||
SliverToBoxAdapter(child: CheckInWidget()),
|
||||
SliverToBoxAdapter(
|
||||
child: CheckInWidget(
|
||||
margin: EdgeInsets.only(left: 8, right: 8, bottom: 4),
|
||||
),
|
||||
),
|
||||
if (!contentOnly)
|
||||
SliverToBoxAdapter(
|
||||
child: PostFeaturedList().padding(horizontal: 8, bottom: 4, top: 4),
|
||||
),
|
||||
SliverList.builder(
|
||||
itemCount: widgetCount,
|
||||
itemBuilder: (context, index) {
|
||||
@@ -373,19 +409,9 @@ class _ActivityListView extends HookConsumerWidget {
|
||||
switch (item.type) {
|
||||
case 'posts.new':
|
||||
case 'posts.new.replies':
|
||||
final isReply = item.type == 'posts.new.replies';
|
||||
itemWidget = PostItem(
|
||||
backgroundColor:
|
||||
isWideScreen(context) ? Colors.transparent : null,
|
||||
itemWidget = PostActionableItem(
|
||||
borderRadius: 8,
|
||||
item: SnPost.fromJson(item.data!),
|
||||
padding:
|
||||
isReply
|
||||
? const EdgeInsets.only(
|
||||
left: 16,
|
||||
right: 16,
|
||||
bottom: 16,
|
||||
)
|
||||
: null,
|
||||
onRefresh: () {
|
||||
activitiesNotifier.forceRefresh();
|
||||
},
|
||||
@@ -396,21 +422,10 @@ class _ActivityListView extends HookConsumerWidget {
|
||||
);
|
||||
},
|
||||
);
|
||||
if (isReply) {
|
||||
itemWidget = Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
const Icon(Symbols.reply),
|
||||
const Gap(8),
|
||||
Text('Replying your post'),
|
||||
],
|
||||
).padding(horizontal: 20, vertical: 8),
|
||||
itemWidget,
|
||||
],
|
||||
);
|
||||
}
|
||||
itemWidget = Card(
|
||||
margin: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
child: itemWidget,
|
||||
);
|
||||
break;
|
||||
case 'discovery':
|
||||
itemWidget = _DiscoveryActivityItem(data: item.data!);
|
||||
@@ -419,7 +434,7 @@ class _ActivityListView extends HookConsumerWidget {
|
||||
itemWidget = const Placeholder();
|
||||
}
|
||||
|
||||
return Column(children: [itemWidget, const Divider(height: 1)]);
|
||||
return itemWidget;
|
||||
},
|
||||
),
|
||||
SliverGap(getTabbedPadding(context).bottom),
|
||||
|
||||
1094
lib/screens/poll/poll_editor.dart
Normal file
1094
lib/screens/poll/poll_editor.dart
Normal file
File diff suppressed because it is too large
Load Diff
@@ -18,7 +18,8 @@ import 'package:island/widgets/post/publishers_modal.dart';
|
||||
import 'package:island/screens/posts/post_detail.dart';
|
||||
import 'package:island/widgets/post/compose_settings_sheet.dart';
|
||||
import 'package:island/services/compose_storage_db.dart';
|
||||
import 'package:island/widgets/post/draft_manager.dart';
|
||||
// DraftManagerSheet is now imported through compose_toolbar.dart
|
||||
import 'package:island/widgets/post/compose_toolbar.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
|
||||
@@ -52,13 +53,13 @@ class PostEditScreen extends HookConsumerWidget {
|
||||
data: (post) => PostComposeScreen(originalPost: post),
|
||||
loading:
|
||||
() => AppScaffold(
|
||||
noBackground: false,
|
||||
isNoBackground: false,
|
||||
appBar: AppBar(leading: const PageBackButton()),
|
||||
body: const Center(child: CircularProgressIndicator()),
|
||||
),
|
||||
error:
|
||||
(e, _) => AppScaffold(
|
||||
noBackground: false,
|
||||
isNoBackground: false,
|
||||
appBar: AppBar(leading: const PageBackButton()),
|
||||
body: Text('Error: $e', textAlign: TextAlign.center),
|
||||
),
|
||||
@@ -92,7 +93,6 @@ class PostComposeScreen extends HookConsumerWidget {
|
||||
|
||||
// Otherwise, continue with regular post compose
|
||||
final theme = Theme.of(context);
|
||||
final colorScheme = theme.colorScheme;
|
||||
|
||||
// When editing, preserve the original replied/forwarded post references
|
||||
final effectiveRepliedPost = repliedPost ?? originalPost?.repliedPost;
|
||||
@@ -205,17 +205,7 @@ class PostComposeScreen extends HookConsumerWidget {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
builder:
|
||||
(context) => ComposeSettingsSheet(
|
||||
titleController: state.titleController,
|
||||
descriptionController: state.descriptionController,
|
||||
visibility: state.visibility,
|
||||
tagsController: state.tagsController,
|
||||
categoriesController: state.categoriesController,
|
||||
onVisibilityChanged: () {
|
||||
// Trigger rebuild if needed
|
||||
},
|
||||
),
|
||||
builder: (context) => ComposeSettingsSheet(state: state),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -238,6 +228,8 @@ class PostComposeScreen extends HookConsumerWidget {
|
||||
onRequestUpload:
|
||||
() => ComposeLogic.uploadAttachment(ref, state, idx),
|
||||
onDelete: () => ComposeLogic.deleteAttachment(ref, state, idx),
|
||||
onUpdate:
|
||||
(value) => ComposeLogic.updateAttachment(state, value, idx),
|
||||
onMove: (delta) {
|
||||
state.attachments.value = ComposeLogic.moveAttachment(
|
||||
state.attachments.value,
|
||||
@@ -265,6 +257,9 @@ class PostComposeScreen extends HookConsumerWidget {
|
||||
() => ComposeLogic.uploadAttachment(ref, state, idx),
|
||||
onDelete:
|
||||
() => ComposeLogic.deleteAttachment(ref, state, idx),
|
||||
onUpdate:
|
||||
(value) =>
|
||||
ComposeLogic.updateAttachment(state, value, idx),
|
||||
onMove: (delta) {
|
||||
state.attachments.value = ComposeLogic.moveAttachment(
|
||||
state.attachments.value,
|
||||
@@ -287,43 +282,10 @@ class PostComposeScreen extends HookConsumerWidget {
|
||||
}
|
||||
},
|
||||
child: AppScaffold(
|
||||
noBackground: false,
|
||||
isNoBackground: false,
|
||||
appBar: AppBar(
|
||||
leading: const PageBackButton(),
|
||||
actions: [
|
||||
if (originalPost == null) // Only show drafts for new posts
|
||||
IconButton(
|
||||
icon: const Icon(Symbols.draft),
|
||||
onPressed: () {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
builder:
|
||||
(context) => DraftManagerSheet(
|
||||
onDraftSelected: (draftId) {
|
||||
final draft =
|
||||
ref.read(
|
||||
composeStorageNotifierProvider,
|
||||
)[draftId];
|
||||
if (draft != null) {
|
||||
state.titleController.text = draft.title ?? '';
|
||||
state.descriptionController.text =
|
||||
draft.description ?? '';
|
||||
state.contentController.text =
|
||||
draft.content ?? '';
|
||||
state.visibility.value = draft.visibility;
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
tooltip: 'drafts'.tr(),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Symbols.save),
|
||||
onPressed: () => ComposeLogic.saveDraft(ref, state),
|
||||
tooltip: 'saveDraft'.tr(),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Symbols.settings),
|
||||
onPressed: showSettingsSheet,
|
||||
@@ -397,31 +359,73 @@ class PostComposeScreen extends HookConsumerWidget {
|
||||
|
||||
// Post content form
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Content field with borderless design
|
||||
RawKeyboardListener(
|
||||
focusNode: FocusNode(),
|
||||
onKey:
|
||||
(event) => ComposeLogic.handleKeyPress(
|
||||
event,
|
||||
state,
|
||||
ref,
|
||||
context,
|
||||
originalPost: originalPost,
|
||||
repliedPost: repliedPost,
|
||||
forwardedPost: forwardedPost,
|
||||
child: KeyboardListener(
|
||||
focusNode: FocusNode(),
|
||||
onKeyEvent:
|
||||
(event) => ComposeLogic.handleKeyPress(
|
||||
event,
|
||||
state,
|
||||
ref,
|
||||
context,
|
||||
originalPost: originalPost,
|
||||
repliedPost: repliedPost,
|
||||
forwardedPost: forwardedPost,
|
||||
),
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
TextField(
|
||||
controller: state.titleController,
|
||||
decoration: InputDecoration(
|
||||
hintText: 'postTitle'.tr(),
|
||||
border: InputBorder.none,
|
||||
isCollapsed: true,
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
vertical: 8,
|
||||
horizontal: 8,
|
||||
),
|
||||
child: TextField(
|
||||
),
|
||||
style: theme.textTheme.titleMedium,
|
||||
onTapOutside:
|
||||
(_) =>
|
||||
FocusManager.instance.primaryFocus
|
||||
?.unfocus(),
|
||||
),
|
||||
TextField(
|
||||
controller: state.descriptionController,
|
||||
decoration: InputDecoration(
|
||||
hintText: 'postDescription'.tr(),
|
||||
border: InputBorder.none,
|
||||
isCollapsed: true,
|
||||
contentPadding: const EdgeInsets.fromLTRB(
|
||||
8,
|
||||
4,
|
||||
8,
|
||||
12,
|
||||
),
|
||||
),
|
||||
style: theme.textTheme.bodyMedium,
|
||||
minLines: 1,
|
||||
maxLines: 3,
|
||||
onTapOutside:
|
||||
(_) =>
|
||||
FocusManager.instance.primaryFocus
|
||||
?.unfocus(),
|
||||
),
|
||||
// Content field with borderless design
|
||||
TextField(
|
||||
controller: state.contentController,
|
||||
style: theme.textTheme.bodyMedium,
|
||||
decoration: InputDecoration(
|
||||
border: InputBorder.none,
|
||||
hintText: 'postContent'.tr(),
|
||||
contentPadding: const EdgeInsets.all(8),
|
||||
isCollapsed: true,
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
vertical: 8,
|
||||
horizontal: 8,
|
||||
),
|
||||
),
|
||||
maxLines: null,
|
||||
onTapOutside:
|
||||
@@ -429,23 +433,23 @@ class PostComposeScreen extends HookConsumerWidget {
|
||||
FocusManager.instance.primaryFocus
|
||||
?.unfocus(),
|
||||
),
|
||||
),
|
||||
|
||||
const Gap(8),
|
||||
const Gap(8),
|
||||
|
||||
// Attachments preview
|
||||
if (state.attachments.value.isNotEmpty)
|
||||
LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final isWide = isWideScreen(context);
|
||||
return isWide
|
||||
? buildWideAttachmentGrid()
|
||||
: buildNarrowAttachmentList();
|
||||
},
|
||||
)
|
||||
else
|
||||
const SizedBox.shrink(),
|
||||
],
|
||||
// Attachments preview
|
||||
if (state.attachments.value.isNotEmpty)
|
||||
LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final isWide = isWideScreen(context);
|
||||
return isWide
|
||||
? buildWideAttachmentGrid()
|
||||
: buildNarrowAttachmentList();
|
||||
},
|
||||
)
|
||||
else
|
||||
const SizedBox.shrink(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -455,27 +459,7 @@ class PostComposeScreen extends HookConsumerWidget {
|
||||
),
|
||||
|
||||
// Bottom toolbar
|
||||
Material(
|
||||
elevation: 4,
|
||||
child: Row(
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: () => ComposeLogic.pickPhotoMedia(ref, state),
|
||||
icon: const Icon(Symbols.add_a_photo),
|
||||
color: colorScheme.primary,
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () => ComposeLogic.pickVideoMedia(ref, state),
|
||||
icon: const Icon(Symbols.videocam),
|
||||
color: colorScheme.primary,
|
||||
),
|
||||
],
|
||||
).padding(
|
||||
bottom: MediaQuery.of(context).padding.bottom + 16,
|
||||
horizontal: 16,
|
||||
top: 8,
|
||||
),
|
||||
),
|
||||
ComposeToolbar(state: state, originalPost: originalPost),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -650,7 +634,7 @@ class PostComposeScreen extends HookConsumerWidget {
|
||||
child: SingleChildScrollView(
|
||||
controller: scrollController,
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: PostItem(item: post, isOpenable: false),
|
||||
child: PostItem(item: post),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -19,8 +19,8 @@ import 'package:island/widgets/content/markdown.dart';
|
||||
import 'package:island/widgets/post/compose_shared.dart';
|
||||
import 'package:island/widgets/post/compose_settings_sheet.dart';
|
||||
import 'package:island/services/compose_storage_db.dart';
|
||||
import 'package:island/widgets/post/compose_toolbar.dart';
|
||||
import 'package:island/widgets/post/publishers_modal.dart';
|
||||
import 'package:island/widgets/post/draft_manager.dart';
|
||||
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
@@ -138,21 +138,62 @@ class ArticleComposeScreen extends HookConsumerWidget {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
builder:
|
||||
(context) => ComposeSettingsSheet(
|
||||
titleController: state.titleController,
|
||||
descriptionController: state.descriptionController,
|
||||
visibility: state.visibility,
|
||||
tagsController: state.tagsController,
|
||||
categoriesController: state.categoriesController,
|
||||
onVisibilityChanged: () {
|
||||
// Trigger rebuild if needed
|
||||
},
|
||||
),
|
||||
builder: (context) => ComposeSettingsSheet(state: state),
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildPreviewPane() {
|
||||
final widgetItem = SingleChildScrollView(
|
||||
padding: const EdgeInsets.symmetric(vertical: 14, horizontal: 8),
|
||||
child: ValueListenableBuilder<TextEditingValue>(
|
||||
valueListenable: state.titleController,
|
||||
builder: (context, titleValue, _) {
|
||||
return ValueListenableBuilder<TextEditingValue>(
|
||||
valueListenable: state.descriptionController,
|
||||
builder: (context, descriptionValue, _) {
|
||||
return ValueListenableBuilder<TextEditingValue>(
|
||||
valueListenable: state.contentController,
|
||||
builder: (context, contentValue, _) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (titleValue.text.isNotEmpty) ...[
|
||||
Text(
|
||||
titleValue.text,
|
||||
style: theme.textTheme.headlineSmall?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const Gap(16),
|
||||
],
|
||||
if (descriptionValue.text.isNotEmpty) ...[
|
||||
Text(
|
||||
descriptionValue.text,
|
||||
style: theme.textTheme.bodyLarge?.copyWith(
|
||||
color: colorScheme.onSurface.withOpacity(0.7),
|
||||
),
|
||||
),
|
||||
const Gap(16),
|
||||
],
|
||||
if (contentValue.text.isNotEmpty)
|
||||
MarkdownTextContent(
|
||||
content: contentValue.text,
|
||||
textStyle: theme.textTheme.bodyMedium,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
if (isWideScreen(context)) {
|
||||
return Align(alignment: Alignment.topLeft, child: widgetItem);
|
||||
}
|
||||
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: colorScheme.outline.withOpacity(0.3)),
|
||||
@@ -178,210 +219,155 @@ class ArticleComposeScreen extends HookConsumerWidget {
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: ValueListenableBuilder<TextEditingValue>(
|
||||
valueListenable: state.titleController,
|
||||
builder: (context, titleValue, _) {
|
||||
return ValueListenableBuilder<TextEditingValue>(
|
||||
valueListenable: state.descriptionController,
|
||||
builder: (context, descriptionValue, _) {
|
||||
return ValueListenableBuilder<TextEditingValue>(
|
||||
valueListenable: state.contentController,
|
||||
builder: (context, contentValue, _) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (titleValue.text.isNotEmpty) ...[
|
||||
Text(
|
||||
titleValue.text,
|
||||
style: theme.textTheme.headlineSmall
|
||||
?.copyWith(fontWeight: FontWeight.bold),
|
||||
),
|
||||
const Gap(16),
|
||||
],
|
||||
if (descriptionValue.text.isNotEmpty) ...[
|
||||
Text(
|
||||
descriptionValue.text,
|
||||
style: theme.textTheme.bodyLarge?.copyWith(
|
||||
color: colorScheme.onSurface.withOpacity(
|
||||
0.7,
|
||||
),
|
||||
),
|
||||
),
|
||||
const Gap(16),
|
||||
],
|
||||
if (contentValue.text.isNotEmpty)
|
||||
MarkdownTextContent(
|
||||
content: contentValue.text,
|
||||
textStyle: theme.textTheme.bodyMedium,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(child: widgetItem),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildEditorPane() {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Publisher row
|
||||
Card(
|
||||
margin: EdgeInsets.only(top: 8),
|
||||
elevation: 1,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Row(
|
||||
children: [
|
||||
GestureDetector(
|
||||
child: ProfilePictureWidget(
|
||||
fileId: state.currentPublisher.value?.picture?.id,
|
||||
radius: 20,
|
||||
fallbackIcon:
|
||||
state.currentPublisher.value == null
|
||||
? Symbols.question_mark
|
||||
: null,
|
||||
),
|
||||
onTap: () {
|
||||
showModalBottomSheet(
|
||||
isScrollControlled: true,
|
||||
context: context,
|
||||
builder: (context) => const PublisherModal(),
|
||||
).then((value) {
|
||||
if (value != null) {
|
||||
state.currentPublisher.value = value;
|
||||
}
|
||||
});
|
||||
},
|
||||
),
|
||||
const Gap(16),
|
||||
if (state.currentPublisher.value == null)
|
||||
Text(
|
||||
'postPublisherUnselected'.tr(),
|
||||
style: theme.textTheme.bodyMedium,
|
||||
)
|
||||
else
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(state.currentPublisher.value!.nick).bold(),
|
||||
Text(
|
||||
'@${state.currentPublisher.value!.name}',
|
||||
).fontSize(12),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Content field with keyboard listener
|
||||
Expanded(
|
||||
child: RawKeyboardListener(
|
||||
focusNode: FocusNode(),
|
||||
onKey:
|
||||
(event) => _handleKeyPress(
|
||||
event,
|
||||
state,
|
||||
ref,
|
||||
context,
|
||||
originalPost: originalPost,
|
||||
),
|
||||
child: TextField(
|
||||
controller: state.contentController,
|
||||
style: theme.textTheme.bodyMedium,
|
||||
return Center(
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 560),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
TextField(
|
||||
controller: state.titleController,
|
||||
decoration: InputDecoration(
|
||||
hintText: 'postTitle'.tr(),
|
||||
border: InputBorder.none,
|
||||
hintText: 'postContent'.tr(),
|
||||
contentPadding: const EdgeInsets.all(8),
|
||||
isCollapsed: true,
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
vertical: 8,
|
||||
horizontal: 8,
|
||||
),
|
||||
),
|
||||
maxLines: null,
|
||||
expands: true,
|
||||
textAlignVertical: TextAlignVertical.top,
|
||||
style: theme.textTheme.titleMedium,
|
||||
onTapOutside:
|
||||
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Attachments preview
|
||||
ValueListenableBuilder<List<UniversalFile>>(
|
||||
valueListenable: state.attachments,
|
||||
builder: (context, attachments, _) {
|
||||
if (attachments.isEmpty) return const SizedBox.shrink();
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Gap(16),
|
||||
Text(
|
||||
'articleAttachmentHint'.tr(),
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
TextField(
|
||||
controller: state.descriptionController,
|
||||
decoration: InputDecoration(
|
||||
hintText: 'postDescription'.tr(),
|
||||
border: InputBorder.none,
|
||||
isCollapsed: true,
|
||||
contentPadding: const EdgeInsets.fromLTRB(8, 4, 8, 12),
|
||||
),
|
||||
style: theme.textTheme.bodyMedium,
|
||||
minLines: 1,
|
||||
maxLines: 3,
|
||||
onTapOutside:
|
||||
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||
),
|
||||
Expanded(
|
||||
child: KeyboardListener(
|
||||
focusNode: FocusNode(),
|
||||
onKeyEvent:
|
||||
(event) => _handleKeyPress(
|
||||
event,
|
||||
state,
|
||||
ref,
|
||||
context,
|
||||
originalPost: originalPost,
|
||||
),
|
||||
child: TextField(
|
||||
controller: state.contentController,
|
||||
style: theme.textTheme.bodyMedium,
|
||||
decoration: InputDecoration(
|
||||
border: InputBorder.none,
|
||||
hintText: 'postContent'.tr(),
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
vertical: 16,
|
||||
horizontal: 8,
|
||||
),
|
||||
),
|
||||
).padding(bottom: 8),
|
||||
ValueListenableBuilder<Map<int, double>>(
|
||||
valueListenable: state.attachmentProgress,
|
||||
builder: (context, progressMap, _) {
|
||||
return Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: [
|
||||
for (var idx = 0; idx < attachments.length; idx++)
|
||||
SizedBox(
|
||||
width: 280,
|
||||
height: 280,
|
||||
child: AttachmentPreview(
|
||||
item: attachments[idx],
|
||||
progress: progressMap[idx],
|
||||
onRequestUpload:
|
||||
() => ComposeLogic.uploadAttachment(
|
||||
ref,
|
||||
state,
|
||||
idx,
|
||||
),
|
||||
onDelete:
|
||||
() => ComposeLogic.deleteAttachment(
|
||||
ref,
|
||||
state,
|
||||
idx,
|
||||
),
|
||||
onMove: (delta) {
|
||||
state
|
||||
.attachments
|
||||
.value = ComposeLogic.moveAttachment(
|
||||
state.attachments.value,
|
||||
idx,
|
||||
delta,
|
||||
);
|
||||
},
|
||||
onInsert:
|
||||
() => ComposeLogic.insertAttachment(
|
||||
ref,
|
||||
state,
|
||||
idx,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
maxLines: null,
|
||||
expands: true,
|
||||
textAlignVertical: TextAlignVertical.top,
|
||||
onTapOutside:
|
||||
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
|
||||
// Attachments preview
|
||||
ValueListenableBuilder<List<UniversalFile>>(
|
||||
valueListenable: state.attachments,
|
||||
builder: (context, attachments, _) {
|
||||
if (attachments.isEmpty) return const SizedBox.shrink();
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Gap(16),
|
||||
Text(
|
||||
'articleAttachmentHint'.tr(),
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
).padding(bottom: 8),
|
||||
ValueListenableBuilder<Map<int, double>>(
|
||||
valueListenable: state.attachmentProgress,
|
||||
builder: (context, progressMap, _) {
|
||||
return Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: [
|
||||
for (var idx = 0; idx < attachments.length; idx++)
|
||||
SizedBox(
|
||||
width: 280,
|
||||
height: 280,
|
||||
child: AttachmentPreview(
|
||||
item: attachments[idx],
|
||||
progress: progressMap[idx],
|
||||
onRequestUpload:
|
||||
() => ComposeLogic.uploadAttachment(
|
||||
ref,
|
||||
state,
|
||||
idx,
|
||||
),
|
||||
onUpdate:
|
||||
(value) =>
|
||||
ComposeLogic.updateAttachment(
|
||||
state,
|
||||
value,
|
||||
idx,
|
||||
),
|
||||
onDelete:
|
||||
() => ComposeLogic.deleteAttachment(
|
||||
ref,
|
||||
state,
|
||||
idx,
|
||||
),
|
||||
onMove: (delta) {
|
||||
state
|
||||
.attachments
|
||||
.value = ComposeLogic.moveAttachment(
|
||||
state.attachments.value,
|
||||
idx,
|
||||
delta,
|
||||
);
|
||||
},
|
||||
onInsert:
|
||||
() => ComposeLogic.insertAttachment(
|
||||
ref,
|
||||
state,
|
||||
idx,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -392,7 +378,7 @@ class ArticleComposeScreen extends HookConsumerWidget {
|
||||
}
|
||||
},
|
||||
child: AppScaffold(
|
||||
noBackground: false,
|
||||
isNoBackground: false,
|
||||
appBar: AppBar(
|
||||
leading: const PageBackButton(),
|
||||
title: ValueListenableBuilder<TextEditingValue>(
|
||||
@@ -406,38 +392,26 @@ class ArticleComposeScreen extends HookConsumerWidget {
|
||||
actions: [
|
||||
// Info banner for article compose
|
||||
const SizedBox.shrink(),
|
||||
if (originalPost == null) // Only show drafts for new articles
|
||||
IconButton(
|
||||
icon: const Icon(Symbols.draft),
|
||||
onPressed: () {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
builder:
|
||||
(context) => DraftManagerSheet(
|
||||
onDraftSelected: (draftId) {
|
||||
final draft =
|
||||
ref.read(
|
||||
composeStorageNotifierProvider,
|
||||
)[draftId];
|
||||
if (draft != null) {
|
||||
state.titleController.text = draft.title ?? '';
|
||||
state.descriptionController.text =
|
||||
draft.description ?? '';
|
||||
state.contentController.text =
|
||||
draft.content ?? '';
|
||||
state.visibility.value = draft.visibility;
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
tooltip: 'drafts'.tr(),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Symbols.save),
|
||||
onPressed: () => ComposeLogic.saveDraft(ref, state),
|
||||
tooltip: 'saveDraft'.tr(),
|
||||
icon: ProfilePictureWidget(
|
||||
fileId: state.currentPublisher.value?.picture?.id,
|
||||
radius: 12,
|
||||
fallbackIcon:
|
||||
state.currentPublisher.value == null
|
||||
? Symbols.question_mark
|
||||
: null,
|
||||
),
|
||||
onPressed: () {
|
||||
showModalBottomSheet(
|
||||
isScrollControlled: true,
|
||||
context: context,
|
||||
builder: (context) => const PublisherModal(),
|
||||
).then((value) {
|
||||
if (value != null) {
|
||||
state.currentPublisher.value = value;
|
||||
}
|
||||
});
|
||||
},
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Symbols.settings),
|
||||
@@ -499,6 +473,7 @@ class ArticleComposeScreen extends HookConsumerWidget {
|
||||
flex: showPreview.value ? 1 : 2,
|
||||
child: buildEditorPane(),
|
||||
),
|
||||
if (showPreview.value) const VerticalDivider(),
|
||||
if (showPreview.value)
|
||||
Expanded(child: buildPreviewPane()),
|
||||
],
|
||||
@@ -510,27 +485,7 @@ class ArticleComposeScreen extends HookConsumerWidget {
|
||||
),
|
||||
|
||||
// Bottom toolbar
|
||||
Material(
|
||||
elevation: 4,
|
||||
child: Row(
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: () => ComposeLogic.pickPhotoMedia(ref, state),
|
||||
icon: const Icon(Symbols.add_a_photo),
|
||||
color: colorScheme.primary,
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () => ComposeLogic.pickVideoMedia(ref, state),
|
||||
icon: const Icon(Symbols.videocam),
|
||||
color: colorScheme.primary,
|
||||
),
|
||||
],
|
||||
).padding(
|
||||
bottom: MediaQuery.of(context).padding.bottom + 16,
|
||||
horizontal: 16,
|
||||
top: 8,
|
||||
),
|
||||
),
|
||||
ComposeToolbar(state: state, originalPost: originalPost),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -539,7 +494,7 @@ class ArticleComposeScreen extends HookConsumerWidget {
|
||||
|
||||
// Helper method to handle keyboard shortcuts
|
||||
void _handleKeyPress(
|
||||
RawKeyEvent event,
|
||||
KeyEvent event,
|
||||
ComposeState state,
|
||||
WidgetRef ref,
|
||||
BuildContext context, {
|
||||
@@ -549,7 +504,9 @@ class ArticleComposeScreen extends HookConsumerWidget {
|
||||
|
||||
final isPaste = event.logicalKey == LogicalKeyboardKey.keyV;
|
||||
final isSave = event.logicalKey == LogicalKeyboardKey.keyS;
|
||||
final isModifierPressed = event.isMetaPressed || event.isControlPressed;
|
||||
final isModifierPressed =
|
||||
HardwareKeyboard.instance.isMetaPressed ||
|
||||
HardwareKeyboard.instance.isControlPressed;
|
||||
final isSubmit = event.logicalKey == LogicalKeyboardKey.enter;
|
||||
|
||||
if (isPaste && isModifierPressed) {
|
||||
|
||||
107
lib/screens/posts/post_category_detail.dart
Normal file
107
lib/screens/posts/post_category_detail.dart
Normal file
@@ -0,0 +1,107 @@
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:island/models/post_category.dart';
|
||||
import 'package:island/models/post_tag.dart';
|
||||
import 'package:island/pods/network.dart';
|
||||
import 'package:island/widgets/app_scaffold.dart';
|
||||
import 'package:island/widgets/post/post_list.dart';
|
||||
import 'package:island/widgets/response.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
|
||||
part 'post_category_detail.g.dart';
|
||||
|
||||
@riverpod
|
||||
Future<SnPostCategory> postCategory(Ref ref, String slug) async {
|
||||
final apiClient = ref.watch(apiClientProvider);
|
||||
final resp = await apiClient.get('/sphere/posts/categories/$slug');
|
||||
return SnPostCategory.fromJson(resp.data);
|
||||
}
|
||||
|
||||
@riverpod
|
||||
Future<SnPostTag> postTag(Ref ref, String slug) async {
|
||||
final apiClient = ref.watch(apiClientProvider);
|
||||
final resp = await apiClient.get('/sphere/posts/tags/$slug');
|
||||
return SnPostTag.fromJson(resp.data);
|
||||
}
|
||||
|
||||
class PostCategoryDetailScreen extends HookConsumerWidget {
|
||||
final String slug;
|
||||
final bool isCategory;
|
||||
const PostCategoryDetailScreen({
|
||||
super.key,
|
||||
required this.slug,
|
||||
required this.isCategory,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final postCategory =
|
||||
isCategory ? ref.watch(postCategoryProvider(slug)) : null;
|
||||
final postTag = isCategory ? null : ref.watch(postTagProvider(slug));
|
||||
|
||||
final postFilterTitle =
|
||||
isCategory
|
||||
? postCategory?.value?.categoryDisplayTitle ?? 'loading'
|
||||
: postTag?.value?.name ?? postTag?.value?.slug ?? 'loading';
|
||||
|
||||
return AppScaffold(
|
||||
isNoBackground: false,
|
||||
appBar: AppBar(title: Text(postFilterTitle).tr()),
|
||||
body: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (isCategory)
|
||||
postCategory!.when(
|
||||
data:
|
||||
(category) => Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(category.categoryDisplayTitle).bold().fontSize(15),
|
||||
Text('A category'),
|
||||
],
|
||||
).padding(horizontal: 24, vertical: 16),
|
||||
error:
|
||||
(error, _) => ResponseErrorWidget(
|
||||
error: error,
|
||||
onRetry: () => ref.invalidate(postCategoryProvider(slug)),
|
||||
),
|
||||
loading: () => ResponseLoadingWidget(),
|
||||
)
|
||||
else
|
||||
postTag!.when(
|
||||
data:
|
||||
(tag) => Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(tag.name ?? '#${tag.slug}').bold().fontSize(15),
|
||||
Text('A tag'),
|
||||
],
|
||||
).padding(horizontal: 24, vertical: 16),
|
||||
error:
|
||||
(error, _) => ResponseErrorWidget(
|
||||
error: error,
|
||||
onRetry: () => ref.invalidate(postTagProvider(slug)),
|
||||
),
|
||||
loading: () => ResponseLoadingWidget(),
|
||||
),
|
||||
const Divider(height: 1),
|
||||
Expanded(
|
||||
child: CustomScrollView(
|
||||
slivers: [
|
||||
const SliverGap(4),
|
||||
SliverPostList(
|
||||
categories: isCategory ? [slug] : null,
|
||||
tags: isCategory ? null : [slug],
|
||||
),
|
||||
SliverGap(MediaQuery.of(context).padding.bottom + 8),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
270
lib/screens/posts/post_category_detail.g.dart
Normal file
270
lib/screens/posts/post_category_detail.g.dart
Normal file
@@ -0,0 +1,270 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'post_category_detail.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$postCategoryHash() => r'0df2de729ba96819ee37377314615abef0c99547';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
_SystemHash._();
|
||||
|
||||
static int combine(int hash, int value) {
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + value);
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
|
||||
return hash ^ (hash >> 6);
|
||||
}
|
||||
|
||||
static int finish(int hash) {
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
|
||||
// ignore: parameter_assignments
|
||||
hash = hash ^ (hash >> 11);
|
||||
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
|
||||
}
|
||||
}
|
||||
|
||||
/// See also [postCategory].
|
||||
@ProviderFor(postCategory)
|
||||
const postCategoryProvider = PostCategoryFamily();
|
||||
|
||||
/// See also [postCategory].
|
||||
class PostCategoryFamily extends Family<AsyncValue<SnPostCategory>> {
|
||||
/// See also [postCategory].
|
||||
const PostCategoryFamily();
|
||||
|
||||
/// See also [postCategory].
|
||||
PostCategoryProvider call(String slug) {
|
||||
return PostCategoryProvider(slug);
|
||||
}
|
||||
|
||||
@override
|
||||
PostCategoryProvider getProviderOverride(
|
||||
covariant PostCategoryProvider provider,
|
||||
) {
|
||||
return call(provider.slug);
|
||||
}
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _dependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
|
||||
_allTransitiveDependencies;
|
||||
|
||||
@override
|
||||
String? get name => r'postCategoryProvider';
|
||||
}
|
||||
|
||||
/// See also [postCategory].
|
||||
class PostCategoryProvider extends AutoDisposeFutureProvider<SnPostCategory> {
|
||||
/// See also [postCategory].
|
||||
PostCategoryProvider(String slug)
|
||||
: this._internal(
|
||||
(ref) => postCategory(ref as PostCategoryRef, slug),
|
||||
from: postCategoryProvider,
|
||||
name: r'postCategoryProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$postCategoryHash,
|
||||
dependencies: PostCategoryFamily._dependencies,
|
||||
allTransitiveDependencies:
|
||||
PostCategoryFamily._allTransitiveDependencies,
|
||||
slug: slug,
|
||||
);
|
||||
|
||||
PostCategoryProvider._internal(
|
||||
super._createNotifier, {
|
||||
required super.name,
|
||||
required super.dependencies,
|
||||
required super.allTransitiveDependencies,
|
||||
required super.debugGetCreateSourceHash,
|
||||
required super.from,
|
||||
required this.slug,
|
||||
}) : super.internal();
|
||||
|
||||
final String slug;
|
||||
|
||||
@override
|
||||
Override overrideWith(
|
||||
FutureOr<SnPostCategory> Function(PostCategoryRef provider) create,
|
||||
) {
|
||||
return ProviderOverride(
|
||||
origin: this,
|
||||
override: PostCategoryProvider._internal(
|
||||
(ref) => create(ref as PostCategoryRef),
|
||||
from: from,
|
||||
name: null,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
debugGetCreateSourceHash: null,
|
||||
slug: slug,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
AutoDisposeFutureProviderElement<SnPostCategory> createElement() {
|
||||
return _PostCategoryProviderElement(this);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is PostCategoryProvider && other.slug == slug;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||
hash = _SystemHash.combine(hash, slug.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
mixin PostCategoryRef on AutoDisposeFutureProviderRef<SnPostCategory> {
|
||||
/// The parameter `slug` of this provider.
|
||||
String get slug;
|
||||
}
|
||||
|
||||
class _PostCategoryProviderElement
|
||||
extends AutoDisposeFutureProviderElement<SnPostCategory>
|
||||
with PostCategoryRef {
|
||||
_PostCategoryProviderElement(super.provider);
|
||||
|
||||
@override
|
||||
String get slug => (origin as PostCategoryProvider).slug;
|
||||
}
|
||||
|
||||
String _$postTagHash() => r'e050fdf9af81a843a9abd9cf979dd2672e0a2b93';
|
||||
|
||||
/// See also [postTag].
|
||||
@ProviderFor(postTag)
|
||||
const postTagProvider = PostTagFamily();
|
||||
|
||||
/// See also [postTag].
|
||||
class PostTagFamily extends Family<AsyncValue<SnPostTag>> {
|
||||
/// See also [postTag].
|
||||
const PostTagFamily();
|
||||
|
||||
/// See also [postTag].
|
||||
PostTagProvider call(String slug) {
|
||||
return PostTagProvider(slug);
|
||||
}
|
||||
|
||||
@override
|
||||
PostTagProvider getProviderOverride(covariant PostTagProvider provider) {
|
||||
return call(provider.slug);
|
||||
}
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _dependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
|
||||
_allTransitiveDependencies;
|
||||
|
||||
@override
|
||||
String? get name => r'postTagProvider';
|
||||
}
|
||||
|
||||
/// See also [postTag].
|
||||
class PostTagProvider extends AutoDisposeFutureProvider<SnPostTag> {
|
||||
/// See also [postTag].
|
||||
PostTagProvider(String slug)
|
||||
: this._internal(
|
||||
(ref) => postTag(ref as PostTagRef, slug),
|
||||
from: postTagProvider,
|
||||
name: r'postTagProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$postTagHash,
|
||||
dependencies: PostTagFamily._dependencies,
|
||||
allTransitiveDependencies: PostTagFamily._allTransitiveDependencies,
|
||||
slug: slug,
|
||||
);
|
||||
|
||||
PostTagProvider._internal(
|
||||
super._createNotifier, {
|
||||
required super.name,
|
||||
required super.dependencies,
|
||||
required super.allTransitiveDependencies,
|
||||
required super.debugGetCreateSourceHash,
|
||||
required super.from,
|
||||
required this.slug,
|
||||
}) : super.internal();
|
||||
|
||||
final String slug;
|
||||
|
||||
@override
|
||||
Override overrideWith(
|
||||
FutureOr<SnPostTag> Function(PostTagRef provider) create,
|
||||
) {
|
||||
return ProviderOverride(
|
||||
origin: this,
|
||||
override: PostTagProvider._internal(
|
||||
(ref) => create(ref as PostTagRef),
|
||||
from: from,
|
||||
name: null,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
debugGetCreateSourceHash: null,
|
||||
slug: slug,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
AutoDisposeFutureProviderElement<SnPostTag> createElement() {
|
||||
return _PostTagProviderElement(this);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is PostTagProvider && other.slug == slug;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||
hash = _SystemHash.combine(hash, slug.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
mixin PostTagRef on AutoDisposeFutureProviderRef<SnPostTag> {
|
||||
/// The parameter `slug` of this provider.
|
||||
String get slug;
|
||||
}
|
||||
|
||||
class _PostTagProviderElement
|
||||
extends AutoDisposeFutureProviderElement<SnPostTag>
|
||||
with PostTagRef {
|
||||
_PostTagProviderElement(super.provider);
|
||||
|
||||
@override
|
||||
String get slug => (origin as PostTagProvider).slug;
|
||||
}
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
||||
@@ -4,7 +4,6 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:island/models/post.dart';
|
||||
import 'package:island/pods/network.dart';
|
||||
import 'package:island/pods/userinfo.dart';
|
||||
import 'package:island/services/responsive.dart';
|
||||
import 'package:island/widgets/app_scaffold.dart';
|
||||
import 'package:island/widgets/post/post_item.dart';
|
||||
import 'package:island/widgets/post/post_quick_reply.dart';
|
||||
@@ -54,10 +53,8 @@ class PostDetailScreen extends HookConsumerWidget {
|
||||
final postState = ref.watch(postStateProvider(id));
|
||||
final user = ref.watch(userInfoProvider);
|
||||
|
||||
final isWide = isWideScreen(context);
|
||||
|
||||
return AppScaffold(
|
||||
noBackground: false,
|
||||
isNoBackground: false,
|
||||
appBar: AppBar(title: const Text('Post')),
|
||||
body: postState.when(
|
||||
data: (post) {
|
||||
@@ -67,13 +64,13 @@ class PostDetailScreen extends HookConsumerWidget {
|
||||
CustomScrollView(
|
||||
slivers: [
|
||||
SliverToBoxAdapter(
|
||||
child: Column(
|
||||
children: [
|
||||
PostItem(
|
||||
child: Center(
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(maxWidth: 600),
|
||||
child: PostItem(
|
||||
item: post!,
|
||||
isOpenable: false,
|
||||
isFullPost: true,
|
||||
backgroundColor: isWide ? Colors.transparent : null,
|
||||
isEmbedReply: false,
|
||||
onUpdate: (newItem) {
|
||||
// Update the local state with the new post data
|
||||
ref
|
||||
@@ -81,11 +78,10 @@ class PostDetailScreen extends HookConsumerWidget {
|
||||
.updatePost(newItem);
|
||||
},
|
||||
),
|
||||
const Divider(height: 1),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
PostRepliesList(postId: id),
|
||||
PostRepliesList(postId: id, maxWidth: 600),
|
||||
SliverGap(MediaQuery.of(context).padding.bottom + 80),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'package:island/models/post.dart';
|
||||
import 'package:island/pods/network.dart';
|
||||
import 'package:island/widgets/app_scaffold.dart';
|
||||
import 'package:island/widgets/post/post_item.dart';
|
||||
import 'package:island/widgets/response.dart';
|
||||
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
|
||||
|
||||
final postSearchNotifierProvider = StateNotifierProvider.autoDispose<
|
||||
@@ -55,7 +56,7 @@ class PostSearchNotifier
|
||||
'query': _currentQuery,
|
||||
'offset': offset,
|
||||
'take': _pageSize,
|
||||
'useVector': true,
|
||||
'useVector': false,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -109,7 +110,7 @@ class _PostSearchScreenState extends ConsumerState<PostSearchScreen> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AppScaffold(
|
||||
noBackground: false,
|
||||
isNoBackground: false,
|
||||
appBar: AppBar(
|
||||
title: TextField(
|
||||
controller: _searchController,
|
||||
@@ -141,6 +142,7 @@ class _PostSearchScreenState extends ConsumerState<PostSearchScreen> {
|
||||
}
|
||||
|
||||
return ListView.builder(
|
||||
padding: EdgeInsets.zero,
|
||||
itemCount: data.items.length + (data.hasMore ? 1 : 0),
|
||||
itemBuilder: (context, index) {
|
||||
if (index >= data.items.length) {
|
||||
@@ -151,14 +153,27 @@ class _PostSearchScreenState extends ConsumerState<PostSearchScreen> {
|
||||
}
|
||||
|
||||
final post = data.items[index];
|
||||
return Column(
|
||||
children: [PostItem(item: post), const Divider(height: 1)],
|
||||
return Center(
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(maxWidth: 600),
|
||||
child: Card(
|
||||
margin: EdgeInsets.symmetric(
|
||||
horizontal: 8,
|
||||
vertical: 4,
|
||||
),
|
||||
child: PostActionableItem(item: post, borderRadius: 8),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
loading: () => const Center(child: CircularProgressIndicator()),
|
||||
error: (error, stack) => Center(child: Text('Error: $error')),
|
||||
error:
|
||||
(error, stack) => ResponseErrorWidget(
|
||||
error: error,
|
||||
onRetry: () => ref.invalidate(postSearchNotifierProvider),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
@@ -11,12 +11,14 @@ import 'package:island/models/user.dart';
|
||||
import 'package:island/pods/config.dart';
|
||||
import 'package:island/pods/network.dart';
|
||||
import 'package:island/services/color.dart';
|
||||
import 'package:island/services/responsive.dart';
|
||||
import 'package:island/widgets/account/account_name.dart';
|
||||
import 'package:island/widgets/account/badge.dart';
|
||||
import 'package:island/widgets/account/status.dart';
|
||||
import 'package:island/widgets/alert.dart';
|
||||
import 'package:island/widgets/app_scaffold.dart';
|
||||
import 'package:island/widgets/content/cloud_files.dart';
|
||||
import 'package:island/widgets/content/markdown.dart';
|
||||
import 'package:island/widgets/post/post_list.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:palette_generator/palette_generator.dart';
|
||||
@@ -85,13 +87,22 @@ class PublisherProfileScreen extends HookConsumerWidget {
|
||||
publisherAppbarForcegroundColorProvider(name),
|
||||
);
|
||||
|
||||
final categoryTabController = useTabController(initialLength: 3);
|
||||
final categoryTab = useState(0);
|
||||
categoryTabController.addListener(() {
|
||||
categoryTab.value = categoryTabController.index;
|
||||
});
|
||||
|
||||
final subscribing = useState(false);
|
||||
|
||||
Future<void> subscribe() async {
|
||||
final apiClient = ref.watch(apiClientProvider);
|
||||
subscribing.value = true;
|
||||
try {
|
||||
await apiClient.post("/publishers/$name/subscribe", data: {'tier': 0});
|
||||
await apiClient.post(
|
||||
"/sphere/publishers/$name/subscribe",
|
||||
data: {'tier': 0},
|
||||
);
|
||||
ref.invalidate(publisherSubscriptionStatusProvider(name));
|
||||
HapticFeedback.heavyImpact();
|
||||
} catch (err) {
|
||||
@@ -105,7 +116,7 @@ class PublisherProfileScreen extends HookConsumerWidget {
|
||||
final apiClient = ref.watch(apiClientProvider);
|
||||
subscribing.value = true;
|
||||
try {
|
||||
await apiClient.post("/publishers/$name/unsubscribe");
|
||||
await apiClient.post("/sphere/publishers/$name/unsubscribe");
|
||||
ref.invalidate(publisherSubscriptionStatusProvider(name));
|
||||
HapticFeedback.heavyImpact();
|
||||
} catch (err) {
|
||||
@@ -121,210 +132,325 @@ class PublisherProfileScreen extends HookConsumerWidget {
|
||||
offset: Offset(1.0, 1.0),
|
||||
);
|
||||
|
||||
Widget publisherBasisWidget(SnPublisher data) => Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
spacing: 20,
|
||||
children: [
|
||||
GestureDetector(
|
||||
child: Badge(
|
||||
isLabelVisible: data.type == 0,
|
||||
padding: EdgeInsets.all(4),
|
||||
label: Icon(
|
||||
Symbols.launch,
|
||||
size: 16,
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||
offset: Offset(0, 48),
|
||||
child: ProfilePictureWidget(file: data.picture, radius: 32),
|
||||
),
|
||||
onTap: () {
|
||||
Navigator.pop(context, true);
|
||||
if (data.account?.name != null) {
|
||||
context.pushNamed(
|
||||
'accountProfile',
|
||||
pathParameters: {'name': data.account!.name},
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Row(
|
||||
spacing: 6,
|
||||
children: [
|
||||
Text(data.nick).fontSize(20),
|
||||
if (data.verification != null)
|
||||
VerificationMark(mark: data.verification!),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'@${data.name}',
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
).fontSize(14).opacity(0.85),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (data.type == 0 && data.account != null)
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
spacing: 6,
|
||||
children: [
|
||||
Icon(
|
||||
data.type == 0 ? Symbols.person : Symbols.workspaces,
|
||||
fill: 1,
|
||||
size: 17,
|
||||
),
|
||||
Text(
|
||||
'publisherBelongsTo'.tr(args: ['@${data.account!.name}']),
|
||||
).fontSize(14),
|
||||
],
|
||||
).opacity(0.85).padding(bottom: 6),
|
||||
if (data.type == 0 && data.account != null)
|
||||
AccountStatusWidget(
|
||||
uname: data.account!.name,
|
||||
padding: EdgeInsets.zero,
|
||||
),
|
||||
subStatus
|
||||
.when(
|
||||
data:
|
||||
(status) => FilledButton.icon(
|
||||
onPressed:
|
||||
subscribing.value
|
||||
? null
|
||||
: (status.isSubscribed
|
||||
? unsubscribe
|
||||
: subscribe),
|
||||
icon: Icon(
|
||||
status.isSubscribed
|
||||
? Symbols.remove_circle
|
||||
: Symbols.add_circle,
|
||||
),
|
||||
label:
|
||||
Text(
|
||||
status.isSubscribed
|
||||
? 'unsubscribe'
|
||||
: 'subscribe',
|
||||
).tr(),
|
||||
style: ButtonStyle(
|
||||
visualDensity: VisualDensity(vertical: -2),
|
||||
),
|
||||
),
|
||||
error: (_, _) => const SizedBox(),
|
||||
loading:
|
||||
() => const SizedBox(
|
||||
height: 36,
|
||||
child: Center(
|
||||
child: SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
.padding(top: 8),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
).padding(horizontal: 24, top: 24);
|
||||
|
||||
Widget publisherBadgesWidget(SnPublisher data) =>
|
||||
(badges.value?.isNotEmpty ?? false)
|
||||
? Card(
|
||||
child: BadgeList(
|
||||
badges: badges.value!,
|
||||
).padding(horizontal: 26, vertical: 20),
|
||||
).padding(horizontal: 4)
|
||||
: const SizedBox.shrink();
|
||||
|
||||
Widget publisherVerificationWidget(SnPublisher data) =>
|
||||
(data.verification != null)
|
||||
? Card(
|
||||
margin: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
child: VerificationStatusCard(mark: data.verification!),
|
||||
)
|
||||
: const SizedBox.shrink();
|
||||
|
||||
Widget publisherBioWidget(SnPublisher data) => Card(
|
||||
margin: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Text('bio').tr().bold().fontSize(15).padding(bottom: 8),
|
||||
if (data.bio.isEmpty)
|
||||
Text('descriptionNone').tr().italic()
|
||||
else
|
||||
MarkdownTextContent(
|
||||
content: data.bio,
|
||||
linesMargin: EdgeInsets.zero,
|
||||
),
|
||||
],
|
||||
).padding(horizontal: 20, vertical: 16),
|
||||
);
|
||||
|
||||
Widget publisherCategoryTabWidget() => Card(
|
||||
margin: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
child: TabBar(
|
||||
controller: categoryTabController,
|
||||
dividerColor: Colors.transparent,
|
||||
splashBorderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||
tabs: [Tab(text: 'All'), Tab(text: 'Posts'), Tab(text: 'Articles')],
|
||||
),
|
||||
);
|
||||
|
||||
return publisher.when(
|
||||
data:
|
||||
(data) => AppScaffold(
|
||||
noBackground: false,
|
||||
body: CustomScrollView(
|
||||
slivers: [
|
||||
SliverAppBar(
|
||||
foregroundColor: appbarColor.value,
|
||||
expandedHeight: 180,
|
||||
pinned: true,
|
||||
leading: PageBackButton(
|
||||
color: appbarColor.value,
|
||||
shadows: [appbarShadow],
|
||||
),
|
||||
flexibleSpace: Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child:
|
||||
data.background?.id != null
|
||||
? CloudImageWidget(file: data.background)
|
||||
: Container(
|
||||
color:
|
||||
Theme.of(
|
||||
context,
|
||||
).appBarTheme.backgroundColor,
|
||||
),
|
||||
isNoBackground: false,
|
||||
appBar:
|
||||
isWideScreen(context)
|
||||
? AppBar(
|
||||
foregroundColor: appbarColor.value,
|
||||
leading: PageBackButton(
|
||||
color: appbarColor.value,
|
||||
shadows: [appbarShadow],
|
||||
),
|
||||
FlexibleSpaceBar(
|
||||
title: Text(
|
||||
data.nick,
|
||||
style: TextStyle(
|
||||
color:
|
||||
appbarColor.value ??
|
||||
Theme.of(context).appBarTheme.foregroundColor,
|
||||
flexibleSpace: Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child:
|
||||
data.background?.id != null
|
||||
? CloudImageWidget(file: data.background)
|
||||
: Container(
|
||||
color:
|
||||
Theme.of(
|
||||
context,
|
||||
).appBarTheme.backgroundColor,
|
||||
),
|
||||
),
|
||||
FlexibleSpaceBar(
|
||||
title: Text(
|
||||
data.nick,
|
||||
style: TextStyle(
|
||||
color:
|
||||
appbarColor.value ??
|
||||
Theme.of(
|
||||
context,
|
||||
).appBarTheme.foregroundColor,
|
||||
shadows: [appbarShadow],
|
||||
),
|
||||
),
|
||||
background:
|
||||
Container(), // Empty container since background is handled by Stack
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
: null,
|
||||
body:
|
||||
isWideScreen(context)
|
||||
? Row(
|
||||
children: [
|
||||
Flexible(
|
||||
flex: 4,
|
||||
child: CustomScrollView(
|
||||
slivers: [
|
||||
SliverGap(16),
|
||||
SliverToBoxAdapter(
|
||||
child: publisherCategoryTabWidget(),
|
||||
),
|
||||
SliverPostList(
|
||||
key: ValueKey(categoryTab.value),
|
||||
pubName: name,
|
||||
type: switch (categoryTab.value) {
|
||||
1 => 0,
|
||||
2 => 1,
|
||||
_ => null,
|
||||
},
|
||||
),
|
||||
SliverGap(
|
||||
MediaQuery.of(context).padding.bottom + 16,
|
||||
),
|
||||
],
|
||||
).padding(left: 8),
|
||||
),
|
||||
Flexible(
|
||||
flex: 3,
|
||||
child: Align(
|
||||
alignment: Alignment.topLeft,
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
publisherBasisWidget(data).padding(bottom: 8),
|
||||
publisherBadgesWidget(data),
|
||||
publisherVerificationWidget(data),
|
||||
publisherBioWidget(data),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
: CustomScrollView(
|
||||
slivers: [
|
||||
SliverAppBar(
|
||||
foregroundColor: appbarColor.value,
|
||||
expandedHeight: 180,
|
||||
pinned: true,
|
||||
leading: PageBackButton(
|
||||
color: appbarColor.value,
|
||||
shadows: [appbarShadow],
|
||||
),
|
||||
),
|
||||
background:
|
||||
Container(), // Empty container since background is handled by Stack
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
spacing: 20,
|
||||
children: [
|
||||
GestureDetector(
|
||||
child: Badge(
|
||||
isLabelVisible: data.type == 0,
|
||||
padding: EdgeInsets.all(4),
|
||||
label: Icon(
|
||||
Symbols.launch,
|
||||
size: 16,
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
backgroundColor:
|
||||
Theme.of(context).colorScheme.primary,
|
||||
offset: Offset(0, 48),
|
||||
child: ProfilePictureWidget(
|
||||
file: data.picture,
|
||||
radius: 32,
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
Navigator.pop(context, true);
|
||||
if (data.account?.name != null) {
|
||||
context.pushNamed(
|
||||
'accountProfile',
|
||||
pathParameters: {'name': data.account!.name},
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Row(
|
||||
spacing: 6,
|
||||
children: [
|
||||
Text(data.nick).fontSize(20),
|
||||
if (data.verification != null)
|
||||
VerificationMark(mark: data.verification!),
|
||||
Text(
|
||||
'@${data.name}',
|
||||
).fontSize(14).opacity(0.85),
|
||||
],
|
||||
),
|
||||
if (data.type == 0 && data.account != null)
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
spacing: 6,
|
||||
children: [
|
||||
Icon(
|
||||
data.type == 0
|
||||
? Symbols.person
|
||||
: Symbols.workspaces,
|
||||
fill: 1,
|
||||
size: 17,
|
||||
),
|
||||
Text(
|
||||
'publisherBelongsTo'.tr(
|
||||
args: ['@${data.account!.name}'],
|
||||
),
|
||||
).fontSize(14),
|
||||
],
|
||||
).opacity(0.85).padding(bottom: 6),
|
||||
if (data.type == 0 && data.account != null)
|
||||
AccountStatusWidget(
|
||||
uname: data.account!.name,
|
||||
padding: EdgeInsets.zero,
|
||||
flexibleSpace: Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child:
|
||||
data.background?.id != null
|
||||
? CloudImageWidget(
|
||||
file: data.background,
|
||||
)
|
||||
: Container(
|
||||
color:
|
||||
Theme.of(
|
||||
context,
|
||||
).appBarTheme.backgroundColor,
|
||||
),
|
||||
),
|
||||
subStatus
|
||||
.when(
|
||||
data:
|
||||
(status) => FilledButton.icon(
|
||||
onPressed:
|
||||
subscribing.value
|
||||
? null
|
||||
: (status.isSubscribed
|
||||
? unsubscribe
|
||||
: subscribe),
|
||||
icon: Icon(
|
||||
status.isSubscribed
|
||||
? Symbols.remove_circle
|
||||
: Symbols.add_circle,
|
||||
),
|
||||
label:
|
||||
Text(
|
||||
status.isSubscribed
|
||||
? 'unsubscribe'
|
||||
: 'subscribe',
|
||||
).tr(),
|
||||
style: ButtonStyle(
|
||||
visualDensity: VisualDensity(
|
||||
vertical: -2,
|
||||
),
|
||||
),
|
||||
),
|
||||
error: (_, _) => const SizedBox(),
|
||||
loading:
|
||||
() => const SizedBox(
|
||||
height: 36,
|
||||
child: Center(
|
||||
child: SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
.padding(top: 8),
|
||||
],
|
||||
FlexibleSpaceBar(
|
||||
title: Text(
|
||||
data.nick,
|
||||
style: TextStyle(
|
||||
color:
|
||||
appbarColor.value ??
|
||||
Theme.of(
|
||||
context,
|
||||
).appBarTheme.foregroundColor,
|
||||
shadows: [appbarShadow],
|
||||
),
|
||||
),
|
||||
background:
|
||||
Container(), // Empty container since background is handled by Stack
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
).padding(horizontal: 24, top: 24),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: Column(
|
||||
children: [
|
||||
if (badges.value?.isNotEmpty ?? false)
|
||||
BadgeList(badges: badges.value!).padding(top: 16),
|
||||
if (data.verification != null)
|
||||
VerificationStatusCard(
|
||||
mark: data.verification!,
|
||||
).padding(top: 16),
|
||||
],
|
||||
).padding(horizontal: 24),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: const Divider(height: 1).padding(vertical: 24),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Text('bio').tr().bold(),
|
||||
Text(
|
||||
data.bio.isEmpty ? 'descriptionNone'.tr() : data.bio,
|
||||
),
|
||||
],
|
||||
).padding(horizontal: 24),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: const Divider(height: 1).padding(top: 24),
|
||||
),
|
||||
SliverPostList(pubName: name),
|
||||
SliverGap(MediaQuery.of(context).padding.bottom + 16),
|
||||
],
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: publisherBasisWidget(data).padding(bottom: 8),
|
||||
),
|
||||
SliverToBoxAdapter(child: publisherBadgesWidget(data)),
|
||||
SliverToBoxAdapter(
|
||||
child: publisherVerificationWidget(data),
|
||||
),
|
||||
SliverToBoxAdapter(child: publisherBioWidget(data)),
|
||||
SliverToBoxAdapter(child: publisherCategoryTabWidget()),
|
||||
SliverPostList(
|
||||
key: ValueKey(categoryTab.value),
|
||||
pubName: name,
|
||||
type: switch (categoryTab.value) {
|
||||
1 => 0,
|
||||
2 => 1,
|
||||
_ => null,
|
||||
},
|
||||
),
|
||||
SliverGap(MediaQuery.of(context).padding.bottom + 16),
|
||||
],
|
||||
),
|
||||
),
|
||||
error:
|
||||
(error, stackTrace) => AppScaffold(
|
||||
isNoBackground: false,
|
||||
appBar: AppBar(leading: const PageBackButton()),
|
||||
body: Center(child: Text(error.toString())),
|
||||
),
|
||||
loading:
|
||||
() => AppScaffold(
|
||||
isNoBackground: false,
|
||||
appBar: AppBar(leading: const PageBackButton()),
|
||||
body: Center(child: CircularProgressIndicator()),
|
||||
),
|
||||
|
||||
@@ -79,7 +79,7 @@ class RealmDetailScreen extends HookConsumerWidget {
|
||||
);
|
||||
|
||||
return AppScaffold(
|
||||
noBackground: false,
|
||||
isNoBackground: false,
|
||||
body: realmState.when(
|
||||
loading: () => const Center(child: CircularProgressIndicator()),
|
||||
error: (error, _) => Center(child: Text('Error: $error')),
|
||||
@@ -321,10 +321,10 @@ class _RealmActionMenu extends HookConsumerWidget {
|
||||
showConfirmAlert(
|
||||
'leaveRealmHint'.tr(),
|
||||
'leaveRealm'.tr(),
|
||||
).then((confirm) {
|
||||
).then((confirm) async {
|
||||
if (confirm) {
|
||||
final client = ref.watch(apiClientProvider);
|
||||
client.delete(
|
||||
await client.delete(
|
||||
'/sphere/realms/$realmSlug/members/me',
|
||||
);
|
||||
ref.invalidate(realmsJoinedProvider);
|
||||
@@ -361,10 +361,12 @@ class _RealmActionMenu extends HookConsumerWidget {
|
||||
showConfirmAlert(
|
||||
'leaveRealmHint'.tr(),
|
||||
'leaveRealm'.tr(),
|
||||
).then((confirm) {
|
||||
).then((confirm) async {
|
||||
if (confirm) {
|
||||
final client = ref.watch(apiClientProvider);
|
||||
client.delete('/sphere/realms/$realmSlug/members/me');
|
||||
await client.delete(
|
||||
'/sphere/realms/$realmSlug/members/me',
|
||||
);
|
||||
ref.invalidate(realmsJoinedProvider);
|
||||
if (context.mounted) {
|
||||
context.pop(true);
|
||||
|
||||
@@ -41,7 +41,7 @@ class RealmListScreen extends HookConsumerWidget {
|
||||
final realmInvites = ref.watch(realmInvitesProvider);
|
||||
|
||||
return AppScaffold(
|
||||
noBackground: false,
|
||||
isNoBackground: false,
|
||||
appBar: AppBar(
|
||||
title: const Text('realms').tr(),
|
||||
actions: [
|
||||
@@ -279,7 +279,7 @@ class EditRealmScreen extends HookConsumerWidget {
|
||||
}
|
||||
|
||||
return AppScaffold(
|
||||
noBackground: false,
|
||||
isNoBackground: false,
|
||||
appBar: AppBar(
|
||||
title: Text(slug == null ? 'createRealm'.tr() : 'editRealm'.tr()),
|
||||
leading: const PageBackButton(),
|
||||
|
||||
@@ -552,7 +552,7 @@ class SettingsScreen extends HookConsumerWidget {
|
||||
}
|
||||
|
||||
return AppScaffold(
|
||||
noBackground: false,
|
||||
isNoBackground: false,
|
||||
appBar: AppBar(
|
||||
title: Text('settings').tr(),
|
||||
actions:
|
||||
|
||||
103
lib/screens/stickers/marketplace.dart
Normal file
103
lib/screens/stickers/marketplace.dart
Normal file
@@ -0,0 +1,103 @@
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:island/models/sticker.dart';
|
||||
import 'package:island/pods/network.dart';
|
||||
import 'package:island/widgets/app_scaffold.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
|
||||
|
||||
part 'marketplace.g.dart';
|
||||
|
||||
@riverpod
|
||||
class MarketplaceStickerPacksNotifier extends _$MarketplaceStickerPacksNotifier
|
||||
with CursorPagingNotifierMixin<SnStickerPack> {
|
||||
@override
|
||||
Future<CursorPagingData<SnStickerPack>> build() {
|
||||
return fetch(cursor: null);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<CursorPagingData<SnStickerPack>> fetch({
|
||||
required String? cursor,
|
||||
}) async {
|
||||
final client = ref.read(apiClientProvider);
|
||||
final offset = cursor == null ? 0 : int.parse(cursor);
|
||||
|
||||
final response = await client.get(
|
||||
'/sphere/stickers',
|
||||
queryParameters: {'offset': offset, 'take': 20},
|
||||
);
|
||||
|
||||
final total = int.parse(response.headers.value('X-Total') ?? '0');
|
||||
final List<dynamic> data = response.data;
|
||||
final stickers = data.map((e) => SnStickerPack.fromJson(e)).toList();
|
||||
|
||||
final hasMore = offset + stickers.length < total;
|
||||
final nextCursor = hasMore ? (offset + stickers.length).toString() : null;
|
||||
|
||||
return CursorPagingData(
|
||||
items: stickers,
|
||||
hasMore: hasMore,
|
||||
nextCursor: nextCursor,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// User-facing marketplace screen for browsing sticker packs.
|
||||
/// This version does NOT rely on publisher name (no pubName).
|
||||
class MarketplaceStickersScreen extends HookConsumerWidget {
|
||||
const MarketplaceStickersScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return AppScaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('stickers').tr(),
|
||||
actions: const [Gap(8)],
|
||||
),
|
||||
body: const SliverMarketplaceStickerPacksList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SliverMarketplaceStickerPacksList extends HookConsumerWidget {
|
||||
const SliverMarketplaceStickerPacksList({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return PagingHelperView(
|
||||
provider: marketplaceStickerPacksNotifierProvider,
|
||||
futureRefreshable: marketplaceStickerPacksNotifierProvider.future,
|
||||
notifierRefreshable: marketplaceStickerPacksNotifierProvider.notifier,
|
||||
contentBuilder:
|
||||
(data, widgetCount, endItemView) => ListView.builder(
|
||||
padding: EdgeInsets.zero,
|
||||
itemCount: widgetCount,
|
||||
itemBuilder: (context, index) {
|
||||
if (index == widgetCount - 1) {
|
||||
return endItemView;
|
||||
}
|
||||
|
||||
final pack = data.items[index];
|
||||
return ListTile(
|
||||
title: Text(pack.name),
|
||||
subtitle: Text(pack.description),
|
||||
trailing: const Icon(Symbols.chevron_right),
|
||||
onTap: () {
|
||||
// Navigate to user-facing sticker pack detail page.
|
||||
// Adjust the route name/parameters if your app uses different ones.
|
||||
context.pushNamed(
|
||||
'stickerPackDetail',
|
||||
pathParameters: {'packId': pack.id},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
32
lib/screens/stickers/marketplace.g.dart
Normal file
32
lib/screens/stickers/marketplace.g.dart
Normal file
@@ -0,0 +1,32 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'marketplace.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$marketplaceStickerPacksNotifierHash() =>
|
||||
r'b62ae8b7f5c4f8bb3be8c17fc005ea26da355187';
|
||||
|
||||
/// See also [MarketplaceStickerPacksNotifier].
|
||||
@ProviderFor(MarketplaceStickerPacksNotifier)
|
||||
final marketplaceStickerPacksNotifierProvider =
|
||||
AutoDisposeAsyncNotifierProvider<
|
||||
MarketplaceStickerPacksNotifier,
|
||||
CursorPagingData<SnStickerPack>
|
||||
>.internal(
|
||||
MarketplaceStickerPacksNotifier.new,
|
||||
name: r'marketplaceStickerPacksNotifierProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$marketplaceStickerPacksNotifierHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef _$MarketplaceStickerPacksNotifier =
|
||||
AutoDisposeAsyncNotifier<CursorPagingData<SnStickerPack>>;
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
||||
230
lib/screens/stickers/pack_detail.dart
Normal file
230
lib/screens/stickers/pack_detail.dart
Normal file
@@ -0,0 +1,230 @@
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:island/models/sticker.dart';
|
||||
import 'package:island/pods/network.dart';
|
||||
import 'package:island/screens/creators/stickers/stickers.dart';
|
||||
import 'package:island/widgets/alert.dart';
|
||||
import 'package:island/widgets/app_scaffold.dart';
|
||||
import 'package:island/widgets/content/cloud_files.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
|
||||
part 'pack_detail.g.dart'; // generated by riverpod_annotation build_runner
|
||||
|
||||
/// Marketplace version of sticker pack detail page (no publisher dependency).
|
||||
/// Shows all stickers in the pack and provides a button to add the sticker.
|
||||
/// API interactions are intentionally left blank per request.
|
||||
@riverpod
|
||||
Future<List<SnSticker>> marketplaceStickerPackContent(
|
||||
Ref ref, {
|
||||
required String packId,
|
||||
}) async {
|
||||
final apiClient = ref.watch(apiClientProvider);
|
||||
final resp = await apiClient.get('/sphere/stickers/$packId/content');
|
||||
return (resp.data as List).map((e) => SnSticker.fromJson(e)).toList();
|
||||
}
|
||||
|
||||
@riverpod
|
||||
Future<bool> marketplaceStickerPackOwnership(
|
||||
Ref ref, {
|
||||
required String packId,
|
||||
}) async {
|
||||
final api = ref.watch(apiClientProvider);
|
||||
try {
|
||||
await api.get('/sphere/stickers/$packId/own');
|
||||
// If not 404, consider owned
|
||||
return true;
|
||||
} on Object catch (e) {
|
||||
// Dio error handling agnostic: treat 404 as not-owned, rethrow others
|
||||
final msg = e.toString();
|
||||
if (msg.contains('404')) return false;
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
class MarketplaceStickerPackDetailScreen extends HookConsumerWidget {
|
||||
final String id;
|
||||
const MarketplaceStickerPackDetailScreen({super.key, required this.id});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
// Pack metadata provider exists globally in creators file; reuse it.
|
||||
final pack = ref.watch(stickerPackProvider(id));
|
||||
final packContent = ref.watch(
|
||||
marketplaceStickerPackContentProvider(packId: id),
|
||||
);
|
||||
final owned = ref.watch(
|
||||
marketplaceStickerPackOwnershipProvider(packId: id),
|
||||
);
|
||||
|
||||
// Add entire pack to user's collection
|
||||
Future<void> addPackToMyCollection() async {
|
||||
final apiClient = ref.watch(apiClientProvider);
|
||||
await apiClient.post('/sphere/stickers/$id/own');
|
||||
HapticFeedback.selectionClick();
|
||||
ref.invalidate(marketplaceStickerPackOwnershipProvider(packId: id));
|
||||
if (!context.mounted) return;
|
||||
showSnackBar('stickerPackAdded'.tr());
|
||||
}
|
||||
|
||||
// Remove ownership of the pack
|
||||
Future<void> removePackFromMyCollection() async {
|
||||
final apiClient = ref.watch(apiClientProvider);
|
||||
await apiClient.delete('/sphere/stickers/$id/own');
|
||||
HapticFeedback.selectionClick();
|
||||
ref.invalidate(marketplaceStickerPackOwnershipProvider(packId: id));
|
||||
if (!context.mounted) return;
|
||||
showSnackBar('stickerPackRemoved'.tr());
|
||||
}
|
||||
|
||||
return AppScaffold(
|
||||
appBar: AppBar(title: Text(pack.value?.name ?? 'loading'.tr())),
|
||||
body: pack.when(
|
||||
data: (p) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
// Pack meta
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Text(p?.description ?? ''),
|
||||
Row(
|
||||
spacing: 4,
|
||||
children: [
|
||||
const Icon(Symbols.folder, size: 16),
|
||||
Text(
|
||||
'${packContent.value?.length ?? 0}/24',
|
||||
style: GoogleFonts.robotoMono(),
|
||||
),
|
||||
],
|
||||
).opacity(0.85),
|
||||
Row(
|
||||
spacing: 4,
|
||||
children: [
|
||||
const Icon(Symbols.sell, size: 16),
|
||||
Text(p?.prefix ?? '', style: GoogleFonts.robotoMono()),
|
||||
],
|
||||
).opacity(0.85),
|
||||
Row(
|
||||
spacing: 4,
|
||||
children: [
|
||||
const Icon(Symbols.tag, size: 16),
|
||||
SelectableText(
|
||||
p?.id ?? id,
|
||||
style: GoogleFonts.robotoMono(),
|
||||
),
|
||||
],
|
||||
).opacity(0.85),
|
||||
],
|
||||
).padding(horizontal: 24, vertical: 24),
|
||||
const Divider(height: 1),
|
||||
// Stickers grid
|
||||
Expanded(
|
||||
child: packContent.when(
|
||||
data:
|
||||
(stickers) => RefreshIndicator(
|
||||
onRefresh:
|
||||
() => ref.refresh(
|
||||
marketplaceStickerPackContentProvider(
|
||||
packId: id,
|
||||
).future,
|
||||
),
|
||||
child: GridView.builder(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 24,
|
||||
vertical: 20,
|
||||
),
|
||||
gridDelegate:
|
||||
const SliverGridDelegateWithMaxCrossAxisExtent(
|
||||
maxCrossAxisExtent: 96,
|
||||
mainAxisSpacing: 12,
|
||||
crossAxisSpacing: 12,
|
||||
),
|
||||
itemCount: stickers.length,
|
||||
itemBuilder: (context, index) {
|
||||
final sticker = stickers[index];
|
||||
return Tooltip(
|
||||
message: ':${p?.prefix ?? ''}${sticker.slug}:',
|
||||
child: ClipRRect(
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(8),
|
||||
),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color:
|
||||
Theme.of(
|
||||
context,
|
||||
).colorScheme.surfaceContainer,
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(8),
|
||||
),
|
||||
),
|
||||
child: AspectRatio(
|
||||
aspectRatio: 1,
|
||||
child: CloudImageWidget(
|
||||
fileId: sticker.imageId,
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
error:
|
||||
(err, _) =>
|
||||
Text(
|
||||
'Error: $err',
|
||||
).textAlignment(TextAlign.center).center(),
|
||||
loading: () => const CircularProgressIndicator().center(),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 24, vertical: 8),
|
||||
child: owned.when(
|
||||
data:
|
||||
(isOwned) => FilledButton.icon(
|
||||
onPressed:
|
||||
isOwned
|
||||
? removePackFromMyCollection
|
||||
: addPackToMyCollection,
|
||||
icon: Icon(
|
||||
isOwned ? Symbols.remove_circle : Symbols.add_circle,
|
||||
),
|
||||
label: Text(
|
||||
isOwned ? 'removePack'.tr() : 'addPack'.tr(),
|
||||
),
|
||||
),
|
||||
loading:
|
||||
() => const SizedBox(
|
||||
height: 32,
|
||||
width: 32,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
),
|
||||
error:
|
||||
(_, _) => OutlinedButton.icon(
|
||||
onPressed: addPackToMyCollection,
|
||||
icon: const Icon(Symbols.add_circle),
|
||||
label: Text('addPack').tr(),
|
||||
),
|
||||
),
|
||||
),
|
||||
Gap(MediaQuery.of(context).padding.bottom),
|
||||
],
|
||||
);
|
||||
},
|
||||
error:
|
||||
(err, _) =>
|
||||
Text('Error: $err').textAlignment(TextAlign.center).center(),
|
||||
loading: () => const CircularProgressIndicator().center(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
317
lib/screens/stickers/pack_detail.g.dart
Normal file
317
lib/screens/stickers/pack_detail.g.dart
Normal file
@@ -0,0 +1,317 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'pack_detail.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$marketplaceStickerPackContentHash() =>
|
||||
r'886f8305c978dbea6e5d990a7d555048ac704a5d';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
_SystemHash._();
|
||||
|
||||
static int combine(int hash, int value) {
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + value);
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
|
||||
return hash ^ (hash >> 6);
|
||||
}
|
||||
|
||||
static int finish(int hash) {
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
|
||||
// ignore: parameter_assignments
|
||||
hash = hash ^ (hash >> 11);
|
||||
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
|
||||
}
|
||||
}
|
||||
|
||||
/// Marketplace version of sticker pack detail page (no publisher dependency).
|
||||
/// Shows all stickers in the pack and provides a button to add the sticker.
|
||||
/// API interactions are intentionally left blank per request.
|
||||
///
|
||||
/// Copied from [marketplaceStickerPackContent].
|
||||
@ProviderFor(marketplaceStickerPackContent)
|
||||
const marketplaceStickerPackContentProvider =
|
||||
MarketplaceStickerPackContentFamily();
|
||||
|
||||
/// Marketplace version of sticker pack detail page (no publisher dependency).
|
||||
/// Shows all stickers in the pack and provides a button to add the sticker.
|
||||
/// API interactions are intentionally left blank per request.
|
||||
///
|
||||
/// Copied from [marketplaceStickerPackContent].
|
||||
class MarketplaceStickerPackContentFamily
|
||||
extends Family<AsyncValue<List<SnSticker>>> {
|
||||
/// Marketplace version of sticker pack detail page (no publisher dependency).
|
||||
/// Shows all stickers in the pack and provides a button to add the sticker.
|
||||
/// API interactions are intentionally left blank per request.
|
||||
///
|
||||
/// Copied from [marketplaceStickerPackContent].
|
||||
const MarketplaceStickerPackContentFamily();
|
||||
|
||||
/// Marketplace version of sticker pack detail page (no publisher dependency).
|
||||
/// Shows all stickers in the pack and provides a button to add the sticker.
|
||||
/// API interactions are intentionally left blank per request.
|
||||
///
|
||||
/// Copied from [marketplaceStickerPackContent].
|
||||
MarketplaceStickerPackContentProvider call({required String packId}) {
|
||||
return MarketplaceStickerPackContentProvider(packId: packId);
|
||||
}
|
||||
|
||||
@override
|
||||
MarketplaceStickerPackContentProvider getProviderOverride(
|
||||
covariant MarketplaceStickerPackContentProvider provider,
|
||||
) {
|
||||
return call(packId: provider.packId);
|
||||
}
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _dependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
|
||||
_allTransitiveDependencies;
|
||||
|
||||
@override
|
||||
String? get name => r'marketplaceStickerPackContentProvider';
|
||||
}
|
||||
|
||||
/// Marketplace version of sticker pack detail page (no publisher dependency).
|
||||
/// Shows all stickers in the pack and provides a button to add the sticker.
|
||||
/// API interactions are intentionally left blank per request.
|
||||
///
|
||||
/// Copied from [marketplaceStickerPackContent].
|
||||
class MarketplaceStickerPackContentProvider
|
||||
extends AutoDisposeFutureProvider<List<SnSticker>> {
|
||||
/// Marketplace version of sticker pack detail page (no publisher dependency).
|
||||
/// Shows all stickers in the pack and provides a button to add the sticker.
|
||||
/// API interactions are intentionally left blank per request.
|
||||
///
|
||||
/// Copied from [marketplaceStickerPackContent].
|
||||
MarketplaceStickerPackContentProvider({required String packId})
|
||||
: this._internal(
|
||||
(ref) => marketplaceStickerPackContent(
|
||||
ref as MarketplaceStickerPackContentRef,
|
||||
packId: packId,
|
||||
),
|
||||
from: marketplaceStickerPackContentProvider,
|
||||
name: r'marketplaceStickerPackContentProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$marketplaceStickerPackContentHash,
|
||||
dependencies: MarketplaceStickerPackContentFamily._dependencies,
|
||||
allTransitiveDependencies:
|
||||
MarketplaceStickerPackContentFamily._allTransitiveDependencies,
|
||||
packId: packId,
|
||||
);
|
||||
|
||||
MarketplaceStickerPackContentProvider._internal(
|
||||
super._createNotifier, {
|
||||
required super.name,
|
||||
required super.dependencies,
|
||||
required super.allTransitiveDependencies,
|
||||
required super.debugGetCreateSourceHash,
|
||||
required super.from,
|
||||
required this.packId,
|
||||
}) : super.internal();
|
||||
|
||||
final String packId;
|
||||
|
||||
@override
|
||||
Override overrideWith(
|
||||
FutureOr<List<SnSticker>> Function(
|
||||
MarketplaceStickerPackContentRef provider,
|
||||
)
|
||||
create,
|
||||
) {
|
||||
return ProviderOverride(
|
||||
origin: this,
|
||||
override: MarketplaceStickerPackContentProvider._internal(
|
||||
(ref) => create(ref as MarketplaceStickerPackContentRef),
|
||||
from: from,
|
||||
name: null,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
debugGetCreateSourceHash: null,
|
||||
packId: packId,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
AutoDisposeFutureProviderElement<List<SnSticker>> createElement() {
|
||||
return _MarketplaceStickerPackContentProviderElement(this);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is MarketplaceStickerPackContentProvider &&
|
||||
other.packId == packId;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||
hash = _SystemHash.combine(hash, packId.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
mixin MarketplaceStickerPackContentRef
|
||||
on AutoDisposeFutureProviderRef<List<SnSticker>> {
|
||||
/// The parameter `packId` of this provider.
|
||||
String get packId;
|
||||
}
|
||||
|
||||
class _MarketplaceStickerPackContentProviderElement
|
||||
extends AutoDisposeFutureProviderElement<List<SnSticker>>
|
||||
with MarketplaceStickerPackContentRef {
|
||||
_MarketplaceStickerPackContentProviderElement(super.provider);
|
||||
|
||||
@override
|
||||
String get packId => (origin as MarketplaceStickerPackContentProvider).packId;
|
||||
}
|
||||
|
||||
String _$marketplaceStickerPackOwnershipHash() =>
|
||||
r'e5dd301c309fac958729d13d984ce7a77edbe7e6';
|
||||
|
||||
/// See also [marketplaceStickerPackOwnership].
|
||||
@ProviderFor(marketplaceStickerPackOwnership)
|
||||
const marketplaceStickerPackOwnershipProvider =
|
||||
MarketplaceStickerPackOwnershipFamily();
|
||||
|
||||
/// See also [marketplaceStickerPackOwnership].
|
||||
class MarketplaceStickerPackOwnershipFamily extends Family<AsyncValue<bool>> {
|
||||
/// See also [marketplaceStickerPackOwnership].
|
||||
const MarketplaceStickerPackOwnershipFamily();
|
||||
|
||||
/// See also [marketplaceStickerPackOwnership].
|
||||
MarketplaceStickerPackOwnershipProvider call({required String packId}) {
|
||||
return MarketplaceStickerPackOwnershipProvider(packId: packId);
|
||||
}
|
||||
|
||||
@override
|
||||
MarketplaceStickerPackOwnershipProvider getProviderOverride(
|
||||
covariant MarketplaceStickerPackOwnershipProvider provider,
|
||||
) {
|
||||
return call(packId: provider.packId);
|
||||
}
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _dependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
|
||||
_allTransitiveDependencies;
|
||||
|
||||
@override
|
||||
String? get name => r'marketplaceStickerPackOwnershipProvider';
|
||||
}
|
||||
|
||||
/// See also [marketplaceStickerPackOwnership].
|
||||
class MarketplaceStickerPackOwnershipProvider
|
||||
extends AutoDisposeFutureProvider<bool> {
|
||||
/// See also [marketplaceStickerPackOwnership].
|
||||
MarketplaceStickerPackOwnershipProvider({required String packId})
|
||||
: this._internal(
|
||||
(ref) => marketplaceStickerPackOwnership(
|
||||
ref as MarketplaceStickerPackOwnershipRef,
|
||||
packId: packId,
|
||||
),
|
||||
from: marketplaceStickerPackOwnershipProvider,
|
||||
name: r'marketplaceStickerPackOwnershipProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$marketplaceStickerPackOwnershipHash,
|
||||
dependencies: MarketplaceStickerPackOwnershipFamily._dependencies,
|
||||
allTransitiveDependencies:
|
||||
MarketplaceStickerPackOwnershipFamily._allTransitiveDependencies,
|
||||
packId: packId,
|
||||
);
|
||||
|
||||
MarketplaceStickerPackOwnershipProvider._internal(
|
||||
super._createNotifier, {
|
||||
required super.name,
|
||||
required super.dependencies,
|
||||
required super.allTransitiveDependencies,
|
||||
required super.debugGetCreateSourceHash,
|
||||
required super.from,
|
||||
required this.packId,
|
||||
}) : super.internal();
|
||||
|
||||
final String packId;
|
||||
|
||||
@override
|
||||
Override overrideWith(
|
||||
FutureOr<bool> Function(MarketplaceStickerPackOwnershipRef provider) create,
|
||||
) {
|
||||
return ProviderOverride(
|
||||
origin: this,
|
||||
override: MarketplaceStickerPackOwnershipProvider._internal(
|
||||
(ref) => create(ref as MarketplaceStickerPackOwnershipRef),
|
||||
from: from,
|
||||
name: null,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
debugGetCreateSourceHash: null,
|
||||
packId: packId,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
AutoDisposeFutureProviderElement<bool> createElement() {
|
||||
return _MarketplaceStickerPackOwnershipProviderElement(this);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is MarketplaceStickerPackOwnershipProvider &&
|
||||
other.packId == packId;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||
hash = _SystemHash.combine(hash, packId.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
mixin MarketplaceStickerPackOwnershipRef on AutoDisposeFutureProviderRef<bool> {
|
||||
/// The parameter `packId` of this provider.
|
||||
String get packId;
|
||||
}
|
||||
|
||||
class _MarketplaceStickerPackOwnershipProviderElement
|
||||
extends AutoDisposeFutureProviderElement<bool>
|
||||
with MarketplaceStickerPackOwnershipRef {
|
||||
_MarketplaceStickerPackOwnershipProviderElement(super.provider);
|
||||
|
||||
@override
|
||||
String get packId =>
|
||||
(origin as MarketplaceStickerPackOwnershipProvider).packId;
|
||||
}
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
||||
@@ -15,6 +15,7 @@ Future<XFile?> cropImage(
|
||||
BuildContext context, {
|
||||
required XFile image,
|
||||
List<CropAspectRatio?>? allowedAspectRatios,
|
||||
bool replacePath = false,
|
||||
}) async {
|
||||
final result = await showMaterialImageCropper(
|
||||
context,
|
||||
@@ -34,7 +35,7 @@ Future<XFile?> cropImage(
|
||||
croppedFile.dispose();
|
||||
return XFile.fromData(
|
||||
croppedBytes.buffer.asUint8List(),
|
||||
path: image.path,
|
||||
path: !replacePath ? image.path : null,
|
||||
mimeType: image.mimeType,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -20,6 +20,33 @@ extension DurationFormatter on Duration {
|
||||
return '${isNegative ? '-' : ''}$hours:$minutes:$seconds';
|
||||
}
|
||||
|
||||
String formatShortDuration() {
|
||||
final isNegative = inMicroseconds < 0;
|
||||
final positiveDuration = isNegative ? -this : this;
|
||||
|
||||
final hours = positiveDuration.inHours;
|
||||
final minutes = (positiveDuration.inMinutes % 60).toString().padLeft(
|
||||
2,
|
||||
'0',
|
||||
);
|
||||
final seconds = (positiveDuration.inSeconds % 60).toString().padLeft(
|
||||
2,
|
||||
'0',
|
||||
);
|
||||
final milliseconds = (positiveDuration.inMilliseconds % 1000)
|
||||
.toString()
|
||||
.padLeft(3, '0');
|
||||
|
||||
String result;
|
||||
if (hours > 0) {
|
||||
result =
|
||||
'${isNegative ? '-' : ''}${hours.toString().padLeft(2, '0')}:$minutes:$seconds.$milliseconds';
|
||||
} else {
|
||||
result = '${isNegative ? '-' : ''}$minutes:$seconds.$milliseconds';
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
String formatOffset() {
|
||||
final isNegative = inMicroseconds < 0;
|
||||
final positiveDuration = isNegative ? -this : this;
|
||||
|
||||
228
lib/services/update_service.dart
Normal file
228
lib/services/update_service.dart
Normal file
@@ -0,0 +1,228 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:island/widgets/content/sheet.dart';
|
||||
|
||||
/// Data model for a GitHub release we care about
|
||||
class GithubReleaseInfo {
|
||||
final String tagName; // e.g. 3.1.0+118
|
||||
final String name; // release title
|
||||
final String body; // changelog markdown
|
||||
final String htmlUrl; // release page
|
||||
final DateTime createdAt;
|
||||
|
||||
const GithubReleaseInfo({
|
||||
required this.tagName,
|
||||
required this.name,
|
||||
required this.body,
|
||||
required this.htmlUrl,
|
||||
required this.createdAt,
|
||||
});
|
||||
}
|
||||
|
||||
/// Parses version and build number from "x.y.z+build"
|
||||
class _ParsedVersion implements Comparable<_ParsedVersion> {
|
||||
final int major;
|
||||
final int minor;
|
||||
final int patch;
|
||||
final int build;
|
||||
|
||||
const _ParsedVersion(this.major, this.minor, this.patch, this.build);
|
||||
|
||||
static _ParsedVersion? tryParse(String input) {
|
||||
// Expect format like 0.0.0+00 (build after '+'). Allow missing build as 0.
|
||||
final partsPlus = input.split('+');
|
||||
final core = partsPlus[0].trim();
|
||||
final buildStr = partsPlus.length > 1 ? partsPlus[1].trim() : '0';
|
||||
final coreParts = core.split('.');
|
||||
if (coreParts.length != 3) return null;
|
||||
|
||||
final major = int.tryParse(coreParts[0]) ?? 0;
|
||||
final minor = int.tryParse(coreParts[1]) ?? 0;
|
||||
final patch = int.tryParse(coreParts[2]) ?? 0;
|
||||
final build = int.tryParse(buildStr) ?? 0;
|
||||
|
||||
return _ParsedVersion(major, minor, patch, build);
|
||||
}
|
||||
|
||||
@override
|
||||
int compareTo(_ParsedVersion other) {
|
||||
if (major != other.major) return major.compareTo(other.major);
|
||||
if (minor != other.minor) return minor.compareTo(other.minor);
|
||||
if (patch != other.patch) return patch.compareTo(other.patch);
|
||||
return build.compareTo(other.build);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() => '$major.$minor.$patch+$build';
|
||||
}
|
||||
|
||||
class UpdateService {
|
||||
UpdateService({Dio? dio})
|
||||
: _dio =
|
||||
dio ??
|
||||
Dio(
|
||||
BaseOptions(
|
||||
headers: {
|
||||
// Identify the app to GitHub; avoids some rate-limits and adds clarity
|
||||
'Accept': 'application/vnd.github+json',
|
||||
'User-Agent': 'solian-update-checker',
|
||||
},
|
||||
connectTimeout: const Duration(seconds: 10),
|
||||
receiveTimeout: const Duration(seconds: 15),
|
||||
),
|
||||
);
|
||||
|
||||
final Dio _dio;
|
||||
|
||||
static const _releasesLatestApi =
|
||||
'https://api.github.com/repos/solsynth/solian/releases/latest';
|
||||
|
||||
/// Checks GitHub for the latest release and compares against the current app version.
|
||||
/// If update is available, shows a bottom sheet with changelog and an action to open release page.
|
||||
Future<void> checkForUpdates(BuildContext context) async {
|
||||
try {
|
||||
final release = await fetchLatestRelease();
|
||||
if (release == null) return;
|
||||
|
||||
final info = await PackageInfo.fromPlatform();
|
||||
final localVersionStr = '${info.version}+${info.buildNumber}';
|
||||
|
||||
final latest = _ParsedVersion.tryParse(release.tagName);
|
||||
final local = _ParsedVersion.tryParse(localVersionStr);
|
||||
|
||||
if (latest == null || local == null) {
|
||||
// If parsing fails, do nothing silently
|
||||
return;
|
||||
}
|
||||
|
||||
final needsUpdate = latest.compareTo(local) > 0;
|
||||
if (!needsUpdate) return;
|
||||
|
||||
if (!context.mounted) return;
|
||||
|
||||
// Delay to ensure UI is ready (if called at startup)
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
|
||||
await showUpdateSheet(context, release);
|
||||
} catch (_) {
|
||||
// Ignore errors (network, api, etc.)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/// Manually show the update sheet with a provided release.
|
||||
/// Useful for About page or testing.
|
||||
Future<void> showUpdateSheet(
|
||||
BuildContext context,
|
||||
GithubReleaseInfo release,
|
||||
) async {
|
||||
if (!context.mounted) return;
|
||||
await showModalBottomSheet(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
useRootNavigator: true,
|
||||
builder:
|
||||
(ctx) => _UpdateSheet(
|
||||
release: release,
|
||||
onOpen: () async {
|
||||
final uri = Uri.parse(release.htmlUrl);
|
||||
if (await canLaunchUrl(uri)) {
|
||||
await launchUrl(uri, mode: LaunchMode.externalApplication);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Fetch the latest release info from GitHub.
|
||||
/// Public so other screens (e.g., About) can manually trigger update checks.
|
||||
Future<GithubReleaseInfo?> fetchLatestRelease() async {
|
||||
final resp = await _dio.get(_releasesLatestApi);
|
||||
if (resp.statusCode != 200) return null;
|
||||
final data = resp.data as Map<String, dynamic>;
|
||||
|
||||
final tagName = (data['tag_name'] ?? '').toString();
|
||||
final name = (data['name'] ?? tagName).toString();
|
||||
final body = (data['body'] ?? '').toString();
|
||||
final htmlUrl = (data['html_url'] ?? '').toString();
|
||||
final createdAtStr = (data['created_at'] ?? '').toString();
|
||||
final createdAt = DateTime.tryParse(createdAtStr) ?? DateTime.now();
|
||||
|
||||
if (tagName.isEmpty || htmlUrl.isEmpty) return null;
|
||||
|
||||
return GithubReleaseInfo(
|
||||
tagName: tagName,
|
||||
name: name,
|
||||
body: body,
|
||||
htmlUrl: htmlUrl,
|
||||
createdAt: createdAt,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _UpdateSheet extends StatelessWidget {
|
||||
const _UpdateSheet({required this.release, required this.onOpen});
|
||||
|
||||
final GithubReleaseInfo release;
|
||||
final VoidCallback onOpen;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
return SheetScaffold(
|
||||
titleText: 'Update available',
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(
|
||||
bottom: 16 + MediaQuery.of(context).padding.bottom,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(release.name, style: theme.textTheme.titleMedium).bold(),
|
||||
Text(release.tagName).fontSize(12),
|
||||
],
|
||||
).padding(vertical: 16, horizontal: 16),
|
||||
const Divider(height: 1),
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 16,
|
||||
),
|
||||
child: SelectableText(
|
||||
release.body.isEmpty
|
||||
? 'No changelog provided.'
|
||||
: release.body,
|
||||
style: theme.textTheme.bodyMedium,
|
||||
),
|
||||
),
|
||||
),
|
||||
Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: FilledButton.icon(
|
||||
onPressed: onOpen,
|
||||
icon: const Icon(Icons.open_in_new),
|
||||
label: const Text('Open release page'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
).padding(horizontal: 16),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user